package executor

import (
	"context"
	"testing"

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

func TestIndexMeetsMatchRequirements(t *testing.T) {
	eqCols := map[string]struct{}{
		"a": {},
		"b": {},
	}
	rangeCols := map[string]struct{}{
		"c": {},
	}
	groupByCols := map[string]struct{}{
		"d": {},
	}

	meta := indexMeta{name: "idx1", columns: []string{"a", "b", "c"}}
	if indexMeetsMatchRequirements(meta, eqCols, rangeCols, groupByCols) {
		t.Fatalf("expected idx1 to fail group-by requirement")
	}

	meta2 := indexMeta{name: "idx2", columns: []string{"b", "d", "c", "a"}}
	if !indexMeetsMatchRequirements(meta2, eqCols, rangeCols, groupByCols) {
		t.Fatalf("expected idx2 to satisfy match requirements")
	}
}

func TestExtractIndexConditionsOrDisqualify(t *testing.T) {
	stmt, err := parser.NewParser("SELECT * FROM ks.tbl WHERE a = 1 OR b = 2").Parse()
	if err != nil {
		t.Fatalf("parse failed: %v", err)
	}
	selectStmt, ok := stmt.(*parser.SelectStatement)
	if !ok {
		t.Fatalf("expected select statement, got %T", stmt)
	}
	_, disqualify, _, err := extractIndexConditions(selectStmt.Where)
	if err != nil {
		t.Fatalf("extractIndexConditions failed: %v", err)
	}
	if !disqualify {
		t.Fatalf("expected OR predicate to disqualify index selection")
	}
}

func TestApplyPreRules(t *testing.T) {
	c1 := indexCandidate{
		meta:          indexMeta{name: "u_cover", unique: true},
		fullMatch:     true,
		needBackTable: false,
		pointRanges:   1,
	}
	c2 := indexCandidate{
		meta:          indexMeta{name: "u_back", unique: true},
		fullMatch:     true,
		needBackTable: true,
		pointRanges:   10,
	}
	c3 := indexCandidate{
		meta:          indexMeta{name: "n_cover", unique: false},
		fullMatch:     true,
		needBackTable: false,
		pointRanges:   5,
	}

	best, _, ok := applyPreRules([]indexCandidate{c2, c1, c3})
	if !ok {
		t.Fatalf("expected pre-rules to select a candidate")
	}
	if best.meta.name != "u_cover" {
		t.Fatalf("expected unique covering index to win, got %s", best.meta.name)
	}
}

func TestRuleFourTieBreak(t *testing.T) {
	rule2 := indexCandidate{
		meta:          indexMeta{name: "u_back", unique: true},
		fullMatch:     true,
		needBackTable: true,
		pointRanges:   10,
	}
	rule3 := indexCandidate{
		meta:          indexMeta{name: "n_cover", unique: false},
		fullMatch:     true,
		needBackTable: false,
		pointRanges:   5,
	}
	best, _, ok := applyPreRules([]indexCandidate{rule2, rule3})
	if !ok {
		t.Fatalf("expected pre-rules to select a candidate")
	}
	if best.meta.name != "n_cover" {
		t.Fatalf("expected rule4 to choose smaller total rows, got %s", best.meta.name)
	}
}

func TestSelectIndexHintSkipsAllowFiltering(t *testing.T) {
	exec := &Executor{}
	exec.ensureInternals()
	stmt, err := parser.NewParser("SELECT * FROM ks.tbl WHERE a = 1 ALLOW FILTERING").Parse()
	if err != nil {
		t.Fatalf("parse failed: %v", err)
	}
	selectStmt := stmt.(*parser.SelectStatement)
	selectStmt.From = &parser.TableName{Schema: "ks", Name: "tbl"}
	selectStmt.Options.AllowFiltering = true
	hint, err := exec.selectPlanner.selectIndexHint(context.Background(), "u", "p", selectStmt)
	if err != nil {
		t.Fatalf("selectIndexHint should not error with allow filtering: %v", err)
	}
	if hint != "" {
		t.Fatalf("expected empty hint when allow filtering is set")
	}
}

func TestSelectBestIndexForORAllowsIntersection(t *testing.T) {
	stmt, err := parser.NewParser("SELECT * FROM ks.tbl WHERE a = 1 AND (b = 2 OR b = 3)").Parse()
	if err != nil {
		t.Fatalf("parse failed: %v", err)
	}
	selectStmt := stmt.(*parser.SelectStatement)
	indexes := []mysqlbackend.IndexInfo{
		{Name: "idx_ab", Column: "a", Seq: 1},
		{Name: "idx_ab", Column: "b", Seq: 2},
	}
	best, _, ok, err := selectBestIndexForOR(selectStmt, indexes)
	if err != nil {
		t.Fatalf("selectBestIndexForOR failed: %v", err)
	}
	if !ok {
		t.Fatalf("expected OR query to be eligible for index selection")
	}
	if best.meta.name != "idx_ab" {
		t.Fatalf("expected idx_ab to be selected, got %s", best.meta.name)
	}
}

func TestSelectBestIndexForORRejectsNoIntersection(t *testing.T) {
	stmt, err := parser.NewParser("SELECT * FROM ks.tbl WHERE a = 1 OR b = 2").Parse()
	if err != nil {
		t.Fatalf("parse failed: %v", err)
	}
	selectStmt := stmt.(*parser.SelectStatement)
	indexes := []mysqlbackend.IndexInfo{
		{Name: "idx_a", Column: "a", Seq: 1},
		{Name: "idx_b", Column: "b", Seq: 1},
	}
	_, _, ok, err := selectBestIndexForOR(selectStmt, indexes)
	if err != nil {
		t.Fatalf("selectBestIndexForOR failed: %v", err)
	}
	if ok {
		t.Fatalf("expected OR query without shared index to be rejected")
	}
}
