package parser

import "testing"

func mustParse(t *testing.T, input string) Statement {
	t.Helper()
	stmt, err := NewParser(input).Parse()
	if err != nil {
		t.Fatalf("failed to parse %q: %v", input, err)
	}
	if stmt == nil {
		t.Fatalf("parsed nil statement for %q", input)
	}
	return stmt
}

func TestParserUseStatement(t *testing.T) {
	stmt := mustParse(t, "USE ks")
	useStmt, ok := stmt.(*UseStatement)
	if !ok {
		t.Fatalf("expected UseStatement, got %T", stmt)
	}
	if useStmt.Keyspace != "ks" {
		t.Fatalf("expected keyspace ks, got %q", useStmt.Keyspace)
	}
}

func TestParserDescribeKeyspaces(t *testing.T) {
	stmt := mustParse(t, "DESC KEYSPACES")
	describe, ok := stmt.(*DescribeStatement)
	if !ok {
		t.Fatalf("expected DescribeStatement, got %T", stmt)
	}
	if describe.Target != DescribeTargetKeyspaces {
		t.Fatalf("expected target KEYSPACES, got %q", describe.Target)
	}
}

func TestParserDescribeKeyspace(t *testing.T) {
	stmt := mustParse(t, "DESCRIBE KEYSPACE ks")
	describe, ok := stmt.(*DescribeStatement)
	if !ok {
		t.Fatalf("expected DescribeStatement, got %T", stmt)
	}
	if describe.Target != DescribeTargetKeyspace {
		t.Fatalf("expected target KEYSPACE, got %q", describe.Target)
	}
	if describe.Keyspace != "ks" {
		t.Fatalf("expected keyspace ks, got %q", describe.Keyspace)
	}
}

func TestParserDescribeTable(t *testing.T) {
	stmt := mustParse(t, "DESC TABLE ks.tbl")
	describe, ok := stmt.(*DescribeStatement)
	if !ok {
		t.Fatalf("expected DescribeStatement, got %T", stmt)
	}
	if describe.Target != DescribeTargetTable {
		t.Fatalf("expected target TABLE, got %q", describe.Target)
	}
	if describe.Table == nil || describe.Table.Schema != "ks" || describe.Table.Name != "tbl" {
		t.Fatalf("unexpected table name: %v", describe.Table)
	}
}

func TestParserDescribeTablesInKeyspace(t *testing.T) {
	stmt := mustParse(t, "DESCRIBE TABLES IN ks")
	describe, ok := stmt.(*DescribeStatement)
	if !ok {
		t.Fatalf("expected DescribeStatement, got %T", stmt)
	}
	if describe.Target != DescribeTargetTables {
		t.Fatalf("expected target TABLES, got %q", describe.Target)
	}
	if describe.Keyspace != "ks" {
		t.Fatalf("expected keyspace ks, got %q", describe.Keyspace)
	}
}

func TestParserCreateIndexKeys(t *testing.T) {
	stmt := mustParse(t, "CREATE INDEX ON ks.tbl KEYS (col)")
	indexStmt, ok := stmt.(*CreateIndexStatement)
	if !ok {
		t.Fatalf("expected CreateIndexStatement, got %T", stmt)
	}
	if indexStmt.Table == nil || indexStmt.Table.Schema != "ks" || indexStmt.Table.Name != "tbl" {
		t.Fatalf("unexpected table name: %v", indexStmt.Table)
	}
	if len(indexStmt.Columns) != 1 || indexStmt.Columns[0] != "col" {
		t.Fatalf("expected column col, got %v", indexStmt.Columns)
	}
}

func TestParserCreateType(t *testing.T) {
	stmt := mustParse(t, "CREATE TYPE ks.address (street text, zip int)")
	create, ok := stmt.(*CreateTypeStatement)
	if !ok {
		t.Fatalf("expected CreateTypeStatement, got %T", stmt)
	}
	if create.Name.Schema != "ks" || create.Name.Name != "address" {
		t.Fatalf("unexpected type name: %v", create.Name)
	}
	if len(create.Fields) != 2 {
		t.Fatalf("expected 2 fields, got %d", len(create.Fields))
	}
}

