package v1

import (
	"bytes"
	"encoding/json"
	"fmt"
	"reflect"
	"strings"

	"github.com/mohae/deepcopy"
	"github.com/pelletier/go-toml"
)

// +k8s:deepcopy-gen=false
// +kubebuilder:validation:Type=string
type TomlConfig struct {
	MP map[string]any `json:"-"`
}

func (c *TomlConfig) MarshalTOML() ([]byte, error) {
	if c == nil {
		return nil, nil
	}

	buff := new(bytes.Buffer)
	encoder := toml.NewEncoder(buff)
	encoder.Indentation("")
	encoder.Order(toml.OrderAlphabetical)
	err := encoder.Encode(c.MP)
	if err != nil {
		return nil, fmt.Errorf("encode TomlConfig: %w", err)
	}

	return buff.Bytes(), nil
}

func (c *TomlConfig) UnmarshalTOML(data []byte) error {
	if err := toml.Unmarshal(data, &c.MP); err != nil {
		return fmt.Errorf("unmarshal TomlConfig: %w", err)
	}
	return nil
}

func (c *TomlConfig) MarshalJSON() ([]byte, error) {
	t, err := c.MarshalTOML()
	if err != nil {
		return nil, fmt.Errorf("marshal Json in TomlConfig: %w", err)
	}

	return json.Marshal(string(t))
}

func (c *TomlConfig) UnmarshalJSON(data []byte) error {
	var value any
	err := json.Unmarshal(data, &value)
	if err != nil {
		return fmt.Errorf("unmarshal Json in TomlConfig: %w", err)
	}

	switch s := value.(type) {
	case string:
		err = toml.Unmarshal([]byte(s), &c.MP)
		if err != nil {
			return fmt.Errorf("unmarshal Json in TomlConfig: %w", err)
		}
		return nil
	case map[string]any:
		// If v is a *map[string]interface{}, numbers are converted to int64 or float64
		// using s directly all numbers are float type of go
		// Keep the behavior Unmarshal *map[string]interface{} directly we unmarshal again here.
		err = json.Unmarshal(data, &c.MP)
		if err != nil {
			return fmt.Errorf("unmarshal Json in TomlConfig: %w", err)
		}
		return nil
	default:
		return fmt.Errorf("unknown type: %v", reflect.TypeOf(value))
	}
}

func (c *TomlConfig) DeepCopyJsonObject() *TomlConfig {
	if c == nil {
		return nil
	}
	if c.MP == nil {
		return NewTomlConfig(nil)
	}

	MP := deepcopy.Copy(c.MP).(map[string]any)
	return NewTomlConfig(MP)
}

func (c *TomlConfig) DeepCopy() *TomlConfig {
	return c.DeepCopyJsonObject()
}

func (c *TomlConfig) DeepCopyInto(out *TomlConfig) {
	*out = *c
	out.MP = c.DeepCopyJsonObject().MP
}

func NewTomlConfig(o map[string]any) *TomlConfig {
	return &TomlConfig{o}
}

var _ json.Marshaler = &TomlConfig{}
var _ json.Unmarshaler = &TomlConfig{}

func (c *TomlConfig) Get(key string) (value any) {
	if c == nil {
		return nil
	}

	v := get(c.MP, key)
	if v == nil {
		return nil
	}

	return v
}

func get(ms map[string]any, key string) (value any) {
	ks := strings.SplitN(key, ".", 2)
	if len(ks) == 1 {
		value = ms[key]
		return
	}

	v := strKeyMap(ms[ks[0]])
	vMap, ok := v.(map[string]any)
	if !ok {
		return nil
	}

	return get(vMap, ks[1])
}

func (c *TomlConfig) Set(key string, value any) {
	set(c.MP, key, value)
}

func set(ms map[string]any, key string, value any) {
	ks := strings.SplitN(key, ".", 2)
	if len(ks) == 1 {
		ms[key] = value
		return
	}

	v := strKeyMap(ms[ks[0]])
	if v == nil {
		v = make(map[string]any)
		ms[ks[0]] = v
	}

	vMap, ok := v.(map[string]any)
	if !ok {
		panic(v)
	}

	set(vMap, ks[1], value)
}

func strKeyMap(val any) any {
	m, ok := val.(map[any]any)
	if ok {
		ret := map[string]any{}
		for k, v := range m {
			kk, ok := k.(string)
			if !ok {
				return val
			}
			ret[kk] = strKeyMap(v)
		}
		return ret
	}

	rv := reflect.ValueOf(val)
	if rv.Kind() == reflect.Slice {
		var ret []any
		for i := 0; i < rv.Len(); i++ {
			ret = append(ret, strKeyMap(rv.Index(i).Interface()))
		}
		return ret
	}

	return val
}
