package configmap

import (
	"fmt"
	"github.com/arttor/helmify/pkg/format"
	"io"
	"strings"
	"text/template"

	"github.com/arttor/helmify/pkg/processor"

	"github.com/arttor/helmify/pkg/helmify"
	yamlformat "github.com/arttor/helmify/pkg/yaml"
	"github.com/sirupsen/logrus"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
)

var configMapTempl, _ = template.New("configMap").Parse(
	`{{ .Meta }}
{{- if .Immutable }}
{{ .Immutable }}
{{- end }}
{{- if .BinaryData }}
{{ .BinaryData }}
{{- end }}
{{- if .Data }}
{{ .Data }}
{{- end }}`)

var configMapGVC = schema.GroupVersionKind{
	Group:   "",
	Version: "v1",
	Kind:    "ConfigMap",
}

// New creates processor for k8s ConfigMap resource.
func New() helmify.Processor {
	return &configMap{}
}

type configMap struct{}

// Process k8s ConfigMap object into template. Returns false if not capable of processing given resource type.
func (d configMap) Process(appMeta helmify.AppMetadata, obj *unstructured.Unstructured) (bool, helmify.Template, error) {
	if obj.GroupVersionKind() != configMapGVC {
		return false, nil, nil
	}
	var meta, immutable, binaryData, data string
	meta, err := processor.ProcessObjMeta(appMeta, obj)
	if err != nil {
		return true, nil, err
	}

	if field, exists, _ := unstructured.NestedBool(obj.Object, "immutable"); exists {
		immutable, err = yamlformat.Marshal(map[string]interface{}{"immutable": field}, 0)
		if err != nil {
			return true, nil, err
		}
	}
	if field, exists, _ := unstructured.NestedStringMap(obj.Object, "binaryData"); exists {
		binaryData, err = yamlformat.Marshal(map[string]interface{}{"binaryData": field}, 0)
		if err != nil {
			return true, nil, err
		}
	}

	name := appMeta.TrimName(obj.GetName())
	var values helmify.Values
	if field, exists, _ := unstructured.NestedStringMap(obj.Object, "data"); exists {
		field, values = parseMapData(field, name)
		data, err = yamlformat.Marshal(map[string]interface{}{"data": field}, 0)
		if err != nil {
			return true, nil, err
		}
		data = strings.ReplaceAll(data, "'", "")
	}

	return true, &result{
		name: name + ".yaml",
		data: struct {
			Meta       string
			Immutable  string
			BinaryData string
			Data       string
		}{Meta: meta, Immutable: immutable, BinaryData: binaryData, Data: data},
		values: values,
	}, nil
}

func parseMapData(data map[string]string, configName string) (map[string]string, helmify.Values) {
	values := helmify.Values{}
	for key, value := range data {
		valuesNamePath := []string{configName, key}
		if strings.HasSuffix(key, ".properties") {
			// handle properties
			templated, err := parseProperties(value, valuesNamePath, values)
			if err != nil {
				logrus.WithError(err).Errorf("unable to process configmap data: %v", valuesNamePath)
				continue
			}
			data[key] = templated
			continue
		}
		if strings.Contains(value, "\n") {
			value = format.RemoveTrailingWhitespaces(value)
			templatedVal, err := values.AddYaml(value, 1, false, valuesNamePath...)
			if err != nil {
				logrus.WithError(err).Errorf("unable to process multiline configmap data: %v", valuesNamePath)
				continue
			}
			data[key] = templatedVal
			continue
		}
		// handle plain string
		templatedVal, err := values.Add(value, valuesNamePath...)
		if err != nil {
			logrus.WithError(err).Errorf("unable to process configmap data: %v", valuesNamePath)
			continue
		}
		data[key] = templatedVal
	}
	return data, values
}

// func parseProperties(properties string, path []string, values helmify.Values) (string, error) {
func parseProperties(properties interface{}, path []string, values helmify.Values) (string, error) {
	var res strings.Builder
	for _, line := range strings.Split(strings.TrimSuffix(properties.(string), "\n"), "\n") {
		prop := strings.Split(line, "=")
		if len(prop) != 2 {
			return "", fmt.Errorf("wrong property format in %v: %s", path, line)
		}
		propName, propVal := prop[0], prop[1]
		propNamePath := strings.Split(propName, ".")
		templatedVal, err := values.Add(propVal, append(path, propNamePath...)...)
		if err != nil {
			return "", err
		}
		_, err = res.WriteString(propName + "=" + templatedVal + "\n")
		if err != nil {
			return "", fmt.Errorf("%w: unable to write to string builder", err)
		}
	}
	return res.String(), nil
}

type result struct {
	name string
	data struct {
		Meta       string
		Immutable  string
		BinaryData string
		Data       string
	}
	values helmify.Values
}

func (r *result) Filename() string {
	return r.name
}

func (r *result) Values() helmify.Values {
	return r.values
}

func (r *result) Write(writer io.Writer) error {
	return configMapTempl.Execute(writer, r.data)
}