func TestParserAlterTypeRename(t *testing.T) {
	stmt := mustParse(t, "ALTER TYPE ks.address RENAME IF EXISTS zip TO postal AND street TO street_name")
	alter, ok := stmt.(*AlterTypeStatement)
	if !ok {
		t.Fatalf("expected AlterTypeStatement, got %T", stmt)
	}
	if alter.Operation != "RENAME" {
		t.Fatalf("expected RENAME operation, got %q", alter.Operation)
	}
	if !alter.FieldIfExists {
		t.Fatalf("expected FieldIfExists=true")
	}
	if len(alter.Renames) != 2 {
		t.Fatalf("expected 2 rename pairs, got %d", len(alter.Renames))
	}
}

func TestParserCreateMaterializedView(t *testing.T) {
	stmt := mustParse(t, "CREATE MATERIALIZED VIEW ks.mv AS SELECT id FROM ks.tbl WHERE id IS NOT NULL PRIMARY KEY (id)")
	create, ok := stmt.(*CreateMaterializedViewStatement)
	if !ok {
		t.Fatalf("expected CreateMaterializedViewStatement, got %T", stmt)
	}
	if create.Name.Schema != "ks" || create.Name.Name != "mv" {
		t.Fatalf("unexpected view name: %v", create.Name)
	}
	if create.Select == nil || create.PrimaryKey == nil {
		t.Fatalf("expected SELECT and PRIMARY KEY in materialized view")
	}
}

func TestParserCreateTrigger(t *testing.T) {
	stmt := mustParse(t, "CREATE TRIGGER tr1 ON ks.tbl USING 'com.example.Trigger'")
	create, ok := stmt.(*CreateTriggerStatement)
	if !ok {
		t.Fatalf("expected CreateTriggerStatement, got %T", stmt)
	}
	if create.Name != "tr1" {
		t.Fatalf("expected trigger name tr1, got %q", create.Name)
	}
	if create.Table.Name != "tbl" || create.Table.Schema != "ks" {
		t.Fatalf("unexpected trigger table: %v", create.Table)
	}
}

func TestParserCreateFunction(t *testing.T) {
	stmt := mustParse(t, "CREATE FUNCTION ks.add_fn (a int, b int) RETURNS NULL ON NULL INPUT RETURNS int LANGUAGE java AS $$ return a + b; $$")
	create, ok := stmt.(*CreateFunctionStatement)
	if !ok {
		t.Fatalf("expected CreateFunctionStatement, got %T", stmt)
	}
	if create.CalledOnNull {
		t.Fatalf("expected returns null on null input")
	}
	if create.ReturnType == nil || create.ReturnType.TypeString() != "INT" {
		t.Fatalf("unexpected return type: %v", create.ReturnType)
	}
	if create.Language != "java" {
		t.Fatalf("unexpected language: %q", create.Language)
	}
	if create.Body == "" {
		t.Fatalf("expected function body")
	}
}

func TestParserCreateAggregate(t *testing.T) {
	stmt := mustParse(t, "CREATE AGGREGATE ks.sum (int) SFUNC ks.sum_state STYPE int FINALFUNC ks.sum_final INITCOND 0")
	create, ok := stmt.(*CreateAggregateStatement)
	if !ok {
		t.Fatalf("expected CreateAggregateStatement, got %T", stmt)
	}
	if len(create.ArgTypes) != 1 {
		t.Fatalf("expected 1 arg type, got %d", len(create.ArgTypes))
	}
	if create.StateFunc == nil || create.StateType == nil {
		t.Fatalf("expected state function and state type")
	}
	if create.FinalFunc == nil || create.InitCond == nil {
		t.Fatalf("expected final function and init condition")
	}
}

func TestParserCreateTableLike(t *testing.T) {
	stmt := mustParse(t, "CREATE TABLE ks.new LIKE ks.old WITH compaction = {'class': 'SizeTieredCompactionStrategy'} AND INDEXES")
	create, ok := stmt.(*CreateTableLikeStatement)
	if !ok {
		t.Fatalf("expected CreateTableLikeStatement, got %T", stmt)
	}
	if !create.WithIndexes {
		t.Fatalf("expected WITH INDEXES")
	}
}

func TestParserBatchEntries(t *testing.T) {
	stmt := mustParse(t, "BEGIN UNLOGGED BATCH INSERT INTO ks.tbl (id) VALUES (1); UPDATE ks.tbl SET name = 'x' WHERE id = 1; APPLY BATCH")
	batch, ok := stmt.(*BatchStatement)
	if !ok {
		t.Fatalf("expected BatchStatement, got %T", stmt)
	}
	if batch.BatchType != "UNLOGGED" {
		t.Fatalf("expected batch type UNLOGGED, got %q", batch.BatchType)
	}
	if len(batch.Entries) != 2 {
		t.Fatalf("expected 2 batch entries, got %d", len(batch.Entries))
	}
}

