package slowlog_explorer

import (
	"strings"
	"testing"
	"time"

	"github.com/pingcap/tidb/parser"
	_ "github.com/pingcap/tidb/types/parser_driver"
)

func TestDatePartitions(t *testing.T) {
	start := time.Date(2026, 4, 7, 10, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 15, 0, 0, 0, time.UTC)
	dates := datePartitions(start, end)
	expected := []string{"20260407", "20260408", "20260409"}
	if len(dates) != len(expected) {
		t.Fatalf("expected %d dates, got %d: %v", len(expected), len(dates), dates)
	}
	for i, d := range dates {
		if d != expected[i] {
			t.Errorf("dates[%d] = %q, want %q", i, d, expected[i])
		}
	}
}

func TestBuildSlowlogSQL(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildSlowlogSQL(start, end, 30, nil, nil, "time DESC")
	t.Logf("SQL: %s", sql)

	if sql == "" {
		t.Fatal("expected non-empty SQL")
	}
	// Basic checks
	if !contains(sql, "clinic_data_proxy.slow_query_logs") {
		t.Error("missing table name")
	}
	if !contains(sql, "date IN ('20260409')") {
		t.Error("missing date partition")
	}
	if !contains(sql, "LIMIT 30") {
		t.Error("missing LIMIT")
	}
	if !contains(sql, "ORDER BY time DESC") {
		t.Error("missing ORDER BY")
	}
}

func TestBuildSlowlogSQLWithFilters(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	filters := []string{"digest = 'abc123'", "query_time > 0.5"}
	sql := buildSlowlogSQL(start, end, 50, nil, filters, "query_time DESC")
	t.Logf("SQL: %s", sql)

	if !contains(sql, "digest = 'abc123'") {
		t.Error("missing digest filter")
	}
	if !contains(sql, "query_time > 0.5") {
		t.Error("missing query_time filter")
	}
}

func TestBuildSlowlogSQLWithCursor(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	cursor := 1744156800.123
	sql := buildSlowlogSQL(start, end, 30, &cursor, nil, "time DESC")
	t.Logf("SQL: %s", sql)

	if !contains(sql, "time < 1744156800.123") {
		t.Errorf("missing cursor condition, got: %s", sql)
	}
}

func TestBuildSlowlogSQLForMaxComputeWithColumns(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	columns := []string{"query_time", "db", "time", "digest", "conn_id", "query"}

	sql := buildSlowlogSQLForDialectWithColumns(start, end, 30, nil, nil, "time DESC", slowlogQueryDialectMaxCompute, columns)
	wantProjection := "SELECT query_time, db, time, digest, conn_id, query FROM"
	if !strings.HasPrefix(sql, wantProjection) {
		t.Fatalf("SQL projection = %q, want prefix %q", sql, wantProjection)
	}
	if strings.Contains(sql, "plan_digest") || strings.Contains(sql, "rocksdb_block_read_byte") {
		t.Fatalf("narrow SQL contains an unrequested column: %s", sql)
	}
}

func TestBuildSlowlogSQLForMaxComputeIncludesHiddenOrderByColumn(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	fields := "db"
	orderSpec, err := parseOrderByForDialect("query_time DESC", slowlogQueryDialectMaxCompute)
	if err != nil {
		t.Fatalf("parseOrderByForDialect: %v", err)
	}
	columns, err := selectSlowlogColumns(&fields, slowlogQueryDialectMaxCompute, orderSpec.column)
	if err != nil {
		t.Fatalf("selectSlowlogColumns: %v", err)
	}

	sql := buildSlowlogSQLForDialectWithColumns(start, end, 30, nil, nil, orderSpec.clause, slowlogQueryDialectMaxCompute, columns)
	if !strings.HasPrefix(sql, "SELECT db, time, digest, conn_id, query_time FROM") {
		t.Fatalf("hidden ORDER BY column missing from projection: %s", sql)
	}
	if !strings.Contains(sql, "ORDER BY CAST(query_time AS DOUBLE) DESC") {
		t.Fatalf("unexpected ORDER BY clause: %s", sql)
	}
	if strings.Contains(sql, ", query FROM") {
		t.Fatalf("query should not be mandatory: %s", sql)
	}
}

