package compare

import (
	"bytes"
	"encoding/json"
	"reflect"
	"time"

	"github.com/duke-git/lancet/v2/convertor"
)

func compareValue(operator string, left, right any) bool {
	leftType, rightType := reflect.TypeOf(left), reflect.TypeOf(right)

	if leftType.Kind() != rightType.Kind() {
		return false
	}

	switch leftType.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64, reflect.Bool, reflect.String:
		return compareBasicValue(operator, left, right)

	case reflect.Struct, reflect.Slice, reflect.Map:
		return compareRefValue(operator, left, right, leftType.Kind())
	}

	return false
}

func compareRefValue(operator string, leftObj, rightObj any, kind reflect.Kind) bool {
	leftVal, rightVal := reflect.ValueOf(leftObj), reflect.ValueOf(rightObj)

	switch kind {
	case reflect.Struct:

		// compare time
		if leftVal.CanConvert(timeType) {
			timeObj1, ok := leftObj.(time.Time)
			if !ok {
				timeObj1 = leftVal.Convert(timeType).Interface().(time.Time)
			}

			timeObj2, ok := rightObj.(time.Time)
			if !ok {
				timeObj2 = rightVal.Convert(timeType).Interface().(time.Time)
			}

			return compareBasicValue(operator, timeObj1.UnixNano(), timeObj2.UnixNano())
		}

		// for other struct type, only process equal operator
		switch operator {
		case equal:
			return objectsAreEqualValues(leftObj, rightObj)
		}

	case reflect.Slice:
		// compare []byte
		if leftVal.CanConvert(bytesType) {
			bytesObj1, ok := leftObj.([]byte)
			if !ok {
				bytesObj1 = leftVal.Convert(bytesType).Interface().([]byte)
			}
			bytesObj2, ok := rightObj.([]byte)
			if !ok {
				bytesObj2 = rightVal.Convert(bytesType).Interface().([]byte)
			}

			switch operator {
			case equal:
				if bytes.Compare(bytesObj1, bytesObj2) == 0 {
					return true
				}
			case lessThan:
				if bytes.Compare(bytesObj1, bytesObj2) == -1 {
					return true
				}
			case greaterThan:
				if bytes.Compare(bytesObj1, bytesObj2) == 1 {
					return true
				}
			case lessOrEqual:
				if bytes.Compare(bytesObj1, bytesObj2) <= 0 {
					return true
				}
			case greaterOrEqual:
				if bytes.Compare(bytesObj1, bytesObj2) >= 0 {
					return true
				}
			}

		}

		// for other type slice, only process equal operator
		switch operator {
		case equal:
			return reflect.DeepEqual(leftObj, rightObj)
		}

	case reflect.Map:
		// only process equal operator
		switch operator {
		case equal:
			return reflect.DeepEqual(leftObj, rightObj)
		}
	}

	return false
}

func objectsAreEqualValues(expected, actual interface{}) bool {
	if objectsAreEqual(expected, actual) {
		return true
	}

	actualType := reflect.TypeOf(actual)
	if actualType == nil {
		return false
	}
	expectedValue := reflect.ValueOf(expected)
	if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) {
		// Attempt comparison after type conversion
		return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual)
	}

	return false
}

func objectsAreEqual(expected, actual interface{}) bool {
	if expected == nil || actual == nil {
		return expected == actual
	}

	exp, ok := expected.([]byte)
	if !ok {
		return reflect.DeepEqual(expected, actual)
	}

	act, ok := actual.([]byte)
	if !ok {
		return false
	}
	if exp == nil || act == nil {
		return exp == nil && act == nil
	}
	return bytes.Equal(exp, act)
}

// compareBasic compare basic value: integer, float, string, bool
func compareBasicValue(operator string, leftValue, rightValue any) bool {
	if leftValue == nil && rightValue == nil && operator == equal {
		return true
	}

	switch leftVal := leftValue.(type) {
	case json.Number:
		if left, err := leftVal.Float64(); err == nil {
			switch rightVal := rightValue.(type) {
			case json.Number:
				if right, err := rightVal.Float64(); err == nil {
					switch operator {
					case equal:
						if left == right {
							return true
						}
					case lessThan:
						if left < right {
							return true
						}
					case greaterThan:
						if left > right {
							return true
						}
					case lessOrEqual:
						if left <= right {
							return true
						}
					case greaterOrEqual:
						if left >= right {
							return true
						}
					}

				}

			case float32, float64, int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
				right, err := convertor.ToFloat(rightValue)
				if err != nil {
					return false
				}
				switch operator {
				case equal:
					if left == right {
						return true
					}
				case lessThan:
					if left < right {
						return true
					}
				case greaterThan:
					if left > right {
						return true
					}
				case lessOrEqual:
					if left <= right {
						return true
					}
				case greaterOrEqual:
					if left >= right {
						return true
					}
				}
			}

		}

	case float32, float64, int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
		left, err := convertor.ToFloat(leftValue)
		if err != nil {
			return false
		}
		switch rightVal := rightValue.(type) {
		case json.Number:
			if right, err := rightVal.Float64(); err == nil {
				switch operator {
				case equal:
					if left == right {
						return true
					}
				case lessThan:
					if left < right {
						return true
					}
				case greaterThan:
					if left > right {
						return true
					}
				case lessOrEqual:
					if left <= right {
						return true
					}
				case greaterOrEqual:
					if left >= right {
						return true
					}
				}
			}
		case float32, float64, int, uint, int8, uint8, int16, uint16, int32, uint32, int64, uint64:
			right, err := convertor.ToFloat(rightValue)
			if err != nil {
				return false
			}

			switch operator {
			case equal:
				if left == right {
					return true
				}
			case lessThan:
				if left < right {
					return true
				}
			case greaterThan:
				if left > right {
					return true
				}
			case lessOrEqual:
				if left <= right {
					return true
				}
			case greaterOrEqual:
				if left >= right {
					return true
				}
			}
		}

	case string:
		left := leftVal
		switch right := rightValue.(type) {
		case string:
			switch operator {
			case equal:
				if left == right {
					return true
				}
			case lessThan:
				if left < right {
					return true
				}
			case greaterThan:
				if left > right {
					return true
				}
			case lessOrEqual:
				if left <= right {
					return true
				}
			case greaterOrEqual:
				if left >= right {
					return true
				}
			}
		}

	case bool:
		left := leftVal
		switch right := rightValue.(type) {
		case bool:
			switch operator {
			case equal:
				if left == right {
					return true
				}
			}
		}

	}

	return false
}