func TestParserBatchWithNewlines(t *testing.T) {
	stmt := mustParse(t, "BEGIN BATCH\nINSERT INTO ks.tbl (id, name) VALUES (1, 'alpha');\nUPDATE ks.tbl SET name = 'beta' WHERE id = 1;\nAPPLY BATCH")
	batch, ok := stmt.(*BatchStatement)
	if !ok {
		t.Fatalf("expected BatchStatement, got %T", stmt)
	}
	if len(batch.Entries) != 2 {
		t.Fatalf("expected 2 batch entries, got %d", len(batch.Entries))
	}
}

func TestParserNamedBindMarker(t *testing.T) {
	stmt := mustParse(t, "SELECT * FROM ks.tbl WHERE id = :id")
	selectStmt, ok := stmt.(*SelectStatement)
	if !ok {
		t.Fatalf("expected SelectStatement, got %T", stmt)
	}
	bin, ok := selectStmt.Where.(*BinaryExpr)
	if !ok {
		t.Fatalf("expected BinaryExpr in WHERE, got %T", selectStmt.Where)
	}
	if _, ok := bin.Right.(*NamedBindMarker); !ok {
		t.Fatalf("expected NamedBindMarker, got %T", bin.Right)
	}
}

func TestParserTypeHintExpr(t *testing.T) {
	stmt := mustParse(t, "SELECT * FROM ks.tbl WHERE id = ?::uuid")
	selectStmt, ok := stmt.(*SelectStatement)
	if !ok {
		t.Fatalf("expected SelectStatement, got %T", stmt)
	}
	bin, ok := selectStmt.Where.(*BinaryExpr)
	if !ok {
		t.Fatalf("expected BinaryExpr in WHERE, got %T", selectStmt.Where)
	}
	hint, ok := bin.Right.(*TypeHintExpr)
	if !ok {
		t.Fatalf("expected TypeHintExpr, got %T", bin.Right)
	}
	if hint.Type == nil || hint.Type.TypeString() != "UUID" {
		t.Fatalf("unexpected type hint: %v", hint.Type)
	}
}

func TestParserIsNullExpr(t *testing.T) {
	stmt := mustParse(t, "SELECT * FROM ks.tbl WHERE id IS NOT NULL")
	selectStmt, ok := stmt.(*SelectStatement)
	if !ok {
		t.Fatalf("expected SelectStatement, got %T", stmt)
	}
	isNull, ok := selectStmt.Where.(*IsNullExpr)
	if !ok {
		t.Fatalf("expected IsNullExpr, got %T", selectStmt.Where)
	}
	if !isNull.Not {
		t.Fatalf("expected NOT flag on IS NULL expression")
	}
}

func TestParserReservedKeywordIdentifiers(t *testing.T) {
	invalid := []string{
		"SELECT * FROM select",
		"CREATE TABLE table (id int PRIMARY KEY)",
		"INSERT INTO keyspace (id) VALUES (1)",
	}
	for _, input := range invalid {
		if _, err := NewParser(input).Parse(); err == nil {
			t.Fatalf("expected error for %q", input)
		}
	}

	valid := []string{
		`SELECT * FROM "select"`,
		`CREATE TABLE "table" (id int PRIMARY KEY)`,
		`INSERT INTO "keyspace" (id) VALUES (1)`,
	}
	for _, input := range valid {
		if _, err := NewParser(input).Parse(); err != nil {
			t.Fatalf("expected %q to parse, got error: %v", input, err)
		}
	}
}

func TestParserCreateTableColumnDefaultAndOnUpdate(t *testing.T) {
	stmt := mustParse(t, "CREATE TABLE ks.tbl (id int PRIMARY KEY, created_at timestamp DEFAULT current_timestamp, updated_at timestamp DEFAULT now() ON UPDATE current_timestamp)")
	createStmt, ok := stmt.(*CreateTableStatement)
	if !ok {
		t.Fatalf("expected CreateTableStatement, got %T", stmt)
	}
	if len(createStmt.Columns) != 3 {
		t.Fatalf("expected 3 columns, got %d", len(createStmt.Columns))
	}
	created := createStmt.Columns[1]
	if !created.HasDefault || created.DefaultExpr == nil {
		t.Fatalf("expected created_at default expression")
	}
	if created.DefaultExpr.String() != "current_timestamp" {
		t.Fatalf("unexpected created_at default expression: %s", created.DefaultExpr.String())
	}
	updated := createStmt.Columns[2]
	if !updated.HasDefault || updated.DefaultExpr == nil {
		t.Fatalf("expected updated_at default expression")
	}
	if !updated.HasOnUpdate || updated.OnUpdateExpr == nil {
		t.Fatalf("expected updated_at on-update expression")
	}
}

