package dbmarshal

import (
	"encoding/json"
	"reflect"
	"strings"
)

// Marshal serializes the given struct using dbstore tags instead of json tags.
// Fields with dbstore tags will be included in the output, while fields with
// json:"-" but dbstore tags will be renamed according to their dbstore tag.
func Marshal(v interface{}) ([]byte, error) {
	if v == nil {
		return []byte("null"), nil
	}

	// Convert the struct to a map using dbstore tags
	mapped, err := structToMapWithDBStoreTags(v)
	if err != nil {
		return nil, err
	}

	// Use standard JSON marshaling on the mapped result
	return json.Marshal(mapped)
}

// Unmarshal deserializes JSON data into the given struct using dbstore tags.
// This will map JSON field names back to struct fields based on dbstore tags.
func Unmarshal(data []byte, v interface{}) error {
	if len(data) == 0 || string(data) == "null" {
		return nil
	}

	// First unmarshal into a map
	var mapped map[string]interface{}
	if err := json.Unmarshal(data, &mapped); err != nil {
		return err
	}

	// Convert the map back to struct using dbstore tags
	return mapToStructWithDBStoreTags(mapped, v)
}

// structToMapWithDBStoreTags converts a struct to a map using dbstore tags
func structToMapWithDBStoreTags(v interface{}) (map[string]interface{}, error) {
	result := make(map[string]interface{})

	rv := reflect.ValueOf(v)
	rt := reflect.TypeOf(v)

	// Handle pointers
	if rv.Kind() == reflect.Ptr {
		if rv.IsNil() {
			return nil, nil
		}
		rv = rv.Elem()
		rt = rt.Elem()
	}

	if rv.Kind() != reflect.Struct {
		// For non-struct types, just return the value as-is
		return map[string]interface{}{"value": v}, nil
	}

	for i := 0; i < rv.NumField(); i++ {
		field := rt.Field(i)
		fieldValue := rv.Field(i)

		// Skip unexported fields
		if !fieldValue.CanInterface() {
			continue
		}

		// Get the field name from dbstore tag, fallback to json tag, then field name
		fieldName := getFieldName(field)
		if fieldName == "" || fieldName == "-" {
			continue
		}

		// Handle nested structs and pointers
		if fieldValue.Kind() == reflect.Ptr && !fieldValue.IsNil() {
			if fieldValue.Elem().Kind() == reflect.Struct {
				// Check if it implements json.Marshaler (like time.Time)
				if marshaler, ok := fieldValue.Interface().(json.Marshaler); ok {
					jsonData, err := marshaler.MarshalJSON()
					if err != nil {
						return nil, err
					}
					var value interface{}
					if err := json.Unmarshal(jsonData, &value); err != nil {
						result[fieldName] = string(jsonData) // fallback to string
					} else {
						result[fieldName] = value
					}
				} else {
					nested, err := structToMapWithDBStoreTags(fieldValue.Interface())
					if err != nil {
						return nil, err
					}
					result[fieldName] = nested
				}
			} else {
				result[fieldName] = fieldValue.Interface()
			}
		} else if fieldValue.Kind() == reflect.Struct {
			// Check if it implements json.Marshaler (like time.Time)
			if marshaler, ok := fieldValue.Interface().(json.Marshaler); ok {
				jsonData, err := marshaler.MarshalJSON()
				if err != nil {
					return nil, err
				}
				var value interface{}
				if err := json.Unmarshal(jsonData, &value); err != nil {
					result[fieldName] = string(jsonData) // fallback to string
				} else {
					result[fieldName] = value
				}
			} else {
				nested, err := structToMapWithDBStoreTags(fieldValue.Interface())
				if err != nil {
					return nil, err
				}
				result[fieldName] = nested
			}
		} else {
			result[fieldName] = fieldValue.Interface()
		}
	}

	return result, nil
}

