package executor

import (
	"context"
	"strings"
	"testing"

	"go.uber.org/zap"

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

type stubSchemaProvider struct {
	columns []types.Row
	indexes []types.Row
}

func (p *stubSchemaProvider) Keyspaces(ctx context.Context, columns []types.ColumnInfo, user, password string) ([]types.Row, error) {
	return nil, nil
}

func (p *stubSchemaProvider) Tables(ctx context.Context, columns []types.ColumnInfo, user, password string) ([]types.Row, error) {
	return nil, nil
}

func (p *stubSchemaProvider) Columns(ctx context.Context, columns []types.ColumnInfo, user, password string) ([]types.Row, error) {
	return p.columns, nil
}

func (p *stubSchemaProvider) Indexes(ctx context.Context, columns []types.ColumnInfo, user, password string) ([]types.Row, error) {
	return p.indexes, nil
}

func (p *stubSchemaProvider) Types(ctx context.Context, columns []types.ColumnInfo, user, password string) ([]types.Row, error) {
	return nil, nil
}

func (p *stubSchemaProvider) Views(ctx context.Context, columns []types.ColumnInfo, user, password string) ([]types.Row, error) {
	return nil, nil
}

func (p *stubSchemaProvider) Triggers(ctx context.Context, columns []types.ColumnInfo, user, password string) ([]types.Row, error) {
	return nil, nil
}

func (p *stubSchemaProvider) Functions(ctx context.Context, columns []types.ColumnInfo, user, password string) ([]types.Row, error) {
	return nil, nil
}

func (p *stubSchemaProvider) Aggregates(ctx context.Context, columns []types.ColumnInfo, user, password string) ([]types.Row, error) {
	return nil, nil
}

func (p *stubSchemaProvider) Users(ctx context.Context, columns []types.ColumnInfo, user, password string) ([]types.Row, error) {
	return nil, nil
}

func TestDescribeTableFromMySQLMetadata(t *testing.T) {
	registry := systemtables.NewRegistry()
	table, ok := registry.Lookup("system_schema.columns")
	if !ok {
		t.Fatal("system_schema.columns not found in registry")
	}
	buildRow := func(values map[string]interface{}) types.Row {
		row := types.Row{Values: make([]interface{}, len(table.Columns))}
		for i, col := range table.Columns {
			if v, ok := values[col.Name]; ok {
				row.Values[i] = v
			}
		}
		return row
	}

	rows := []types.Row{
		buildRow(map[string]interface{}{
			"keyspace_name":     "demo",
			"table_name":        "events",
			"column_name":       "id",
			"column_name_bytes": []byte("id"),
			"kind":              "partition_key",
			"position":          0,
			"clustering_order":  "NONE",
			"type":              "text",
		}),
		buildRow(map[string]interface{}{
			"keyspace_name":     "demo",
			"table_name":        "events",
			"column_name":       "ts",
			"column_name_bytes": []byte("ts"),
			"default_expr":      "CURRENT_TIMESTAMP",
			"on_update_expr":    "CURRENT_TIMESTAMP",
			"kind":              "clustering",
			"position":          0,
			"clustering_order":  "DESC",
			"type":              "timestamp",
		}),
		buildRow(map[string]interface{}{
			"keyspace_name":     "demo",
			"table_name":        "events",
			"column_name":       "value",
			"column_name_bytes": []byte("value"),
			"kind":              "regular",
			"position":          0,
			"clustering_order":  "NONE",
			"type":              "text",
		}),
	}

	systemExec := systemtables.NewSystemTableExecutor(registry)
	indexTable, ok := registry.Lookup("system_schema.indexes")
	if !ok {
		t.Fatal("system_schema.indexes not found in registry")
	}
	buildIndexRow := func(values map[string]interface{}) types.Row {
		row := types.Row{Values: make([]interface{}, len(indexTable.Columns))}
		for i, col := range indexTable.Columns {
			if v, ok := values[col.Name]; ok {
				row.Values[i] = v
			}
		}
		return row
	}
	indexRows := []types.Row{
		buildIndexRow(map[string]interface{}{
			"keyspace_name": "demo",
			"table_name":    "events",
			"index_name":    "z_idx",
			"kind":          "CUSTOM",
			"options":       map[string]string{"target": "value"},
		}),
		buildIndexRow(map[string]interface{}{
			"keyspace_name": "demo",
			"table_name":    "events",
			"index_name":    "a_idx",
			"kind":          "CUSTOM",
			"options":       map[string]string{"target": "ts"},
		}),
	}

	systemExec.SetSchemaProvider(&stubSchemaProvider{columns: rows, indexes: indexRows})

	executor := &Executor{
		logger:         zap.NewNop(),
		systemExecutor: systemExec,
	}

	result, err := executor.Execute(context.Background(), "user", "pass", "DESC TABLE demo.events")
	if err != nil {
		t.Fatalf("DESC TABLE failed: %v", err)
	}
	if len(result.Rows) != 1 {
		t.Fatalf("expected one row, got %d", len(result.Rows))
	}
	createStatement, ok := result.Rows[0].Values[3].(string)
	if !ok {
		t.Fatalf("expected create_statement string, got %#v", result.Rows[0].Values[3])
	}
	if !strings.Contains(createStatement, "CREATE TABLE demo.events") {
		t.Fatalf("unexpected create statement: %s", createStatement)
	}
	if !strings.Contains(createStatement, "PRIMARY KEY (id, ts)") {
		t.Fatalf("missing primary key: %s", createStatement)
	}
	if !strings.Contains(createStatement, "CLUSTERING ORDER BY (ts DESC)") {
		t.Fatalf("missing clustering order: %s", createStatement)
	}
	if !strings.Contains(createStatement, "ts timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") {
		t.Fatalf("missing column default/on-update in create statement: %s", createStatement)
	}
	if !strings.Contains(createStatement, "CREATE INDEX a_idx") || !strings.Contains(createStatement, "CREATE INDEX z_idx") {
		t.Fatalf("missing index statements: %s", createStatement)
	}
	aPos := strings.Index(createStatement, "CREATE INDEX a_idx")
	zPos := strings.Index(createStatement, "CREATE INDEX z_idx")
	if aPos == -1 || zPos == -1 || aPos > zPos {
		t.Fatalf("index statements not sorted: %s", createStatement)
	}
}

func TestExtractTiDBTTLClauseFromShowCreate(t *testing.T) {
	tests := []struct {
		name   string
		sql    string
		expect string
	}{
		{
			name: "ttl with enable and interval",
			sql:  "CREATE TABLE `app`.`events` (`id` bigint(20) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 TTL=`created_at` + INTERVAL 30 DAY TTL_ENABLE='ON' TTL_JOB_INTERVAL='24h' PARTITION BY HASH (`id`) PARTITIONS 4",
			expect: "ttl = `created_at` + INTERVAL 30 DAY " +
				"ttl_enable = 'ON' ttl_job_interval = '24h'",
		},
		{
			name:   "ttl only",
			sql:    "CREATE TABLE `app`.`events` (`id` bigint(20) NOT NULL) ENGINE=InnoDB TTL=`created_at` + INTERVAL 7 DAY",
			expect: "ttl = `created_at` + INTERVAL 7 DAY",
		},
		{
			name: "multiline ttl options",
			sql: "CREATE TABLE `app`.`events` (\n" +
				"  `id` bigint(20) NOT NULL,\n" +
				"  `created_at` datetime NOT NULL\n" +
				") ENGINE=InnoDB\n" +
				"TTL = `created_at` + INTERVAL 5 DAY\n" +
				"TTL_ENABLE = 'OFF'\n" +
				"TTL_JOB_INTERVAL = '48h'",
			expect: "ttl = `created_at` + INTERVAL 5 DAY " +
				"ttl_enable = 'OFF' ttl_job_interval = '48h'",
		},
		{
			name: "tidb ttl hints in show create",
			sql: "CREATE TABLE `chat_test`.`events` (\n" +
				"  `tenant_id` text,\n" +
				"  `payload` text,\n" +
				"  `created_at` timestamp,\n" +
				"  PRIMARY KEY (`tenant_id`)\n" +
				") TTL = `created_at` + INTERVAL 1 DAY */ /*T![ttl] " +
				"TTL_ENABLE = 'ON' TTL_JOB_INTERVAL = '1h' */",
			expect: "ttl = `created_at` + INTERVAL 1 DAY " +
				"ttl_enable = 'ON' ttl_job_interval = '1h'",
		},
		{
			name:   "no ttl",
			sql:    "CREATE TABLE `app`.`events` (`id` bigint(20) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
			expect: "",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got := extractTiDBTTLClauseFromShowCreate(tc.sql)
			if got != tc.expect {
				t.Fatalf("extractTiDBTTLClauseFromShowCreate() = %q, want %q", got, tc.expect)
			}
		})
	}
}

func TestBuildCreateStatementIncludesTTLClause(t *testing.T) {
	def := &describeTableDefinition{
		Keyspace:      "app",
		Table:         "events",
		PartitionKeys: []string{"id"},
		Columns: []types.ColumnInfo{
			{Keyspace: "app", Table: "events", Name: "id", Type: "bigint"},
		},
		TTLClause: "ttl = created_at + interval 30 day ttl_enable = 'on'",
	}

	createStmt, err := buildCreateStatement(def)
	if err != nil {
		t.Fatalf("buildCreateStatement() error = %v", err)
	}
	if !strings.Contains(createStmt, " ttl = created_at + interval 30 day ttl_enable = 'on';") {
		t.Fatalf("expected ttl clause in create statement, got %q", createStmt)
	}
}