func TestParserRejectsInvalidOnClauseInColumnDefinition(t *testing.T) {
	if _, err := NewParser("CREATE TABLE ks.tbl (id int PRIMARY KEY, updated_at timestamp ON created_at)").Parse(); err == nil {
		t.Fatalf("expected parse error for invalid ON clause")
	}
}

func TestParserCreateTableDirectTTLClause(t *testing.T) {
	stmt := mustParse(t, "CREATE TABLE ks.tbl (id int PRIMARY KEY) TTL = `created_at` + INTERVAL 30 DAY TTL_ENABLE = 'ON' TTL_JOB_INTERVAL = '24h'")
	createStmt, ok := stmt.(*CreateTableStatement)
	if !ok {
		t.Fatalf("expected CreateTableStatement, got %T", stmt)
	}
	raw, ok := createStmt.Options[directTTLOptionKey].(*StringLiteral)
	if !ok {
		t.Fatalf("expected %q option as string literal, got %T", directTTLOptionKey, createStmt.Options[directTTLOptionKey])
	}
	want := "ttl = `created_at` + interval 30 day ttl_enable = 'ON' ttl_job_interval = '24h'"
	if raw.Value != want {
		t.Fatalf("raw ttl = %q, want %q", raw.Value, want)
	}
}

func TestParserAlterTableDirectTTLClause(t *testing.T) {
	stmt := mustParse(t, "ALTER TABLE ks.tbl TTL = `created_at` + INTERVAL 7 DAY TTL_ENABLE = 'ON'")
	alterStmt, ok := stmt.(*AlterTableStatement)
	if !ok {
		t.Fatalf("expected AlterTableStatement, got %T", stmt)
	}
	if alterStmt.Operation != "WITH" {
		t.Fatalf("expected Operation WITH, got %q", alterStmt.Operation)
	}
	raw, ok := alterStmt.Options[directTTLOptionKey].(*StringLiteral)
	if !ok {
		t.Fatalf("expected %q option as string literal, got %T", directTTLOptionKey, alterStmt.Options[directTTLOptionKey])
	}
	want := "ttl = `created_at` + interval 7 day ttl_enable = 'ON'"
	if raw.Value != want {
		t.Fatalf("raw ttl = %q, want %q", raw.Value, want)
	}
}

func TestParserAlterTableDirectRemoveTTLClause(t *testing.T) {
	stmt := mustParse(t, "ALTER TABLE ks.tbl REMOVE TTL")
	alterStmt, ok := stmt.(*AlterTableStatement)
	if !ok {
		t.Fatalf("expected AlterTableStatement, got %T", stmt)
	}
	if alterStmt.Operation != "WITH" {
		t.Fatalf("expected Operation WITH, got %q", alterStmt.Operation)
	}
	raw, ok := alterStmt.Options[directTTLOptionKey].(*StringLiteral)
	if !ok {
		t.Fatalf("expected %q option as string literal, got %T", directTTLOptionKey, alterStmt.Options[directTTLOptionKey])
	}
	if raw.Value != "remove ttl" {
		t.Fatalf("raw ttl = %q, want %q", raw.Value, "remove ttl")
	}
}

func TestParserRejectsWrappedTTLOptions(t *testing.T) {
	cases := []string{
		"CREATE TABLE ks.tbl (id int PRIMARY KEY) WITH ttl = 'TTL = created_at + INTERVAL 1 DAY'",
		"ALTER TABLE ks.tbl WITH ttl = 'REMOVE TTL'",
		"CREATE TABLE ks.tbl (id int PRIMARY KEY) WITH tidb_ttl_raw = 'TTL = created_at + INTERVAL 1 DAY'",
		"ALTER TABLE ks.tbl WITH tidb_ttl_raw = 'REMOVE TTL'",
	}
	for _, c := range cases {
		if _, err := NewParser(c).Parse(); err == nil {
			t.Fatalf("expected parse error for %q", c)
		}
	}
}