// mapToStructWithDBStoreTags converts a map to struct using dbstore tags
func mapToStructWithDBStoreTags(mapped map[string]interface{}, v interface{}) error {
	rv := reflect.ValueOf(v)
	if rv.Kind() != reflect.Ptr || rv.IsNil() {
		return json.Unmarshal([]byte("{}"), v) // fallback to regular json
	}

	rv = rv.Elem()
	rt := rv.Type()

	if rv.Kind() != reflect.Struct {
		// For non-struct types, try regular JSON unmarshaling
		data, _ := json.Marshal(mapped)
		return json.Unmarshal(data, v)
	}

	// Create a mapping from dbstore field names to struct field indices
	fieldMap := make(map[string]int)
	for i := 0; i < rt.NumField(); i++ {
		field := rt.Field(i)
		fieldName := getFieldName(field)
		if fieldName != "" && fieldName != "-" {
			fieldMap[fieldName] = i
		}
	}

	// Set field values from the map
	for mapKey, mapValue := range mapped {
		if fieldIndex, exists := fieldMap[mapKey]; exists {
			fieldValue := rv.Field(fieldIndex)
			if !fieldValue.CanSet() {
				continue
			}

			if err := setFieldValue(fieldValue, mapValue); err != nil {
				return err
			}
		}
	}

	return nil
}

// getFieldName extracts the field name from dbstore tag, falling back to json tag or field name
func getFieldName(field reflect.StructField) string {
	// First try dbstore tag
	if dbstoreTag := field.Tag.Get("dbstore"); dbstoreTag != "" {
		tagValue := strings.Split(dbstoreTag, ",")[0]
		if tagValue == "-" {
			// If dbstore:"-", ignore this field
			return ""
		}
		return tagValue
	}

	// Then try json tag
	if jsonTag := field.Tag.Get("json"); jsonTag != "" {
		if jsonTag == "-" {
			// If json:"-" and no dbstore tag, ignore this field
			return ""
		}
		return strings.Split(jsonTag, ",")[0]
	}

	// If no tags, use exact field name (only for exported fields)
	if field.PkgPath == "" { // exported field
		return field.Name
	}

	// Unexported field without tags should be ignored
	return ""
}

// setFieldValue sets the field value from the map value, handling type conversions
func setFieldValue(fieldValue reflect.Value, mapValue interface{}) error {
	if mapValue == nil {
		return nil
	}

	mapValueReflect := reflect.ValueOf(mapValue)
	fieldType := fieldValue.Type()

	// Handle pointer fields
	if fieldType.Kind() == reflect.Ptr {
		if mapValue == nil {
			fieldValue.Set(reflect.Zero(fieldType))
			return nil
		}

		// Create a new instance of the pointed-to type
		elemType := fieldType.Elem()
		newElem := reflect.New(elemType)

		if elemType.Kind() == reflect.Struct {
			// Special handling for time.Time and other json.Unmarshaler types
			if newElem.Interface() != nil {
				if unmarshaler, ok := newElem.Interface().(json.Unmarshaler); ok {
					jsonData, err := json.Marshal(mapValue)
					if err != nil {
						return err
					}
					if err := unmarshaler.UnmarshalJSON(jsonData); err != nil {
						return err
					}
					fieldValue.Set(newElem)
					return nil
				}
			}

			// Handle nested structs
			if mapAsMap, ok := mapValue.(map[string]interface{}); ok {
				if err := mapToStructWithDBStoreTags(mapAsMap, newElem.Interface()); err != nil {
					return err
				}
			}
		} else {
			// Handle primitive types
			if mapValueReflect.Type().ConvertibleTo(elemType) {
				newElem.Elem().Set(mapValueReflect.Convert(elemType))
			}
		}

		fieldValue.Set(newElem)
		return nil
	}

	// Handle struct fields
	if fieldType.Kind() == reflect.Struct {
		// Special handling for time.Time and other json.Unmarshaler types
		if fieldValue.CanAddr() {
			if unmarshaler, ok := fieldValue.Addr().Interface().(json.Unmarshaler); ok {
				jsonData, err := json.Marshal(mapValue)
				if err != nil {
					return err
				}
				return unmarshaler.UnmarshalJSON(jsonData)
			}
		}

		if mapAsMap, ok := mapValue.(map[string]interface{}); ok {
			return mapToStructWithDBStoreTags(mapAsMap, fieldValue.Addr().Interface())
		}
	}

	// Handle direct assignment with type conversion
	if mapValueReflect.Type().ConvertibleTo(fieldType) {
		fieldValue.Set(mapValueReflect.Convert(fieldType))
	} else {
		// Try JSON marshaling/unmarshaling as fallback
		jsonData, err := json.Marshal(mapValue)
		if err != nil {
			return err
		}
		return json.Unmarshal(jsonData, fieldValue.Addr().Interface())
	}

	return nil
}
