package unstructured

import (
	"errors"
	"fmt"
	"github.com/hashicorp/go-multierror"
	"github.com/itchyny/gojq"
	"gopkg.in/yaml.v3"
	"strings"
)

func YAMLHasKey(query string, content []byte) (bool, error) {
	data := map[string]interface{}{}

	err := yaml.Unmarshal([]byte(content), &data)
	if err != nil {
		return false, err
	}

	c, err := gojq.Parse(fmt.Sprintf(".%s | ..", query))
	if err != nil {
		return false, err
	}
	code, err := gojq.Compile(c)
	if err != nil {
		return false, err
	}
	iter := code.Run(data)

	v, _ := iter.Next()

	if err, ok := v.(error); ok {
		return false, err
	}
	if v == nil {
		return false, nil
	}
	return true, nil
}

func lookupKey(command string, data map[string]interface{}) (interface{}, error) {
	query, err := gojq.Parse(command)
	if err != nil {
		return nil, err
	}
	code, err := gojq.Compile(query)
	if err != nil {
		return nil, err
	}
	iter := code.Run(data)

	v, ok := iter.Next()
	if !ok {
		return nil, errors.New("failed getting result from gojq")
	}

	return v, nil
}

func LookupString(command string, data map[string]interface{}) (string, error) {
	v, err := lookupKey(command, data)
	if err != nil {
		return "", err
	}

	if t, ok := v.(string); ok {
		return t, nil
	}

	return "", fmt.Errorf("value is not a string: %T", v)
}

func ReplaceValue(command string, data map[string]interface{}) (string, error) {
	v, err := lookupKey(command, data)
	if err != nil {
		return "", err
	}

	if _, ok := v.(map[string]interface{}); ok {
		b, err := yaml.Marshal(v)
		return string(b), err
	}

	return "", fmt.Errorf("unexpected type: %T", v)
}

// func ReplaceKeyValue(command string, data interface{}) (string, error) {
// 	LookupString(commd
// }

func jq(command string, data map[string]interface{}) (map[string]interface{}, error) {
	v, err := lookupKey(command, data)
	if err != nil {
		return make(map[string]interface{}), err
	}

	if err, ok := v.(error); ok {
		return nil, err
	}
	if t, ok := v.(map[string]interface{}); ok {
		return t, nil
	}

	return make(map[string]interface{}), nil
}

func ToYAML(v map[string]interface{}) ([]byte, error) {
	data := map[string]interface{}{}
	var errs error

	for k, value := range v {
		prefix := "."
		equal := "="
		tmplKey := "%s"
		tmplValue := "\"%s\""
		
		// If key has a dash we need to add quotes to it to avoid failure parsing ut
		if strings.Contains(k, "-") {
			tmplKey = "\"%s\""
		}

		// support boolean types
		if value == "true" || value == "false" {
			tmplValue = "%s"
		}
		finalTemplate := prefix + tmplKey + equal + tmplValue
		newData, err := jq(fmt.Sprintf(finalTemplate, k, value), data)
		if err != nil {
			errs = multierror.Append(errs, err)
			continue
		}
		data = newData
	}

	out, err := yaml.Marshal(&data)
	if err != nil {
		errs = multierror.Append(errs, err)
	}
	return out, errs
}

// ToYAMLMap turns a map string interface which describes a yaml file in 'dot.yaml' format to a fully deep marshalled yaml.
func ToYAMLMap(v map[string]interface{}) (map[string]interface{}, error) {
	result := map[string]interface{}{}
	tempData, err := ToYAML(v)
	if err != nil {
		return result, err
	}
	err = yaml.Unmarshal(tempData, &result)

	return result, err
}