package rest

import (
	"fmt"
	"reflect"
	"strings"

	"git.ali33.ru/fcg-xvii/go-tools/json"
)

// IRestConverter реализует пользовательский пасер из значения, полученного из запроса (см. пример из time.go)
type IRestConverter interface {
	// RestFrom реализeт конвертацию из данных запроса в объект
	RestFrom(any) IErrorArgs
}

func parseVal(from, to reflect.Value, fieldName string) IErrorArgs {
	if from.Kind() == reflect.Interface {
		from = from.Elem()
	}
	if !from.CanConvert(to.Type()) {
		return NewError(
			"FieldType",
			json.Map{
				"field":    fieldName,
				"expected": to.Type().String(),
				"received": from.Type().String(),
			},
		)
	}
	to.Set(from.Convert(to.Type()))
	return nil
}

func parseType(from, to reflect.Value, fieldName string) IErrorArgs {
	from = realValue(from)
	if !from.IsValid() {
		return nil
	}
	to = realValue(to)
	tn := StructTypeName(to)
	cv, check := fieldConverters[tn]
	if check {
		val, err := cv.Unpack(from.Interface())
		if err != nil {
			return NewError(
				"FieldValue",
				json.Map{
					"field": fieldName,
					"error": err.Error(),
				},
			)
		}
		to.Set(reflect.ValueOf(val))
		return nil
	}
	switch to.Kind() {
	case reflect.Interface:
		if from.Kind() != reflect.Invalid {
			to.Set(from)
		}
		return nil
	case reflect.Struct:
		return parseStruct(from, to, fieldName)
	case reflect.Slice:
		return parseSlice(from, to, fieldName)
	case reflect.Map:
		return parseMap(from, to, fieldName)
	default:
		return parseVal(from, to, fieldName)
	}
}

func realValue(val reflect.Value) reflect.Value {
	if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface {
		//if val.Kind() == reflect.Interface {
		if val.IsNil() && val.Kind() == reflect.Ptr {
			val.Set(reflect.New(val.Type().Elem()))
		}
		// check
		cval := val.Elem()
		if cval.Kind() == reflect.Invalid {
			return val
		}
		val = cval
		if val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface {
			return realValue(cval)
		}
	}
	return val
}

func parseSlice(from, to reflect.Value, fieldName string) IErrorArgs {
	if to.IsNil() {
		to.Set(reflect.MakeSlice(to.Type(), 0, from.Cap()))
	}
	eType := to.Type().Elem()
	// Перебираем элементы среза, откуда копируем.
	for i := 0; i < from.Len(); i++ {
		iFieldName := fmt.Sprintf("%v[%v]", fieldName, i)
		fromVal := from.Index(i)
		toVal := reflect.New(eType).Elem()
		if err := parseType(fromVal, toVal.Addr(), iFieldName); err != nil {
			return err
		}
		to.Set(reflect.Append(to, toVal))
	}
	return nil
}

func parseStruct(from, to reflect.Value, fieldName string) IErrorArgs {
	if f, check := to.Interface().(IRestConverter); check {
		return f.RestFrom(from.Interface())
	}
	if to.CanAddr() {
		if f, check := to.Addr().Interface().(IRestConverter); check {
			return f.RestFrom(from.Interface())
		}
	}
loop:
	for i := 0; i < to.NumField(); i++ {
		required := false
		// устанавливаем тип и указатель поля объекта, куда копируем
		fType := to.Type().Field(i)
		fVal := to.Field(i)
		if !fVal.CanSet() {
			continue
		}
		// проверяем тег, если он есть
		tag := fType.Tag.Get("rest")
		if fType.Anonymous && fType.Type.Kind() == reflect.Ptr {
			elem := fType.Type.Elem()
			if elem.Kind() == reflect.Struct {
				if fVal.IsNil() {
					fVal.Set(reflect.New(elem))
				}
				if ierr := parseStruct(from, fVal.Elem(), prefixFieldName(fieldName, fType.Name)); ierr != nil {
					return ierr
				}
				continue loop
			}
		}
		if len(tag) > 0 {
			required = strings.Contains(tag, "required")
		}
		var rVal reflect.Value
		switch from.Kind() {
		case reflect.Struct:
			if rVal = from.FieldByName(fType.Name); !rVal.IsValid() {
				rVal = from.FieldByName(fType.Name)
			}
		case reflect.Map:
			mapKeys := from.MapKeys()
			lowerFTypeName := strings.ToLower(fType.Name)
			for _, key := range mapKeys {
				lowerKey := strings.ToLower(key.String())
				if lowerKey == lowerFTypeName {
					rVal = from.MapIndex(key)
					break
				}
			}
			if !rVal.IsValid() {
				// Если ни один из ключей не совпал, попытаться найти по оригинальному ключу
				rVal = from.MapIndex(reflect.ValueOf(fType.Name))
			}
		default:
			return NewError(
				"FieldType",
				json.Map{
					"field":    fieldName,
					"expected": to.Type().String(),
					"received": from.Type().String(),
				},
			)
		}
		if !rVal.IsValid() {
			if required {
				return ErrorMessage("RequiredField", fType.Name)
			}
			continue
		}
		if rVal.Kind() == reflect.Interface {
			rVal = rVal.Elem()
		}
		var iFieldName string
		if fieldName == "" {
			iFieldName = fType.Name
		} else {
			iFieldName = fmt.Sprintf("%v.%v", fieldName, fType.Name)
		}
		if err := parseType(rVal, fVal.Addr(), iFieldName); err != nil {
			return err
		}
	}
	return nil
}

func parseMap(from, to reflect.Value, fieldName string) IErrorArgs {
	if to.IsNil() {
		to.Set(reflect.MakeMap(to.Type()))
	}
	switch from.Kind() {
	case reflect.Map:
		for _, key := range from.MapKeys() {
			iFieldName := fmt.Sprintf("%v[%v]", fieldName, key)
			fromVal := from.MapIndex(key)
			toVal := reflect.New(to.Type().Elem()).Elem()
			if err := parseType(fromVal, toVal.Addr(), iFieldName); err != nil {
				return err
			}
			to.SetMapIndex(key, toVal)
		}
	case reflect.Struct:
		for i := 0; i < from.NumField(); i++ {
			iFieldName := fmt.Sprintf("%v.%v", fieldName, from.Type().Field(i).Name)
			fromVal := from.Field(i)
			toVal := reflect.New(to.Type().Elem()).Elem()
			if err := parseType(fromVal, toVal.Addr(), iFieldName); err != nil {
				return err
			}
			fName := from.Type().Field(i).Name
			to.SetMapIndex(reflect.ValueOf(fName), toVal)
		}
	default:
		return NewError(
			"FieldType",
			json.Map{
				"field":    fieldName,
				"expected": to.Type().String(),
				"received": from.Type().String(),
			},
		)
	}
	return nil
}

// Serialize преобразует словарь from в объект произвольного типа
func Serialize(from json.Map, to any) IErrorArgs {
	elemTo := reflect.ValueOf(to).Elem()
	res := parseType(
		reflect.ValueOf(&from),
		elemTo,
		"",
	)
	return res
}