package file

import (
	"fmt"
	"reflect"
	"sort"
	"strconv"
	"strings"

	"github.com/traefik/paerser/parser"
)

func decodeRawToNode(data map[string]interface{}, filters ...string) (*parser.Node, error) {
	root := &parser.Node{
		Name: parser.DefaultRootName,
	}

	vData := reflect.ValueOf(data)
	err := decodeRaw(root, vData, filters...)
	if err != nil {
		return nil, err
	}

	return root, nil
}

func decodeRaw(node *parser.Node, vData reflect.Value, filters ...string) error {
	sortedKeys := sortKeys(vData, filters)

	for _, key := range sortedKeys {
		if key.Kind() == reflect.Invalid {
			continue
		}
		if vData.MapIndex(key).IsNil() {
			continue
		}

		value := reflect.ValueOf(vData.MapIndex(key).Interface())

		child := &parser.Node{Name: key.String()}

		switch value.Kind() {
		case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
			fallthrough
		case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
			fallthrough
		case reflect.Float32, reflect.Float64:
			fallthrough
		case reflect.Bool:
			fallthrough
		case reflect.String:
			value, err := getSimpleValue(value)
			if err != nil {
				return err
			}
			child.Value = value
		case reflect.Slice:
			var values []string
			var kind reflect.Kind

			for i := range value.Len() {
				item := value.Index(i)

				// Try to guess the kind of the slice.
				// TODO(ldez): it's related to raw map. Rethink the node parser.
				switch item.Kind() {
				case reflect.Interface:
					if kind < item.Elem().Kind() {
						kind = item.Elem().Kind()
					}
				case reflect.Map:
				// noop
				default:
					if kind < item.Kind() {
						kind = item.Kind()
					}
				}

				switch item.Kind() {
				case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
					fallthrough
				case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
					fallthrough
				case reflect.Bool:
					fallthrough
				case reflect.String:
					fallthrough
				case reflect.Map:
					fallthrough
				case reflect.Interface:
					sValue := reflect.ValueOf(item.Interface())
					if sValue.Kind() == reflect.Map {
						ch := &parser.Node{
							Name: "[" + strconv.Itoa(i) + "]",
						}

						child.Children = append(child.Children, ch)
						err := decodeRaw(ch, sValue)
						if err != nil {
							return err
						}
					} else {
						val, err := getSimpleValue(sValue)
						if err != nil {
							return err
						}
						values = append(values, val)
					}
				default:
					return fmt.Errorf("field %s uses unsupported slice type: %s", child.Name, item.Kind().String())
				}
			}

			// TODO(ldez): the kind is related to raw map. Rethink the node parser.
			child.Value = ""
			if len(values) > 0 {
				child.Value = fmt.Sprintf("%[1]s%[2]d%[1]s%[3]s", defaultRawSliceSeparator, kind, strings.Join(values, defaultRawSliceSeparator))
			}
		case reflect.Map:
			err := decodeRaw(child, value)
			if err != nil {
				return err
			}
		default:
			return fmt.Errorf("field %s uses unsupported type: %s", child.Name, value.Kind().String())
		}

		node.Children = append(node.Children, child)
	}

	return nil
}

func getSimpleValue(item reflect.Value) (string, error) {
	switch item.Kind() {
	case reflect.String:
		return item.String(), nil
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return strconv.FormatInt(item.Int(), 10), nil
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return strconv.FormatUint(item.Uint(), 10), nil
	case reflect.Float32, reflect.Float64:
		return strings.TrimSuffix(strconv.FormatFloat(item.Float(), 'f', 6, 64), ".000000"), nil
	case reflect.Bool:
		return strconv.FormatBool(item.Bool()), nil
	default:
		return "", fmt.Errorf("unsupported simple value type: %s", item.Kind().String())
	}
}

func sortKeys(vData reflect.Value, filters []string) []reflect.Value {
	var sortedKeys []reflect.Value

	for _, v := range vData.MapKeys() {
		rValue := reflect.ValueOf(v.Interface())
		key := rValue.String()

		if len(filters) == 0 {
			sortedKeys = append(sortedKeys, rValue)
			continue
		}

		for _, filter := range filters {
			if strings.EqualFold(key, filter) {
				sortedKeys = append(sortedKeys, rValue)
				continue
			}
		}
	}

	sort.Slice(sortedKeys, func(i, j int) bool {
		return sortedKeys[i].String() < sortedKeys[j].String()
	})

	return sortedKeys
}
