// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. All rights reserved.

package resource

import (
	_ "context"
	"encoding/json"
	"fmt"
	"gopkg.in/yaml.v3"
	"io"
	"log/slog"
	_ "net/url"
	_ "os"
	"os/exec"
	"strings"
	"text/template"
)

type CommandExecutor func(value any) ([]byte, error)
type CommandExtractAttributes func(output []byte, target any) error

type CommandArg string

type Command struct {
	Path      string                   `json:"path" yaml:"path"`
	Args      []CommandArg             `json:"args" yaml:"args"`
	Executor  CommandExecutor          `json:"-" yaml:"-"`
	Extractor CommandExtractAttributes `json:"-" yaml:"-"`
}

func NewCommand() *Command {
	c := &Command{}
	c.Executor = func(value any) ([]byte, error) {
		args, err := c.Template(value)
		if err != nil {
			return nil, err
		}
		cmd := exec.Command(c.Path, args...)

		slog.Info("execute() - cmd", "path", c.Path, "args", args)
		output, stdoutPipeErr := cmd.StdoutPipe()
		if stdoutPipeErr != nil {
			return nil, stdoutPipeErr
		}

		stderr, pipeErr := cmd.StderrPipe()
		if pipeErr != nil {
			return nil, pipeErr
		}

		if startErr := cmd.Start(); startErr != nil {
			return nil, startErr
		}

		slog.Info("execute() - start", "cmd", cmd)
		stdOutOutput, _ := io.ReadAll(output)
		stdErrOutput, _ := io.ReadAll(stderr)

		slog.Info("execute() - io", "stdout", string(stdOutOutput), "stderr", string(stdErrOutput))
		waitErr := cmd.Wait()

		slog.Info("execute()", "path", c.Path, "args", args, "output", string(stdOutOutput), "error", string(stdErrOutput))

		if len(stdErrOutput) > 0 {
			return stdOutOutput, fmt.Errorf(string(stdErrOutput))
		}
		return stdOutOutput, waitErr
	}
	return c
}

func (c *Command) Load(r io.Reader) error {
	decoder := NewYAMLDecoder(r)
	return decoder.Decode(c)
}

func (c *Command) LoadDecl(yamlResourceDeclaration string) error {
	decoder := NewYAMLStringDecoder(yamlResourceDeclaration)
	return decoder.Decode(c)
}

func (c *Command) Template(value any) ([]string, error) {
	var args []string = make([]string, 0, len(c.Args) * 2)
	for i, arg := range c.Args {
		var commandLineArg strings.Builder
		err := template.Must(template.New(fmt.Sprintf("arg%d", i)).Parse(string(arg))).Execute(&commandLineArg, value)
		if err != nil {
			return nil, err
		}
		if commandLineArg.Len() > 0 {
			splitArg := strings.Split(commandLineArg.String(), " ")
			slog.Info("Template()", "split", splitArg, "len", len(splitArg))
			args = append(args, splitArg...)
		}
	}
	
	slog.Info("Template()", "Args", c.Args, "lencargs", len(c.Args), "args", args, "lenargs", len(args), "value", value)
	return args, nil
}

func (c *Command) Execute(value any) ([]byte, error) {
	return c.Executor(value)
}

func (c *CommandArg) UnmarshalValue(value string) error {
	*c = CommandArg(value)
	return nil
}

func (c *CommandArg) UnmarshalJSON(data []byte) error {
	var s string
	if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil {
		return unmarshalRouteTypeErr
	}
	return c.UnmarshalValue(s)
}

func (c *CommandArg) UnmarshalYAML(value *yaml.Node) error {
	var s string
	if err := value.Decode(&s); err != nil {
		return err
	}
	return c.UnmarshalValue(s)
}
