package executor

import (
	"fmt"

	"github.com/everything-on-mysql/cassandra-on-mysql/pkg/cql/parser"
)

func rewriteTimeFunctionsInExpr(expr parser.Expression, params []interface{}, markerIndex map[parser.Expression]int, lookup func(string) (string, bool), allowUnresolved bool) (parser.Expression, bool, bool, error) {
	if expr == nil {
		return nil, false, false, nil
	}

	switch e := expr.(type) {
	case *parser.TypeHintExpr:
		inner, changed, requiresProxy, err := rewriteTimeFunctionsInExpr(e.Expr, params, markerIndex, lookup, allowUnresolved)
		if err != nil {
			return nil, false, false, err
		}
		if !changed {
			return e, false, requiresProxy, nil
		}
		return &parser.TypeHintExpr{Token: e.Token, Expr: inner, Type: e.Type}, true, requiresProxy, nil
	case *parser.AliasedExpr:
		inner, changed, requiresProxy, err := rewriteTimeFunctionsInExpr(e.Expr, params, markerIndex, lookup, allowUnresolved)
		if err != nil {
			return nil, false, false, err
		}
		if !changed {
			return e, false, requiresProxy, nil
		}
		return &parser.AliasedExpr{Token: e.Token, Expr: inner, Alias: e.Alias}, true, requiresProxy, nil
	case *parser.IsNullExpr:
		inner, changed, requiresProxy, err := rewriteTimeFunctionsInExpr(e.Expr, params, markerIndex, lookup, allowUnresolved)
		if err != nil {
			return nil, false, false, err
		}
		if !changed {
			return e, false, requiresProxy, nil
		}
		return &parser.IsNullExpr{Token: e.Token, Expr: inner, Not: e.Not}, true, requiresProxy, nil
	case *parser.BinaryExpr:
		left, leftChanged, leftProxy, err := rewriteTimeFunctionsInExpr(e.Left, params, markerIndex, lookup, allowUnresolved)
		if err != nil {
			return nil, false, false, err
		}
		right, rightChanged, rightProxy, err := rewriteTimeFunctionsInExpr(e.Right, params, markerIndex, lookup, allowUnresolved)
		if err != nil {
			return nil, false, false, err
		}
		if !leftChanged && !rightChanged {
			return e, false, leftProxy || rightProxy, nil
		}
		return &parser.BinaryExpr{Token: e.Token, Op: e.Op, Left: left, Right: right}, true, leftProxy || rightProxy, nil
	case *parser.UnaryExpr:
		inner, changed, requiresProxy, err := rewriteTimeFunctionsInExpr(e.Expr, params, markerIndex, lookup, allowUnresolved)
		if err != nil {
			return nil, false, false, err
		}
		if !changed {
			return e, false, requiresProxy, nil
		}
		return &parser.UnaryExpr{Token: e.Token, Op: e.Op, Expr: inner}, true, requiresProxy, nil
	case *parser.FunctionCall:
		spec, ok := timeFunctionSpecForName(e.Name)
		if !ok {
			args := make([]parser.Expression, len(e.Args))
			changed := false
			requiresProxy := false
			for i, arg := range e.Args {
				updated, ch, proxy, err := rewriteTimeFunctionsInExpr(arg, params, markerIndex, lookup, allowUnresolved)
				if err != nil {
					return nil, false, false, err
				}
				if ch {
					changed = true
				}
				if proxy {
					requiresProxy = true
				}
				args[i] = updated
			}
			if !changed {
				return e, false, requiresProxy, nil
			}
			return &parser.FunctionCall{Name: e.Name, Args: args}, true, requiresProxy, nil
		}
		if len(e.Args) != 1 {
			return nil, false, false, fmt.Errorf("%s expects a single argument", e.Name)
		}

		arg := e.Args[0]
		lookupType := ""
		if lookup != nil {
			if typ, ok := inferCastSourceType(arg, lookup); ok {
				lookupType = normalizeTimeFunctionSourceType(typ)
			}
		}
		if lookupType != "" && !isTimeFunctionSourceType(spec, lookupType) {
			return nil, false, false, fmt.Errorf("%s has unsupported argument type", e.Name)
		}

		if idx, ok := castBindMarkerIndex(arg, markerIndex); ok {
			if idx < 0 || idx >= len(params) {
				if allowUnresolved {
					return e, false, true, nil
				}
				return nil, false, false, fmt.Errorf("bind marker index out of range")
			}
			converted, err := evalTimeFunction(spec, params[idx], lookupType)
			if err != nil {
				return nil, false, false, err
			}
			params[idx] = converted
			return arg, true, false, nil
		}

		if lit, ok := literalValueFromExpr(arg); ok {
			converted, err := evalTimeFunction(spec, lit, lookupType)
			if err != nil {
				return nil, false, false, err
			}
			return literalExprForCastValue(converted, timeFunctionResultType(spec)), true, false, nil
		}

		if allowUnresolved {
			return e, false, true, nil
		}
		return nil, false, false, fmt.Errorf("%s requires a bind marker or literal", e.Name)
	case *parser.CollectionAccessExpr:
		target, targetChanged, targetProxy, err := rewriteTimeFunctionsInExpr(e.Target, params, markerIndex, lookup, allowUnresolved)
		if err != nil {
			return nil, false, false, err
		}
		start, startChanged, startProxy, err := rewriteTimeFunctionsInExpr(e.Start, params, markerIndex, lookup, allowUnresolved)
		if err != nil {
			return nil, false, false, err
		}
		end, endChanged, endProxy, err := rewriteTimeFunctionsInExpr(e.End, params, markerIndex, lookup, allowUnresolved)
		if err != nil {
			return nil, false, false, err
		}
		if !targetChanged && !startChanged && !endChanged {
			return e, false, targetProxy || startProxy || endProxy, nil
		}
		return &parser.CollectionAccessExpr{
			Token:  e.Token,
			Target: target,
			Start:  start,
			End:    end,
			Slice:  e.Slice,
		}, true, targetProxy || startProxy || endProxy, nil
	case *parser.ListLiteral:
		return rewriteTimeFunctionsInExprList(e.Token, e.Elements, params, markerIndex, lookup, allowUnresolved, func(token parser.Position, elems []parser.Expression) parser.Expression {
			return &parser.ListLiteral{Token: token, Elements: elems}
		})
	case *parser.VectorLiteral:
		return rewriteTimeFunctionsInExprList(e.Token, e.Elements, params, markerIndex, lookup, allowUnresolved, func(token parser.Position, elems []parser.Expression) parser.Expression {
			return &parser.VectorLiteral{Token: token, Elements: elems}
		})
	case *parser.SetLiteral:
		return rewriteTimeFunctionsInExprList(e.Token, e.Elements, params, markerIndex, lookup, allowUnresolved, func(token parser.Position, elems []parser.Expression) parser.Expression {
			return &parser.SetLiteral{Token: token, Elements: elems}
		})
	case *parser.TupleLiteral:
		return rewriteTimeFunctionsInExprList(e.Token, e.Elements, params, markerIndex, lookup, allowUnresolved, func(token parser.Position, elems []parser.Expression) parser.Expression {
			return &parser.TupleLiteral{Token: token, Elements: elems}
		})
	case *parser.MapLiteral:
		changed := false
		requiresProxy := false
		entries := make([]parser.MapEntry, len(e.Entries))
		for i, entry := range e.Entries {
			keyExpr, keyChanged, keyProxy, err := rewriteTimeFunctionsInExpr(entry.Key, params, markerIndex, lookup, allowUnresolved)
			if err != nil {
				return nil, false, false, err
			}
			valExpr, valChanged, valProxy, err := rewriteTimeFunctionsInExpr(entry.Value, params, markerIndex, lookup, allowUnresolved)
			if err != nil {
				return nil, false, false, err
			}
			if keyChanged || valChanged {
				changed = true
			}
			if keyProxy || valProxy {
				requiresProxy = true
			}
			entries[i] = parser.MapEntry{Key: keyExpr, Value: valExpr}
		}
		if !changed {
			return e, false, requiresProxy, nil
		}
		return &parser.MapLiteral{Token: e.Token, Entries: entries}, true, requiresProxy, nil
	default:
		return expr, false, false, nil
	}
}

func rewriteTimeFunctionsInExprList(token parser.Position, elems []parser.Expression, params []interface{}, markerIndex map[parser.Expression]int, lookup func(string) (string, bool), allowUnresolved bool, builder func(parser.Position, []parser.Expression) parser.Expression) (parser.Expression, bool, bool, error) {
	if len(elems) == 0 {
		return builder(token, nil), false, false, nil
	}
	changed := false
	requiresProxy := false
	out := make([]parser.Expression, len(elems))
	for i, elem := range elems {
		updated, ch, proxy, err := rewriteTimeFunctionsInExpr(elem, params, markerIndex, lookup, allowUnresolved)
		if err != nil {
			return nil, false, false, err
		}
		if ch {
			changed = true
		}
		if proxy {
			requiresProxy = true
		}
		out[i] = updated
	}
	if !changed {
		return builder(token, elems), false, requiresProxy, nil
	}
	return builder(token, out), true, requiresProxy, nil
}
