package executor

import (
	"fmt"
	"strings"

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

type counterAssignment struct {
	column string
	delta  parser.Expression
}

func collectCounterAssignments(assignments []*parser.Assignment, columnTypes map[string]string) ([]counterAssignment, bool, error) {
	if len(assignments) == 0 {
		return nil, false, nil
	}
	out := make([]counterAssignment, 0, len(assignments))
	hasCounter := false
	hasNonCounter := false

	for _, assignment := range assignments {
		if assignment == nil {
			continue
		}
		columnType := lookupColumnType(columnTypes, assignment.Column)
		delta, isCounter, err := counterAssignmentDelta(assignment, columnType)
		if err != nil {
			return nil, true, err
		}
		if isCounter {
			if hasNonCounter {
				return nil, true, fmt.Errorf("counter updates cannot mix counter and non-counter columns")
			}
			hasCounter = true
			out = append(out, counterAssignment{
				column: assignment.Column,
				delta:  delta,
			})
			continue
		}
		if hasCounter {
			return nil, true, fmt.Errorf("counter updates cannot mix counter and non-counter columns")
		}
		hasNonCounter = true
	}

	if !hasCounter {
		return nil, false, nil
	}
	return out, true, nil
}

func counterAssignmentDelta(assignment *parser.Assignment, columnType string) (parser.Expression, bool, error) {
	if assignment == nil {
		return nil, false, nil
	}
	if normalizeCQLTypeString(columnType) != "counter" {
		return nil, false, nil
	}
	delta, err := counterDeltaExpr(assignment)
	if err != nil {
		return nil, true, err
	}
	return delta, true, nil
}

func counterDeltaExpr(assignment *parser.Assignment) (parser.Expression, error) {
	if assignment == nil || assignment.Value == nil {
		return nil, fmt.Errorf("counter updates must use column = column +/- term")
	}
	if _, ok := assignment.Target.(*parser.ColumnExpr); !ok {
		return nil, fmt.Errorf("counter updates must target columns")
	}
	bin, ok := assignment.Value.(*parser.BinaryExpr)
	if !ok {
		return nil, fmt.Errorf("counter updates must use column = column +/- term")
	}
	op := strings.TrimSpace(bin.Op)
	if op != "+" && op != "-" {
		return nil, fmt.Errorf("counter updates must use column = column +/- term")
	}

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

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

func (e *dmlExecutor) buildCounterUpdatePlan(table *parser.TableName, assignments []counterAssignment, whereConditions []equalityCondition, markerIndex map[parser.Expression]int, columnTypes map[string]string) (*dmlPlan, error) {
	if table == nil {
		return nil, fmt.Errorf("table is required")
	}
	if len(assignments) == 0 {
		return nil, fmt.Errorf("counter updates require assignments")
	}

	columns := make([]string, 0, len(assignments)+len(whereConditions))
	for _, assignment := range assignments {
		columns = append(columns, assignment.column)
	}
	for _, cond := range whereConditions {
		columns = append(columns, cond.column)
	}

	var sql strings.Builder
	sql.WriteString("INSERT INTO ")
	sql.WriteString(e.translator.QuoteIdentifier(table.Schema))
	sql.WriteString(".")
	sql.WriteString(e.translator.QuoteIdentifier(table.Name))
	sql.WriteString(" (")
	for i, col := range columns {
		if i > 0 {
			sql.WriteString(", ")
		}
		sql.WriteString(e.translator.QuoteIdentifier(col))
	}
	sql.WriteString(") VALUES (")

	plan := &dmlPlan{}
	for i, assignment := range assignments {
		if i > 0 {
			sql.WriteString(", ")
		}
		colType := lookupColumnType(columnTypes, assignment.column)
		sql.WriteString(e.translateExpressionWithType(assignment.delta, colType))
		appendBindMarkerIndices(assignment.delta, markerIndex, &plan.paramOrder)
	}
	for i, cond := range whereConditions {
		if i > 0 || len(assignments) > 0 {
			sql.WriteString(", ")
		}
		condType := lookupColumnType(columnTypes, cond.column)
		sql.WriteString(e.translateExpressionWithType(cond.value, condType))
		appendBindMarkerIndices(cond.value, markerIndex, &plan.paramOrder)
	}
	sql.WriteString(") ON DUPLICATE KEY UPDATE ")

	for i, assignment := range assignments {
		if i > 0 {
			sql.WriteString(", ")
		}
		quoted := e.translator.QuoteIdentifier(assignment.column)
		sql.WriteString(quoted)
		sql.WriteString(" = ")
		sql.WriteString(quoted)
		sql.WriteString(" + VALUES(")
		sql.WriteString(quoted)
		sql.WriteString(")")
	}

	plan.sql = sql.String()
	return plan, nil
}
