package api

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

const (
	keyStart     = '['
	keyEnd       = ']'
	keySeparator = '='
)

func parseFields(opts *ApiOptions) (map[string]interface{}, error) {
	params := make(map[string]interface{})
	parseField := func(f string, isMagic bool) error {
		var valueIndex int
		var keystack []string
		keyStartAt := 0
	parseLoop:
		for i, r := range f {
			switch r {
			case keyStart:
				if keyStartAt == 0 {
					keystack = append(keystack, f[0:i])
				}
				keyStartAt = i + 1
			case keyEnd:
				keystack = append(keystack, f[keyStartAt:i])
			case keySeparator:
				if keyStartAt == 0 {
					keystack = append(keystack, f[0:i])
				}
				valueIndex = i + 1
				break parseLoop
			}
		}

		if len(keystack) == 0 {
			return fmt.Errorf("invalid key: %q", f)
		}

		key := f
		var value interface{} = nil
		if valueIndex == 0 {
			if keystack[len(keystack)-1] != "" {
				return fmt.Errorf("field %q requires a value separated by an '=' sign", key)
			}
		} else {
			key = f[0 : valueIndex-1]
			value = f[valueIndex:]
		}

		if isMagic && value != nil {
			var err error
			value, err = magicFieldValue(value.(string), opts)
			if err != nil {
				return fmt.Errorf("error parsing %q value: %w", key, err)
			}
		}

		destMap := params
		isArray := false
		var subkey string
		for _, k := range keystack {
			if k == "" {
				isArray = true
				continue
			}
			if subkey != "" {
				var err error
				if isArray {
					destMap, err = addParamsSlice(destMap, subkey, k)
					isArray = false
				} else {
					destMap, err = addParamsMap(destMap, subkey)
				}
				if err != nil {
					return err
				}
			}
			subkey = k
		}

		if isArray {
			if value == nil {
				destMap[subkey] = []interface{}{}
			} else {
				if v, exists := destMap[subkey]; exists {
					if existSlice, ok := v.([]interface{}); ok {
						destMap[subkey] = append(existSlice, value)
					} else {
						return fmt.Errorf("expected array type under %q, got %T", subkey, v)
					}
				} else {
					destMap[subkey] = []interface{}{value}
				}
			}
		} else {
			if _, exists := destMap[subkey]; exists {
				return fmt.Errorf("unexpected override existing field under %q", subkey)
			}
			destMap[subkey] = value
		}
		return nil
	}
	for _, f := range opts.RawFields {
		if err := parseField(f, false); err != nil {
			return params, err
		}
	}
	for _, f := range opts.MagicFields {
		if err := parseField(f, true); err != nil {
			return params, err
		}
	}
	return params, nil
}

func addParamsMap(m map[string]interface{}, key string) (map[string]interface{}, error) {
	if v, exists := m[key]; exists {
		if existMap, ok := v.(map[string]interface{}); ok {
			return existMap, nil
		} else {
			return nil, fmt.Errorf("expected map type under %q, got %T", key, v)
		}
	}
	newMap := make(map[string]interface{})
	m[key] = newMap
	return newMap, nil
}

func addParamsSlice(m map[string]interface{}, prevkey, newkey string) (map[string]interface{}, error) {
	if v, exists := m[prevkey]; exists {
		if existSlice, ok := v.([]interface{}); ok {
			if len(existSlice) > 0 {
				lastItem := existSlice[len(existSlice)-1]
				if lastMap, ok := lastItem.(map[string]interface{}); ok {
					if _, keyExists := lastMap[newkey]; !keyExists {
						return lastMap, nil
					} else if reflect.TypeOf(lastMap[newkey]).Kind() == reflect.Slice {
						return lastMap, nil
					}
				}
			}
			newMap := make(map[string]interface{})
			m[prevkey] = append(existSlice, newMap)
			return newMap, nil
		} else {
			return nil, fmt.Errorf("expected array type under %q, got %T", prevkey, v)
		}
	}
	newMap := make(map[string]interface{})
	m[prevkey] = []interface{}{newMap}
	return newMap, nil
}

func magicFieldValue(v string, opts *ApiOptions) (interface{}, error) {
	if strings.HasPrefix(v, "@") {
		b, err := opts.IO.ReadUserFile(v[1:])
		if err != nil {
			return "", err
		}
		return string(b), nil
	}

	if n, err := strconv.Atoi(v); err == nil {
		return n, nil
	}

	switch v {
	case "true":
		return true, nil
	case "false":
		return false, nil
	case "null":
		return nil, nil
	default:
		return fillPlaceholders(v, opts)
	}
}