func TestParseFilters(t *testing.T) {
	raw := `[{"field":"digest","op":"=","value":"abc123"},{"field":"query_time","op":">","value":500},{"field":"is_internal","op":"=","value":"false"}]`
	clauses, err := parseFilters(raw)
	if err != nil {
		t.Fatalf("parseFilters: %v", err)
	}
	t.Logf("Clauses: %v", clauses)
	if len(clauses) != 3 {
		t.Fatalf("expected 3 clauses, got %d", len(clauses))
	}
	// digest should be hex-validated and lowercased
	if !contains(clauses[0], "digest") {
		t.Error("missing digest clause")
	}
	// query_time: 500ms → 0.5s
	if !contains(clauses[1], "CAST(query_time AS DOUBLE) > 0.5") {
		t.Errorf("expected query_time > 0.5, got %s", clauses[1])
	}
}

func TestParseFiltersNumericStringValues(t *testing.T) {
	raw := `[{"field":"query_time","op":">=","value":"500"},{"field":"total_keys","op":"<","value":"1000"},{"field":"request_unit_read","op":">","value":"12.5"}]`
	clauses, err := parseFilters(raw)
	if err != nil {
		t.Fatalf("parseFilters: %v", err)
	}
	if len(clauses) != 3 {
		t.Fatalf("expected 3 clauses, got %d", len(clauses))
	}
	if !contains(clauses[0], "CAST(query_time AS DOUBLE) >= 0.5") {
		t.Errorf("expected query_time >= 0.5, got %s", clauses[0])
	}
	if !contains(clauses[1], "CAST(total_keys AS DOUBLE) < 1000") {
		t.Errorf("expected total_keys < 1000, got %s", clauses[1])
	}
	if !contains(clauses[2], "CAST(request_unit_read AS DOUBLE) > 12.5") {
		t.Errorf("expected request_unit_read > 12.5, got %s", clauses[2])
	}
}

func TestParseFiltersRejectsInvalidInput(t *testing.T) {
	tests := []string{
		`[{"field":"unknown","op":"=","value":"x"}]`,
		`[{"field":"query_time","op":"LIKE","value":"500"}]`,
		`[{"field":"digest","op":"=","value":"not-a-hex"}]`,
		`[{"field":"db","op":"=","value":"db' OR 1=1"}]`,
	}
	for _, raw := range tests {
		if _, err := parseFilters(raw); err == nil {
			t.Fatalf("expected parseFilters(%s) to fail", raw)
		}
	}
}

func TestParseOrderBy(t *testing.T) {
	validTests := []struct {
		input    string
		expected string
	}{
		{"", "time DESC"},
		{"time DESC", "time DESC"},
		{"query_time ASC", "CAST(query_time AS DOUBLE) ASC"},
		{"mem_max", "CAST(mem_max AS DOUBLE) DESC"},
		{"time asc", "time ASC"},
	}
	for _, tt := range validTests {
		result, err := parseOrderBy(tt.input)
		if err != nil {
			t.Errorf("parseOrderBy(%q) unexpected error: %v", tt.input, err)
			continue
		}
		if result != tt.expected {
			t.Errorf("parseOrderBy(%q) = %q, want %q", tt.input, result, tt.expected)
		}
	}

	invalidTests := []string{"invalid_column DESC", "time SIDEWAYS", "time DESC extra"}
	for _, input := range invalidTests {
		if _, err := parseOrderBy(input); err == nil {
			t.Errorf("parseOrderBy(%q) expected error", input)
		}
	}
}

func TestParseOrderByForDialectCastsNumericColumns(t *testing.T) {
	spec, err := parseOrderByForDialect("total_keys DESC", slowlogQueryDialectBigQuery)
	if err != nil {
		t.Fatalf("parseOrderByForDialect: %v", err)
	}
	if spec.clause != "PARSE_NUMERIC(NULLIF(CONCAT(total_keys, ''), '')) DESC" {
		t.Fatalf("BigQuery order clause = %q", spec.clause)
	}

	spec, err = parseOrderByForDialect("request_count ASC", slowlogQueryDialectMaxCompute)
	if err != nil {
		t.Fatalf("parseOrderByForDialect: %v", err)
	}
	if spec.clause != "CAST(NULLIF(request_count, '') AS DOUBLE) ASC" {
		t.Fatalf("MaxCompute order clause = %q", spec.clause)
	}
}

