package executor

import (
	"fmt"
	"strings"
	"time"
)

type timeFunctionKind uint8

const (
	timeFunctionUnknown timeFunctionKind = iota
	timeFunctionToTimestamp
	timeFunctionToDate
	timeFunctionToUnixTimestamp
)

type timeFunctionSpec struct {
	kind            timeFunctionKind
	requireTimeuuid bool
}

func normalizeFunctionName(name string) string {
	normalized := strings.ToLower(name)
	return strings.ReplaceAll(normalized, "_", "")
}

func timeFunctionSpecForName(name string) (timeFunctionSpec, bool) {
	switch normalizeFunctionName(name) {
	case "totimestamp":
		return timeFunctionSpec{kind: timeFunctionToTimestamp}, true
	case "dateof":
		return timeFunctionSpec{kind: timeFunctionToTimestamp, requireTimeuuid: true}, true
	case "todate":
		return timeFunctionSpec{kind: timeFunctionToDate}, true
	case "tounixtimestamp":
		return timeFunctionSpec{kind: timeFunctionToUnixTimestamp}, true
	case "unixtimestampof":
		return timeFunctionSpec{kind: timeFunctionToUnixTimestamp, requireTimeuuid: true}, true
	default:
		return timeFunctionSpec{}, false
	}
}

func timeFunctionResultType(spec timeFunctionSpec) string {
	switch spec.kind {
	case timeFunctionToTimestamp:
		return "timestamp"
	case timeFunctionToDate:
		return "date"
	case timeFunctionToUnixTimestamp:
		return "bigint"
	default:
		return "text"
	}
}

func normalizeTimeFunctionSourceType(fromType string) string {
	normalized := normalizeCastType(fromType)
	if normalized == "uuid" {
		return "timeuuid"
	}
	return normalized
}

func isTimeFunctionSourceType(spec timeFunctionSpec, fromType string) bool {
	normalized := normalizeTimeFunctionSourceType(fromType)
	if normalized == "" || normalized == "text" {
		return true
	}
	if spec.requireTimeuuid {
		return normalized == "timeuuid"
	}
	switch spec.kind {
	case timeFunctionToTimestamp:
		return normalized == "timeuuid" || normalized == "date" || normalized == "timestamp"
	case timeFunctionToDate:
		return normalized == "timeuuid" || normalized == "timestamp"
	case timeFunctionToUnixTimestamp:
		return normalized == "timeuuid" || normalized == "timestamp" || normalized == "date"
	default:
		return false
	}
}

func evalTimeFunction(spec timeFunctionSpec, value interface{}, fromType string) (interface{}, error) {
	if value == nil {
		return nil, nil
	}
	normalized := normalizeTimeFunctionSourceType(fromType)
	if spec.requireTimeuuid && normalized != "" && normalized != "timeuuid" {
		return nil, fmt.Errorf("timeuuid value is required")
	}

	switch spec.kind {
	case timeFunctionToTimestamp:
		return evalToTimestamp(value, normalized)
	case timeFunctionToDate:
		return evalToDate(value, normalized)
	case timeFunctionToUnixTimestamp:
		return evalToUnixTimestamp(value, normalized)
	default:
		return nil, fmt.Errorf("unsupported time conversion")
	}
}

func evalToTimestamp(value interface{}, fromType string) (time.Time, error) {
	switch normalizeTimeFunctionSourceType(fromType) {
	case "timeuuid":
		return timeFromTimeuuid(value)
	case "date":
		if ts, ok := timeFromDateValue(value); ok {
			return ts, nil
		}
		return time.Time{}, fmt.Errorf("invalid date value")
	case "timestamp":
		if ts, ok := timeFromTimestampValue(value); ok {
			return ts, nil
		}
		return time.Time{}, fmt.Errorf("invalid timestamp value")
	}

	if ts, ok := timeFromTimeuuidValue(value); ok {
		return ts, nil
	}
	if ts, ok := timeFromDateValue(value); ok {
		return ts, nil
	}
	if ts, ok := timeFromTimestampValue(value); ok {
		return ts, nil
	}
	return time.Time{}, fmt.Errorf("toTimestamp expects timeuuid, date, or timestamp")
}

func evalToDate(value interface{}, fromType string) (time.Time, error) {
	switch normalizeTimeFunctionSourceType(fromType) {
	case "timeuuid":
		ts, err := timeFromTimeuuid(value)
		if err != nil {
			return time.Time{}, err
		}
		return dateFromTime(ts), nil
	case "timestamp":
		if ts, ok := timeFromTimestampValue(value); ok {
			return dateFromTime(ts), nil
		}
		return time.Time{}, fmt.Errorf("invalid timestamp value")
	}

	if ts, ok := timeFromTimeuuidValue(value); ok {
		return dateFromTime(ts), nil
	}
	if ts, ok := timeFromTimestampValue(value); ok {
		return dateFromTime(ts), nil
	}
	return time.Time{}, fmt.Errorf("toDate expects timeuuid or timestamp")
}

func evalToUnixTimestamp(value interface{}, fromType string) (int64, error) {
	switch normalizeTimeFunctionSourceType(fromType) {
	case "timeuuid":
		ts, err := timeFromTimeuuid(value)
		if err != nil {
			return 0, err
		}
		return unixMillis(ts), nil
	case "timestamp":
		if ts, ok := timeFromTimestampValue(value); ok {
			return unixMillis(ts), nil
		}
		return 0, fmt.Errorf("invalid timestamp value")
	case "date":
		if ts, ok := timeFromDateValue(value); ok {
			return unixMillis(ts), nil
		}
		return 0, fmt.Errorf("invalid date value")
	}

	if ts, ok := timeFromTimeuuidValue(value); ok {
		return unixMillis(ts), nil
	}
	if ts, ok := timeFromTimestampValue(value); ok {
		return unixMillis(ts), nil
	}
	if ts, ok := timeFromDateValue(value); ok {
		return unixMillis(ts), nil
	}
	return 0, fmt.Errorf("toUnixTimestamp expects timeuuid, timestamp, or date")
}

func timeFromTimeuuid(value interface{}) (time.Time, error) {
	if b, ok := value.([]byte); ok {
		return timeuuidToTime(string(b))
	}
	return timeuuidToTime(value)
}

func timeFromTimeuuidValue(value interface{}) (time.Time, bool) {
	ts, err := timeFromTimeuuid(value)
	if err != nil {
		return time.Time{}, false
	}
	return ts, true
}

func timeFromDateValue(value interface{}) (time.Time, bool) {
	switch v := value.(type) {
	case time.Time:
		return dateFromTime(v), true
	case string:
		if parsed, ok := parseDateString(v); ok {
			return parsed, true
		}
	case []byte:
		if parsed, ok := parseDateString(string(v)); ok {
			return parsed, true
		}
	}
	return time.Time{}, false
}

func timeFromTimestampValue(value interface{}) (time.Time, bool) {
	switch v := value.(type) {
	case []byte:
		return parseTimestampValue(string(v))
	default:
		return parseTimestampValue(v)
	}
}

func unixMillis(ts time.Time) int64 {
	return ts.UTC().UnixNano() / int64(time.Millisecond)
}
