package dto

import (
	"errors"
	"reflect"
	"time"

	datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
)

// MapStructList maps a list of source structs to a list of destination structs
func MapStructList[S any, D any](source []S, destination *[]D) error {
	*destination = make([]D, 0, len(source))

	for _, item := range source {
		var destItem D
		if err := MapStruct(item, &destItem); err != nil {
			return err
		}
		*destination = append(*destination, destItem)
	}
	return nil
}

// MapStruct maps a source struct to a destination struct
func MapStruct[S any, D any](source S, destination *D) error {
	// Ensure destination is a non-nil pointer
	destValue := reflect.ValueOf(destination)
	if destValue.Kind() != reflect.Ptr || destValue.IsNil() {
		return errors.New("destination must be a non-nil pointer to a struct")
	}

	// Ensure source is a struct
	sourceValue := reflect.ValueOf(source)
	if sourceValue.Kind() != reflect.Struct {
		return errors.New("source must be a struct")
	}

	return mapStructInternal(sourceValue, destValue.Elem())
}

func mapStructInternal(sourceVal reflect.Value, destVal reflect.Value) error {
	for i := 0; i < destVal.NumField(); i++ {
		destField := destVal.Field(i)
		destFieldType := destVal.Type().Field(i)

		if destFieldType.Anonymous {
			if err := mapStructInternal(sourceVal, destField); err != nil {
				return err
			}
			continue
		}

		sourceField := sourceVal.FieldByName(destFieldType.Name)

		if sourceField.IsValid() && destField.CanSet() {
			if err := mapField(sourceField, destField); err != nil {
				return err
			}
		}
	}
	return nil
}

//nolint:gocognit
func mapField(sourceField reflect.Value, destField reflect.Value) error {
	// Handle pointer to struct in source
	if sourceField.Kind() == reflect.Ptr && !sourceField.IsNil() {
		switch {
		case sourceField.Elem().Kind() == reflect.Struct:
			switch {
			case destField.Kind() == reflect.Struct:
				// Map from pointer to struct -> struct
				return mapStructInternal(sourceField.Elem(), destField)
			case destField.Kind() == reflect.Ptr && destField.CanSet():
				// Map from pointer to struct -> pointer to struct
				if destField.IsNil() {
					destField.Set(reflect.New(destField.Type().Elem()))
				}
				return mapStructInternal(sourceField.Elem(), destField.Elem())
			}
		case destField.Kind() == reflect.Ptr &&
			destField.CanSet() &&
			sourceField.Elem().Type().AssignableTo(destField.Type().Elem()):
			// Handle primitive pointer types (e.g., *string to *string)
			if destField.IsNil() {
				destField.Set(reflect.New(destField.Type().Elem()))
			}
			destField.Elem().Set(sourceField.Elem())
			return nil
		case destField.Kind() != reflect.Ptr &&
			destField.CanSet() &&
			sourceField.Elem().Type().AssignableTo(destField.Type()):
			// Handle *T to T conversion for primitive types
			destField.Set(sourceField.Elem())
			return nil
		}
	}

	// Handle pointer to struct in destination
	if destField.Kind() == reflect.Ptr && destField.CanSet() {
		switch {
		case sourceField.Kind() == reflect.Struct:
			// Map from struct -> pointer to struct
			if destField.IsNil() {
				destField.Set(reflect.New(destField.Type().Elem()))
			}
			return mapStructInternal(sourceField, destField.Elem())
		case !sourceField.IsZero() && sourceField.Type().AssignableTo(destField.Type().Elem()):
			// Handle T to *T conversion for primitive types
			if destField.IsNil() {
				destField.Set(reflect.New(destField.Type().Elem()))
			}
			destField.Elem().Set(sourceField)
			return nil
		}
	}

	switch {
	case sourceField.Type() == destField.Type():
		destField.Set(sourceField)
	case sourceField.Kind() == reflect.Slice && destField.Kind() == reflect.Slice:
		return mapSlice(sourceField, destField)
	case sourceField.Kind() == reflect.Struct && destField.Kind() == reflect.Struct:
		return mapStructInternal(sourceField, destField)
	default:
		return mapSpecialTypes(sourceField, destField)
	}
	return nil
}

func mapSlice(sourceField reflect.Value, destField reflect.Value) error {
	if sourceField.Type().Elem() == destField.Type().Elem() {
		newSlice := reflect.MakeSlice(destField.Type(), sourceField.Len(), sourceField.Cap())
		for j := 0; j < sourceField.Len(); j++ {
			newSlice.Index(j).Set(sourceField.Index(j))
		}
		destField.Set(newSlice)
	} else if sourceField.Type().Elem().Kind() == reflect.Struct && destField.Type().Elem().Kind() == reflect.Struct {
		newSlice := reflect.MakeSlice(destField.Type(), sourceField.Len(), sourceField.Cap())
		for j := 0; j < sourceField.Len(); j++ {
			sourceElem := sourceField.Index(j)
			destElem := reflect.New(destField.Type().Elem()).Elem()
			if err := mapStructInternal(sourceElem, destElem); err != nil {
				return err
			}
			newSlice.Index(j).Set(destElem)
		}
		destField.Set(newSlice)
	}
	return nil
}

func mapSpecialTypes(sourceField reflect.Value, destField reflect.Value) error {
	if _, ok := sourceField.Interface().(datatype.DateTime); ok {
		if sourceField.Type() == reflect.TypeOf(datatype.DateTime{}) && destField.Type() == reflect.TypeOf(time.Time{}) {
			dateValue := sourceField.Interface().(datatype.DateTime)
			destField.Set(reflect.ValueOf(dateValue.ToTime()))
		}
	}
	return nil
}
