package executor

import (
	"fmt"

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

func applyBindMarkersToSelect(stmt *parser.SelectStatement, params []interface{}) (*parser.SelectStatement, error) {
	if stmt == nil || len(params) == 0 {
		return stmt, nil
	}
	markerIndex := buildBindMarkerIndex(stmt)
	if len(markerIndex) == 0 {
		return stmt, nil
	}

	out := *stmt
	var err error

	out.Columns, err = replaceBindMarkersExprSlice(stmt.Columns, params, markerIndex)
	if err != nil {
		return nil, err
	}
	out.Where, err = replaceBindMarkersExpr(stmt.Where, params, markerIndex)
	if err != nil {
		return nil, err
	}
	out.Having, err = replaceBindMarkersExpr(stmt.Having, params, markerIndex)
	if err != nil {
		return nil, err
	}
	out.GroupBy, err = replaceBindMarkersExprSlice(stmt.GroupBy, params, markerIndex)
	if err != nil {
		return nil, err
	}

	opts := stmt.Options
	opts.Limit, err = replaceBindMarkersExpr(stmt.Options.Limit, params, markerIndex)
	if err != nil {
		return nil, err
	}
	opts.PerPartitionLimit, err = replaceBindMarkersExpr(stmt.Options.PerPartitionLimit, params, markerIndex)
	if err != nil {
		return nil, err
	}
	out.Options = opts

	return &out, nil
}

func replaceBindMarkersExprSlice(exprs []parser.Expression, params []interface{}, markerIndex map[parser.Expression]int) ([]parser.Expression, error) {
	if len(exprs) == 0 {
		return exprs, nil
	}
	changed := false
	out := make([]parser.Expression, len(exprs))
	for i, expr := range exprs {
		replaced, err := replaceBindMarkersExpr(expr, params, markerIndex)
		if err != nil {
			return nil, err
		}
		if replaced != expr {
			changed = true
		}
		out[i] = replaced
	}
	if !changed {
		return exprs, nil
	}
	return out, nil
}

func replaceBindMarkersExpr(expr parser.Expression, params []interface{}, markerIndex map[parser.Expression]int) (parser.Expression, error) {
	if expr == nil {
		return nil, nil
	}

	switch e := expr.(type) {
	case *parser.BindMarker, *parser.NamedBindMarker:
		index, ok := markerIndex[expr]
		if !ok {
			return nil, fmt.Errorf("bind marker index not found")
		}
		if index >= len(params) {
			return nil, fmt.Errorf("bind marker index out of range")
		}
		return literalExprFromValue(params[index]), nil
	case *parser.BinaryExpr:
		left, err := replaceBindMarkersExpr(e.Left, params, markerIndex)
		if err != nil {
			return nil, err
		}
		right, err := replaceBindMarkersExpr(e.Right, params, markerIndex)
		if err != nil {
			return nil, err
		}
		if left == e.Left && right == e.Right {
			return e, nil
		}
		return &parser.BinaryExpr{Token: e.Token, Op: e.Op, Left: left, Right: right}, nil
	case *parser.UnaryExpr:
		inner, err := replaceBindMarkersExpr(e.Expr, params, markerIndex)
		if err != nil {
			return nil, err
		}
		if inner == e.Expr {
			return e, nil
		}
		return &parser.UnaryExpr{Token: e.Token, Op: e.Op, Expr: inner}, nil
	case *parser.IsNullExpr:
		inner, err := replaceBindMarkersExpr(e.Expr, params, markerIndex)
		if err != nil {
			return nil, err
		}
		if inner == e.Expr {
			return e, nil
		}
		return &parser.IsNullExpr{Token: e.Token, Expr: inner, Not: e.Not}, nil
	case *parser.TypeHintExpr:
		inner, err := replaceBindMarkersExpr(e.Expr, params, markerIndex)
		if err != nil {
			return nil, err
		}
		if inner == e.Expr {
			return e, nil
		}
		return &parser.TypeHintExpr{Token: e.Token, Expr: inner, Type: e.Type}, nil
	case *parser.AliasedExpr:
		inner, err := replaceBindMarkersExpr(e.Expr, params, markerIndex)
		if err != nil {
			return nil, err
		}
		if inner == e.Expr {
			return e, nil
		}
		return &parser.AliasedExpr{Token: e.Token, Expr: inner, Alias: e.Alias}, nil
	case *parser.FunctionCall:
		changed := false
		args := make([]parser.Expression, len(e.Args))
		for i, arg := range e.Args {
			replaced, err := replaceBindMarkersExpr(arg, params, markerIndex)
			if err != nil {
				return nil, err
			}
			if replaced != arg {
				changed = true
			}
			args[i] = replaced
		}
		if !changed {
			return e, nil
		}
		return &parser.FunctionCall{Name: e.Name, Args: args}, nil
	case *parser.CollectionAccessExpr:
		target, err := replaceBindMarkersExpr(e.Target, params, markerIndex)
		if err != nil {
			return nil, err
		}
		start, err := replaceBindMarkersExpr(e.Start, params, markerIndex)
		if err != nil {
			return nil, err
		}
		end, err := replaceBindMarkersExpr(e.End, params, markerIndex)
		if err != nil {
			return nil, err
		}
		if target == e.Target && start == e.Start && end == e.End {
			return e, nil
		}
		return &parser.CollectionAccessExpr{
			Token:  e.Token,
			Target: target,
			Start:  start,
			End:    end,
			Slice:  e.Slice,
		}, nil
	case *parser.ListLiteral:
		return replaceBindMarkersInList(e.Token, e.Elements, params, markerIndex, func(token parser.Position, elems []parser.Expression) parser.Expression {
			return &parser.ListLiteral{Token: token, Elements: elems}
		})
	case *parser.VectorLiteral:
		return replaceBindMarkersInList(e.Token, e.Elements, params, markerIndex, func(token parser.Position, elems []parser.Expression) parser.Expression {
			return &parser.VectorLiteral{Token: token, Elements: elems}
		})
	case *parser.SetLiteral:
		return replaceBindMarkersInList(e.Token, e.Elements, params, markerIndex, func(token parser.Position, elems []parser.Expression) parser.Expression {
			return &parser.SetLiteral{Token: token, Elements: elems}
		})
	case *parser.TupleLiteral:
		return replaceBindMarkersInList(e.Token, e.Elements, params, markerIndex, func(token parser.Position, elems []parser.Expression) parser.Expression {
			return &parser.TupleLiteral{Token: token, Elements: elems}
		})
	case *parser.MapLiteral:
		changed := false
		entries := make([]parser.MapEntry, len(e.Entries))
		for i, entry := range e.Entries {
			keyExpr, err := replaceBindMarkersExpr(entry.Key, params, markerIndex)
			if err != nil {
				return nil, err
			}
			valExpr, err := replaceBindMarkersExpr(entry.Value, params, markerIndex)
			if err != nil {
				return nil, err
			}
			if keyExpr != entry.Key || valExpr != entry.Value {
				changed = true
			}
			entries[i] = parser.MapEntry{Key: keyExpr, Value: valExpr}
		}
		if !changed {
			return e, nil
		}
		return &parser.MapLiteral{Token: e.Token, Entries: entries}, nil
	default:
		return expr, nil
	}
}

func replaceBindMarkersInList(token parser.Position, elems []parser.Expression, params []interface{}, markerIndex map[parser.Expression]int, builder func(parser.Position, []parser.Expression) parser.Expression) (parser.Expression, error) {
	if len(elems) == 0 {
		return builder(token, nil), nil
	}
	changed := false
	out := make([]parser.Expression, len(elems))
	for i, elem := range elems {
		replaced, err := replaceBindMarkersExpr(elem, params, markerIndex)
		if err != nil {
			return nil, err
		}
		if replaced != elem {
			changed = true
		}
		out[i] = replaced
	}
	if !changed {
		return builder(token, elems), nil
	}
	return builder(token, out), nil
}

func literalExprFromValue(value interface{}) parser.Expression {
	switch v := value.(type) {
	case nil:
		return &parser.NullLiteral{}
	case bool:
		if v {
			return &parser.BooleanLiteral{Value: "true"}
		}
		return &parser.BooleanLiteral{Value: "false"}
	case string:
		return &parser.StringLiteral{Value: v}
	case []byte:
		return &parser.StringLiteral{Value: string(v)}
	case int:
		return &parser.IntegerLiteral{Value: int64(v)}
	case int8:
		return &parser.IntegerLiteral{Value: int64(v)}
	case int16:
		return &parser.IntegerLiteral{Value: int64(v)}
	case int32:
		return &parser.IntegerLiteral{Value: int64(v)}
	case int64:
		return &parser.IntegerLiteral{Value: v}
	case uint:
		return &parser.IntegerLiteral{Value: int64(v)}
	case uint8:
		return &parser.IntegerLiteral{Value: int64(v)}
	case uint16:
		return &parser.IntegerLiteral{Value: int64(v)}
	case uint32:
		return &parser.IntegerLiteral{Value: int64(v)}
	case uint64:
		return &parser.IntegerLiteral{Value: int64(v)}
	case float32:
		return &parser.FloatLiteral{Value: float64(v)}
	case float64:
		return &parser.FloatLiteral{Value: v}
	case fmt.Stringer:
		return &parser.StringLiteral{Value: v.String()}
	default:
		return &parser.StringLiteral{Value: fmt.Sprint(v)}
	}
}