func TestBuildStatsSQL(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildStatsSQL(start, end, nil)
	t.Logf("Stats SQL: %s", sql)
	if !contains(sql, "COUNT(*)") {
		t.Error("missing COUNT")
	}
	if !contains(sql, "approx_percentile") {
		t.Error("missing percentile")
	}
}

func TestBuildStatsSQLForMaxCompute(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildStatsSQLForDialect(start, end, nil, slowlogQueryDialectMaxCompute)
	t.Logf("MaxCompute stats SQL: %s", sql)
	if !contains(sql, "percentile_approx(CAST(query_time AS DOUBLE), 0.5)") {
		t.Error("missing MaxCompute percentile function")
	}
	if contains(sql, "approx_percentile") {
		t.Error("MaxCompute stats SQL should not use Athena percentile function")
	}
}

func TestBuildStatsSQLForBigQuery(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildStatsSQLForDialect(start, end, nil, slowlogQueryDialectBigQuery)
	t.Logf("BigQuery stats SQL: %s", sql)
	if !contains(sql, "APPROX_QUANTILES(PARSE_NUMERIC(NULLIF(CONCAT(query_time, ''), '')), 100) AS percentiles") {
		t.Error("missing BigQuery quantiles array")
	}
	if contains(sql, "approx_percentile") || contains(sql, "[OFFSET") {
		t.Error("BigQuery stats SQL should not use unsupported percentile syntax")
	}
}

func TestBuildTopDigestsSQL(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildTopDigestsSQL(start, end, 5, nil)
	t.Logf("TopDigests SQL: %s", sql)
	if !contains(sql, "GROUP BY digest") {
		t.Error("missing GROUP BY")
	}
	if !contains(sql, "LIMIT 5") {
		t.Error("missing LIMIT")
	}
}

func TestBuildTopDigestsSQLForMaxCompute(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildTopDigestsSQLForDialect(start, end, 5, nil, slowlogQueryDialectMaxCompute, "total_time")
	t.Logf("MaxCompute top digests SQL: %s", sql)
	if !contains(sql, "percentile_approx(CAST(query_time AS DOUBLE), 0.99)") {
		t.Error("missing MaxCompute percentile function")
	}
	if contains(sql, "approx_percentile") {
		t.Error("MaxCompute top digests SQL should not use Athena percentile function")
	}
	if !contains(sql, "any_value(query)") {
		t.Error("missing sample query aggregate")
	}
}

func TestBuildMaxComputeAggregateSQLIsTiDBParseable(t *testing.T) {
	start := time.Date(2026, 8, 24, 16, 0, 0, 0, time.UTC)
	end := time.Date(2026, 8, 25, 16, 0, 0, 0, time.UTC)
	filters := []string{"is_internal = false"}
	queries := map[string]string{
		"stats":       buildStatsSQLForDialect(start, end, filters, slowlogQueryDialectMaxCompute),
		"top digests": buildTopDigestsSQLForDialect(start, end, 5, filters, slowlogQueryDialectMaxCompute, "total_time"),
	}

	p := parser.New()
	for name, sql := range queries {
		t.Run(name, func(t *testing.T) {
			if _, err := p.ParseOneStmt(sql, "", ""); err != nil {
				t.Fatalf("generated logical SQL is not TiDB-parseable: %v\nSQL: %s", err, sql)
			}
		})
	}
}

func TestBuildTopDigestsSQLForBigQuery(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildTopDigestsSQLForDialect(start, end, 5, nil, slowlogQueryDialectBigQuery, "total_time")
	t.Logf("BigQuery top digests SQL: %s", sql)
	if !contains(sql, "APPROX_QUANTILES(PARSE_NUMERIC(NULLIF(CONCAT(query_time, ''), '')), 100) AS p99_ms") {
		t.Error("missing BigQuery quantiles array")
	}
	if contains(sql, "approx_percentile") || contains(sql, "[OFFSET") {
		t.Error("BigQuery top digests SQL should not use unsupported percentile syntax")
	}
	if !contains(sql, "GROUP BY digest") {
		t.Error("missing GROUP BY")
	}
}

