package executor

import (
	"fmt"
	"strings"

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

func isNumericType(cqlType string) bool {
	switch strings.ToLower(strings.TrimSpace(cqlType)) {
	case "tinyint", "smallint", "int", "bigint", "varint", "decimal", "float", "double":
		return true
	default:
		return false
	}
}

func isAllowedArithmeticAssignment(targetColumn string, expr parser.Expression) bool {
	if expr == nil {
		return false
	}
	switch e := expr.(type) {
	case *parser.TypeHintExpr:
		return isAllowedArithmeticAssignment(targetColumn, e.Expr)
	case *parser.CastExpr:
		return isAllowedArithmeticAssignment(targetColumn, e.Expr)
	case *parser.BinaryExpr:
		op := strings.TrimSpace(e.Op)
		if op != "+" && op != "-" {
			return false
		}
		if col, ok := e.Left.(*parser.ColumnExpr); ok && strings.EqualFold(col.Name, targetColumn) {
			return !exprHasColumnRef(e.Right)
		}
		if col, ok := e.Right.(*parser.ColumnExpr); ok && strings.EqualFold(col.Name, targetColumn) {
			return !exprHasColumnRef(e.Left)
		}
		return false
	default:
		return false
	}
}

func validateArithmeticAssignment(targetColumn, cqlType string, expr parser.Expression) error {
	if !exprHasColumnRef(expr) {
		return nil
	}
	if !isNumericType(cqlType) {
		return fmt.Errorf("UPDATE arithmetic is only supported for numeric columns")
	}
	if !isAllowedArithmeticAssignment(targetColumn, expr) {
		return fmt.Errorf("UPDATE assignments may only reference the target column in simple arithmetic")
	}
	return nil
}

func arithmeticAssignmentDelta(assignment *parser.Assignment) (parser.Expression, error) {
	if assignment == nil {
		return nil, fmt.Errorf("UPDATE arithmetic must use column = column +/- term")
	}
	if _, ok := assignment.Target.(*parser.ColumnExpr); !ok {
		return nil, fmt.Errorf("UPDATE arithmetic must target columns")
	}
	return arithmeticDeltaExpr(assignment.Column, assignment.Value)
}

func arithmeticDeltaExpr(targetColumn string, expr parser.Expression) (parser.Expression, error) {
	if expr == nil {
		return nil, fmt.Errorf("UPDATE arithmetic must use column = column +/- term")
	}
	bin, ok := unwrapArithmeticExpr(expr).(*parser.BinaryExpr)
	if !ok {
		return nil, fmt.Errorf("UPDATE arithmetic must use column = column +/- term")
	}
	op := strings.TrimSpace(bin.Op)
	if op != "+" && op != "-" {
		return nil, fmt.Errorf("UPDATE arithmetic must use column = column +/- term")
	}

	leftExpr := bin.Left
	rightExpr := bin.Right
	left := unwrapArithmeticExpr(leftExpr)
	right := unwrapArithmeticExpr(rightExpr)

	if _, ok := columnExprMatch(left, targetColumn); ok {
		if exprHasColumnRef(rightExpr) {
			return nil, fmt.Errorf("UPDATE arithmetic cannot reference other columns")
		}
		if op == "-" {
			return &parser.UnaryExpr{Op: "-", Expr: rightExpr}, nil
		}
		return rightExpr, nil
	}
	if _, ok := columnExprMatch(right, targetColumn); ok {
		if op == "-" {
			return nil, fmt.Errorf("UPDATE arithmetic requires the target column on the left for '-'")
		}
		if exprHasColumnRef(leftExpr) {
			return nil, fmt.Errorf("UPDATE arithmetic cannot reference other columns")
		}
		return leftExpr, nil
	}
	return nil, fmt.Errorf("UPDATE arithmetic must use column = column +/- term")
}

func unwrapArithmeticExpr(expr parser.Expression) parser.Expression {
	for {
		switch e := expr.(type) {
		case *parser.TypeHintExpr:
			expr = e.Expr
		case *parser.CastExpr:
			expr = e.Expr
		default:
			return expr
		}
	}
}
