package v1

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

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

// +kubebuilder:validation:Type=string
type TomlConfig struct {
	MP map[string]interface{} `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) {
	toml, err := c.MarshalTOML()
	if err != nil {
		return nil, fmt.Errorf("marshal Json in TomlConfig: %w", err)
	}

	return json.Marshal(string(toml))
}

func (c *TomlConfig) UnmarshalJSON(data []byte) error {
	var value interface{}
	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]interface{}:
		// 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]interface{})
	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]interface{}) *TomlConfig {
	return &TomlConfig{o}
}

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