func TestBuildTopDigestsSQLWithMetric(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildTopDigestsSQLForDialect(start, end, 10, nil, slowlogQueryDialectDefault, "sum_total_keys")
	t.Logf("Top digests by total keys SQL: %s", sql)
	if !contains(sql, "ROUND(SUM(COALESCE(CAST(total_keys AS DOUBLE), 0)), 2) AS sum_total_keys") {
		t.Error("missing SUM total_keys metric")
	}
	if !contains(sql, "ROUND(SUM(COALESCE(CAST(process_keys AS DOUBLE), 0)), 2) AS sum_process_keys") {
		t.Error("missing companion SUM process_keys metric")
	}
	if !contains(sql, "ORDER BY sum_total_keys DESC") {
		t.Error("top digests should order by selected metric")
	}
}

func TestBuildTopDigestsSQLWithCountMetric(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildTopDigestsSQLForDialect(start, end, 10, nil, slowlogQueryDialectDefault, "count")
	t.Logf("Top digests by count SQL: %s", sql)
	if !contains(sql, "COUNT(*) AS cnt") {
		t.Error("missing count metric")
	}
	if !contains(sql, "ORDER BY cnt DESC") {
		t.Error("top digests should order by count")
	}
}

func TestBuildTopDigestsSQLWithRUMetric(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildTopDigestsSQLForDialect(start, end, 10, nil, slowlogQueryDialectBigQuery, "avg_ru_read")
	t.Logf("Top digests by avg RU read SQL: %s", sql)
	if !contains(sql, "ROUND(AVG(COALESCE(PARSE_NUMERIC(NULLIF(CONCAT(request_unit_read, ''), '')), 0)), 2) AS avg_ru_read") {
		t.Error("missing AVG RU read metric")
	}
	if !contains(sql, "ROUND(AVG(COALESCE(PARSE_NUMERIC(NULLIF(CONCAT(request_unit_write, ''), '')), 0)), 2) AS avg_ru_write") {
		t.Error("missing companion AVG RU write metric")
	}
	if !contains(sql, "ORDER BY avg_ru_read DESC") {
		t.Error("top digests should order by selected metric")
	}
}

func TestBuildTopDigestsSQLWithRUMetricUsesAthenaNumericColumns(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildTopDigestsSQLForDialect(start, end, 10, nil, slowlogQueryDialectDefault, "sum_ru_read")
	t.Logf("Athena top digests by SUM RU read SQL: %s", sql)
	if contains(sql, "NULLIF") || contains(sql, "AS VARCHAR") || contains(sql, "AS CHAR") {
		t.Error("Athena RU metric should use numeric columns directly")
	}
	if !contains(sql, "ROUND(SUM(COALESCE(CAST(request_unit_read AS DOUBLE), 0)), 2) AS sum_ru_read") {
		t.Error("missing zero-safe SUM RU read metric")
	}
	if !contains(sql, "ROUND(SUM(COALESCE(CAST(request_unit_write AS DOUBLE), 0)), 2) AS sum_ru_write") {
		t.Error("missing zero-safe companion SUM RU write metric")
	}
}

func TestBuildSlowlogSQLForSynapse(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildSlowlogSQLForDialect(start, end, 30, nil, nil, "[time] DESC", slowlogQueryDialectSynapse)
	t.Logf("Synapse slowlog SQL: %s", sql)
	if !contains(sql, "SELECT TOP (30)") {
		t.Error("Synapse list SQL should use TOP")
	}
	if contains(sql, "LIMIT") {
		t.Error("Synapse list SQL should not use LIMIT")
	}
	if !contains(sql, "[date] IN ('20260409')") || !contains(sql, "ORDER BY [time] DESC") {
		t.Error("Synapse list SQL should quote date/time columns")
	}
}

