package dto

import (
	"errors"
	"github.com/stonith404/pocket-id/backend/internal/model/types"
	"reflect"
	"time"
)

// 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 {
	// Loop through the fields of the destination struct
	for i := 0; i < destVal.NumField(); i++ {
		destField := destVal.Field(i)
		destFieldType := destVal.Type().Field(i)

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

		sourceField := sourceVal.FieldByName(destFieldType.Name)

		// If the source field is valid and can be assigned to the destination field
		if sourceField.IsValid() && destField.CanSet() {
			// Handle direct assignment for simple types
			if sourceField.Type() == destField.Type() {
				destField.Set(sourceField)

			} else if sourceField.Kind() == reflect.Slice && destField.Kind() == reflect.Slice {
				// Handle slices
				if sourceField.Type().Elem() == destField.Type().Elem() {
					// Direct assignment for slices of primitive types or non-struct elements
					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 {
					// Recursively map slices of structs
					newSlice := reflect.MakeSlice(destField.Type(), sourceField.Len(), sourceField.Cap())

					for j := 0; j < sourceField.Len(); j++ {
						// Get the element from both source and destination slice
						sourceElem := sourceField.Index(j)
						destElem := reflect.New(destField.Type().Elem()).Elem()

						// Recursively map the struct elements
						if err := mapStructInternal(sourceElem, destElem); err != nil {
							return err
						}

						// Set the mapped element in the new slice
						newSlice.Index(j).Set(destElem)
					}

					destField.Set(newSlice)
				}
			} else if sourceField.Kind() == reflect.Struct && destField.Kind() == reflect.Struct {
				// Recursively map nested structs
				if err := mapStructInternal(sourceField, destField); err != nil {
					return err
				}
			} else {
				// Type switch for specific type conversions
				switch sourceField.Interface().(type) {
				case datatype.DateTime:
					// Convert datatype.DateTime to time.Time
					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
}