func TestBuildStatsSQLForSynapse(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildStatsSQLForDialect(start, end, nil, slowlogQueryDialectSynapse)
	t.Logf("Synapse stats SQL: %s", sql)
	if !contains(sql, "COUNT_BIG(*) AS total_count") {
		t.Error("Synapse stats SQL should use COUNT_BIG")
	}
	if !contains(sql, "APPROX_PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY TRY_CAST(NULLIF(CAST([query_time] AS VARCHAR(64)), '') AS FLOAT)) AS p99") {
		t.Error("Synapse stats SQL should use Synapse percentile syntax")
	}
}

func TestBuildTopDigestsSQLForSynapse(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildTopDigestsSQLForDialect(start, end, 10, nil, slowlogQueryDialectSynapse, "sum_ru_read")
	t.Logf("Synapse top digests SQL: %s", sql)
	if !contains(sql, "SELECT TOP (10)") || contains(sql, "LIMIT") {
		t.Error("Synapse top digests SQL should use TOP without LIMIT")
	}
	if !contains(sql, "COUNT_BIG(*) AS cnt") {
		t.Error("Synapse top digests SQL should use COUNT_BIG")
	}
	if !contains(sql, "MAX(CAST([query] AS NVARCHAR(4000))) AS sample_query") {
		t.Error("Synapse top digests SQL should use MAX(CAST(query AS NVARCHAR)) for sample query")
	}
	if !contains(sql, "ROUND(SUM(COALESCE(TRY_CAST(NULLIF(CAST([request_unit_read] AS VARCHAR(64)), '') AS FLOAT), 0)), 2) AS sum_ru_read") {
		t.Error("missing Synapse zero-safe SUM RU read metric")
	}
}

func TestBuildTrendSQLForSynapse(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildTrendSQLForDialect(start, end, "p99", 300, nil, slowlogQueryDialectSynapse)
	t.Logf("Synapse trend SQL: %s", sql)
	if !contains(sql, "FLOOR([time] / 300) * 300") {
		t.Error("Synapse trend SQL should quote time in bucket expression")
	}
	if !contains(sql, "APPROX_PERCENTILE_CONT(0.99) WITHIN GROUP") {
		t.Error("Synapse trend SQL should use Synapse percentile syntax")
	}
}

func TestBuildDetailSQLForSynapse(t *testing.T) {
	sql := buildDetailSQLForDialect(1775707648.457, "abc123", "conn_1", slowlogQueryDialectSynapse)
	t.Logf("Synapse detail SQL: %s", sql)
	if !contains(sql, "SELECT TOP (1)") || contains(sql, "LIMIT") {
		t.Error("Synapse detail SQL should use TOP without LIMIT")
	}
	if !contains(sql, "WHERE [date] = '20260409'") || !contains(sql, "ORDER BY ABS([time] - 1775707648.457") {
		t.Error("Synapse detail SQL should quote date/time columns")
	}
}

func TestParseFiltersForSynapse(t *testing.T) {
	raw := `[{"field":"request_unit_read","op":">","value":"12.5"},{"field":"is_internal","op":"=","value":"false"}]`
	clauses, err := parseFiltersForDialect(raw, slowlogQueryDialectSynapse)
	if err != nil {
		t.Fatalf("parseFiltersForDialect: %v", err)
	}
	if !contains(clauses[0], "TRY_CAST(NULLIF(CAST([request_unit_read] AS VARCHAR(64)), '') AS FLOAT) > 12.5") {
		t.Errorf("unexpected Synapse RU filter: %s", clauses[0])
	}
	if clauses[1] != "[is_internal] = 0" {
		t.Errorf("unexpected Synapse boolean filter: %s", clauses[1])
	}
}

func TestParseOrderByForSynapse(t *testing.T) {
	spec, err := parseOrderByForDialect("request_unit_write DESC", slowlogQueryDialectSynapse)
	if err != nil {
		t.Fatalf("parseOrderByForDialect: %v", err)
	}
	if spec.clause != "TRY_CAST(NULLIF(CAST([request_unit_write] AS VARCHAR(64)), '') AS FLOAT) DESC" {
		t.Fatalf("Synapse order clause = %q", spec.clause)
	}
}

func TestBuildTrendSQLUsesExplicitBucketExpression(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildTrendSQL(start, end, "count", 300, nil)
	t.Logf("Trend SQL: %s", sql)
	if contains(sql, "GROUP BY 1") || contains(sql, "ORDER BY 1") {
		t.Error("trend SQL should not use select ordinals for ODPS compatibility")
	}
	if !contains(sql, "GROUP BY FLOOR(time / 300) * 300") {
		t.Error("trend SQL should group by explicit bucket expression")
	}
}

func TestBuildTrendSQLForMaxCompute(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildTrendSQLForDialect(start, end, "p99", 300, nil, slowlogQueryDialectMaxCompute)
	t.Logf("MaxCompute trend SQL: %s", sql)
	if !contains(sql, "percentile_approx(CAST(query_time AS DOUBLE), 0.99)") {
		t.Error("missing MaxCompute percentile function")
	}
	if contains(sql, "approx_percentile") {
		t.Error("MaxCompute trend SQL should not use Athena percentile function")
	}
	if !contains(sql, "GROUP BY FLOOR(time / 300) * 300") {
		t.Error("trend SQL should group by explicit bucket expression")
	}
}

func TestBuildTrendSQLForBigQuery(t *testing.T) {
	start := time.Date(2026, 4, 9, 0, 0, 0, 0, time.UTC)
	end := time.Date(2026, 4, 9, 3, 0, 0, 0, time.UTC)
	sql := buildTrendSQLForDialect(start, end, "p99", 300, nil, slowlogQueryDialectBigQuery)
	t.Logf("BigQuery trend SQL: %s", sql)
	if !contains(sql, "APPROX_QUANTILES(PARSE_NUMERIC(NULLIF(CONCAT(query_time, ''), '')), 100) AS val") {
		t.Error("missing BigQuery quantiles array")
	}
	if contains(sql, "approx_percentile") || contains(sql, "[OFFSET") {
		t.Error("BigQuery trend SQL should not use unsupported percentile syntax")
	}
	if !contains(sql, "GROUP BY FLOOR(time / 300) * 300") {
		t.Error("trend SQL should group by explicit bucket expression")
	}
}

func TestSlowlogQueryDialectForProvider(t *testing.T) {
	if got := slowlogQueryDialectForProvider("alicloud"); got != slowlogQueryDialectMaxCompute {
		t.Fatalf("alicloud dialect = %q, want %q", got, slowlogQueryDialectMaxCompute)
	}
	if got := slowlogQueryDialectForProvider("gcp"); got != slowlogQueryDialectBigQuery {
		t.Fatalf("gcp dialect = %q, want %q", got, slowlogQueryDialectBigQuery)
	}
	if got := slowlogQueryDialectForProvider("azure"); got != slowlogQueryDialectSynapse {
		t.Fatalf("azure dialect = %q, want %q", got, slowlogQueryDialectSynapse)
	}
	if got := slowlogQueryDialectForProvider("aws"); got != slowlogQueryDialectDefault {
		t.Fatalf("aws dialect = %q, want default", got)
	}
}

func TestTrendInterval(t *testing.T) {
	now := time.Now()
	if trendInterval(now.Add(-30*time.Minute), now) != 60 {
		t.Error("30min range should use 60s interval")
	}
	if trendInterval(now.Add(-2*time.Hour), now) != 300 {
		t.Error("2h range should use 300s interval")
	}
	if trendInterval(now.Add(-6*time.Hour), now) != 600 {
		t.Error("6h range should use 600s interval")
	}
	if trendInterval(now.Add(-24*time.Hour), now) != 900 {
		t.Error("24h range should use 900s interval")
	}
}

func contains(s, substr string) bool {
	return len(s) > 0 && len(substr) > 0 && (s == substr || len(s) >= len(substr) && containsStr(s, substr))
}

func containsStr(s, sub string) bool {
	for i := 0; i <= len(s)-len(sub); i++ {
		if s[i:i+len(sub)] == sub {
			return true
		}
	}
	return false
}
