package platform

import (
	"fmt"
	"net/url"
	"slices"
	"strings"
	"testing"
	"time"
)

func TestBuildVehicleListSQL(t *testing.T) {
	query := url.Values{"keyword": {"粤A"}, "protocol": {"JT808"}, "limit": {"5"}, "offset": {"10"}}
	built := buildVehicleListSQL(query)
	for _, want := range []string{"vehicle_identity_binding", "vehicle_realtime_snapshot", "vehicle_realtime_location", "ORDER BY s.updated_at DESC, s.vin ASC, s.protocol ASC"} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("SQL missing %q: %s", want, built.Text)
		}
	}
	if !strings.Contains(built.CountText, "COUNT(*)") || strings.Contains(built.CountText, "LIMIT") {
		t.Fatalf("count SQL = %s", built.CountText)
	}
	if len(built.Args) != 7 || built.Args[0] != "%粤A%" || built.Args[4] != "JT808" || built.Args[5] != 5 || built.Args[6] != 10 {
		t.Fatalf("args = %#v", built.Args)
	}
	if len(built.CountArgs) != 5 || built.CountArgs[0] != "%粤A%" || built.CountArgs[4] != "JT808" {
		t.Fatalf("count args = %#v", built.CountArgs)
	}
}

func TestBuildVehicleListSQLFiltersServiceStatus(t *testing.T) {
	query := url.Values{"serviceStatus": {"degraded"}, "limit": {"8"}}
	built := buildVehicleListSQL(query)
	for _, want := range []string{
		"vs.source_count > 0",
		"vs.online_source_count > 0",
		"vs.online_source_count < vs.source_count",
	} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("SQL missing vehicle list service status predicate %q: %s", want, built.Text)
		}
	}
	if !strings.Contains(built.CountText, "vs.source_count > 0") || !strings.Contains(built.CountText, "vs.online_source_count < vs.source_count") {
		t.Fatalf("count SQL should include vehicle list service status predicate: %s", built.CountText)
	}
}

func TestBuildVehicleCoverageSQL(t *testing.T) {
	query := url.Values{"keyword": {"粤A"}, "protocol": {"GB32960"}, "coverage": {"multi"}, "online": {"online"}, "bindingStatus": {"bound"}, "limit": {"8"}, "offset": {"16"}}
	built := buildVehicleCoverageSQL(query)
	for _, want := range []string{"GROUP BY v.vin", "HAVING", "GROUP_CONCAT(DISTINCT s.protocol", "source_count", "online_source_count", "ORDER BY MAX(s.updated_at) DESC, v.vin ASC"} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("SQL missing %q: %s", want, built.Text)
		}
	}
	if strings.Contains(built.Text, "1ORDER BY") || strings.Contains(built.Text, "0ORDER BY") {
		t.Fatalf("SQL should keep whitespace before ORDER BY: %s", built.Text)
	}
	if !strings.Contains(built.CountText, "vehicle_coverage_count") || !strings.Contains(built.CountText, "HAVING") {
		t.Fatalf("count SQL = %s", built.CountText)
	}
	if len(built.Args) != 9 || built.Args[0] != "%粤A%" || built.Args[6] != "GB32960" || built.Args[7] != 8 || built.Args[8] != 16 {
		t.Fatalf("args = %#v", built.Args)
	}
	if len(built.CountArgs) != 7 || built.CountArgs[0] != "%粤A%" || built.CountArgs[6] != "GB32960" {
		t.Fatalf("count args = %#v", built.CountArgs)
	}
}

func TestBuildVehicleCoverageSQLFiltersServiceStatus(t *testing.T) {
	query := url.Values{"serviceStatus": {"degraded"}, "limit": {"8"}}
	built := buildVehicleCoverageSQL(query)
	for _, want := range []string{
		"HAVING",
		"COUNT(DISTINCT s.protocol) > 0",
		"COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) > 0",
		"COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) < COUNT(DISTINCT s.protocol)",
	} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("SQL missing degraded service status predicate %q: %s", want, built.Text)
		}
	}
	if !strings.Contains(built.CountText, "vehicle_coverage_count") || !strings.Contains(built.CountText, "HAVING") {
		t.Fatalf("count SQL should include service status HAVING: %s", built.CountText)
	}
}

func TestBuildVehicleCoverageSQLIncludesNoDataVehicles(t *testing.T) {
	query := url.Values{"serviceStatus": {"no_data"}, "limit": {"8"}}
	built := buildVehicleCoverageSQL(query)
	for _, want := range []string{
		"vehicle_identity_binding",
		"UNION",
		"vehicle_realtime_snapshot",
		"LEFT JOIN vehicle_realtime_snapshot s ON s.vin = v.vin",
		"COUNT(DISTINCT s.protocol) = 0",
		"MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 1",
	} {
		if !strings.Contains(built.Text+built.CountText, want) {
			t.Fatalf("no-data coverage SQL missing %q: %s / %s", want, built.Text, built.CountText)
		}
	}
}

func TestBuildVehicleCoverageSQLFiltersMissingProtocol(t *testing.T) {
	query := url.Values{"missingProtocol": {"YUTONG_MQTT"}, "limit": {"8"}}
	built := buildVehicleCoverageSQL(query)
	for _, want := range []string{
		"HAVING",
		"COUNT(DISTINCT CASE WHEN s.protocol = ? THEN s.protocol END) = 0",
		"vehicle_coverage_count",
	} {
		if !strings.Contains(built.Text+built.CountText, want) {
			t.Fatalf("missing protocol SQL missing %q: %s / %s", want, built.Text, built.CountText)
		}
	}
	if len(built.Args) < 3 || built.Args[0] != "YUTONG_MQTT" || built.CountArgs[0] != "YUTONG_MQTT" {
		t.Fatalf("missing protocol args should be shared by data and count queries, args=%#v count=%#v", built.Args, built.CountArgs)
	}
}

func TestBuildVehicleCoverageSQLFiltersArchiveStatus(t *testing.T) {
	query := url.Values{"archiveStatus": {"incomplete"}, "limit": {"8"}}
	built := buildVehicleCoverageSQL(query)
	for _, want := range []string{
		"HAVING",
		"COALESCE(NULLIF(MAX(NULLIF(s.plate, '')), ''), NULLIF(MAX(NULLIF(b.plate, '')), '')) IS NULL",
		"NULLIF(MAX(NULLIF(b.phone, '')), '') IS NULL",
		"NULLIF(MAX(NULLIF(b.oem, '')), '') IS NULL",
		"vehicle_coverage_count",
	} {
		if !strings.Contains(built.Text+built.CountText, want) {
			t.Fatalf("archive status SQL missing %q: %s / %s", want, built.Text, built.CountText)
		}
	}
}

func TestBuildVehicleCoverageSQLFiltersArchiveMissingField(t *testing.T) {
	query := url.Values{"archiveMissing": {"phone"}, "limit": {"8"}}
	built := buildVehicleCoverageSQL(query)
	for _, want := range []string{
		"HAVING",
		"NULLIF(MAX(NULLIF(b.phone, '')), '') IS NULL",
		"vehicle_coverage_count",
	} {
		if !strings.Contains(built.Text+built.CountText, want) {
			t.Fatalf("archive missing-field SQL missing %q: %s / %s", want, built.Text, built.CountText)
		}
	}
}

func TestBuildVehicleCoverageSummarySQL(t *testing.T) {
	query := url.Values{"keyword": {"粤A"}, "coverage": {"multi"}, "online": {"online"}, "bindingStatus": {"bound"}, "serviceStatus": {"healthy"}, "archiveMissing": {"oem"}}
	built := buildVehicleCoverageSummarySQL(query)
	for _, want := range []string{
		"vehicle_realtime_snapshot",
		"vehicle_identity_binding",
		"vehicle_coverage_summary",
		"SUM(CASE WHEN online_source_count > 0 THEN 1 ELSE 0 END)",
		"SUM(CASE WHEN source_count > 1 THEN 1 ELSE 0 END)",
		"SUM(CASE WHEN source_count = 0 THEN 1 ELSE 0 END)",
		"SUM(CASE WHEN archive_complete = 0 THEN 1 ELSE 0 END)",
		"SUM(CASE WHEN missing_phone = 1 THEN 1 ELSE 0 END)",
		"SUM(CASE WHEN missing_oem = 1 THEN 1 ELSE 0 END)",
		"HAVING",
		"COUNT(DISTINCT s.protocol) > 1",
		"NULLIF(MAX(NULLIF(b.oem, '')), '') IS NULL",
	} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("summary SQL missing %q: %s", want, built.Text)
		}
	}
	if strings.Contains(built.Text, "LIMIT") || strings.Contains(built.Text, "OFFSET") {
		t.Fatalf("summary SQL should not paginate: %s", built.Text)
	}
	if len(built.Args) != 6 || built.Args[0] != "%粤A%" {
		t.Fatalf("args = %#v", built.Args)
	}
}

func TestBuildRealtimeLocationSQL(t *testing.T) {
	query := url.Values{"vin": {"粤A"}, "protocol": {"GB32960"}, "limit": {"10"}, "offset": {"20"}}
	built := buildRealtimeLocationSQL(query)
	for _, want := range []string{"vehicle_realtime_location", "vehicle_identity_binding", "ORDER BY l.updated_at DESC, l.vin ASC, l.protocol ASC"} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("SQL missing %q: %s", want, built.Text)
		}
	}
	if !strings.Contains(built.CountText, "COUNT(*)") || strings.Contains(built.CountText, "LIMIT") {
		t.Fatalf("count SQL = %s", built.CountText)
	}
	if len(built.Args) != 6 || built.Args[0] != "GB32960" || built.Args[4] != 10 || built.Args[5] != 20 {
		t.Fatalf("args = %#v", built.Args)
	}
	if len(built.CountArgs) != 4 || built.CountArgs[0] != "GB32960" {
		t.Fatalf("count args = %#v", built.CountArgs)
	}
}

func TestBuildVehicleRealtimeSQL(t *testing.T) {
	query := url.Values{"vin": {"粤A"}, "protocol": {"JT808"}, "online": {"online"}, "limit": {"10"}, "offset": {"20"}}
	built := buildVehicleRealtimeSQL(query)
	for _, want := range []string{"vehicle_realtime_location", "vehicle_identity_binding", "vehicle_realtime_snapshot", "vehicle_daily_mileage", "access_report_interval_ms", "GROUP BY v.vin", "GROUP_CONCAT(DISTINCT l.protocol", "online_source_count", "location_available", "soc_available", "today_mileage_available", "vehicle_realtime_count"} {
		if !strings.Contains(built.Text+built.CountText, want) {
			t.Fatalf("SQL missing %q: %s / %s", want, built.Text, built.CountText)
		}
	}
	if !strings.Contains(built.Text, "ORDER BY MAX(l.updated_at) IS NULL ASC, MAX(l.updated_at) DESC, v.vin ASC") {
		t.Fatalf("SQL should keep vehicle-level stable pagination order: %s", built.Text)
	}
	for _, want := range []string{
		"INTERVAL 10 MINUTE",
		"WHEN 'GB32960' THEN 10",
		"WHEN 'YUTONG_MQTT' THEN 20",
		"WHEN 'JT808' THEN 30",
		"ELSE 100 END ELSE 100 END ASC, l.updated_at DESC",
		"location_source",
		"location_conflict",
		"location_conflict_distance_m",
	} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("realtime source arbitration missing %q: %s", want, built.Text)
		}
	}
	if len(built.Args) != 8 || built.Args[0] != "JT808" || built.Args[1] != "%粤A%" || built.Args[6] != 10 || built.Args[7] != 20 {
		t.Fatalf("args = %#v", built.Args)
	}
	if len(built.CountArgs) != 6 || built.CountArgs[0] != "JT808" || built.CountArgs[1] != "%粤A%" {
		t.Fatalf("count args = %#v", built.CountArgs)
	}
}

func TestBuildVehicleRealtimeSQLFiltersMultipleVehicleKeywords(t *testing.T) {
	built := buildVehicleRealtimeSQL(url.Values{"keywords": {"粤AG18312, 川AHTWO1, 粤AG18312"}, "limit": {"10"}})
	for _, text := range []string{built.Text, built.CountText} {
		if !strings.Contains(text, "v.vin IN (?,?)") || !strings.Contains(text, "l.plate IN (?,?)") || !strings.Contains(text, "v.plate IN (?,?)") {
			t.Fatalf("batch vehicle search must use exact identity sets in rows and count: %s", text)
		}
		if strings.Contains(text, "LIKE") {
			t.Fatalf("batch vehicle search must not expand copied plates into fuzzy predicates: %s", text)
		}
	}
	if len(built.Args) != 8 || built.Args[0] != "粤AG18312" || built.Args[1] != "川AHTWO1" || built.Args[2] != "粤AG18312" || built.Args[6] != 10 {
		t.Fatalf("batch args = %#v", built.Args)
	}
	if len(built.CountArgs) != 6 || built.CountArgs[0] != "粤AG18312" || built.CountArgs[1] != "川AHTWO1" || built.CountArgs[4] != "粤AG18312" {
		t.Fatalf("batch count args = %#v", built.CountArgs)
	}
}

func TestBuildVehicleRealtimeSQLBoundsCopiedPlateSet(t *testing.T) {
	keywords := make([]string, 0, vehicleSearchKeywordLimit+10)
	for index := 0; index < vehicleSearchKeywordLimit+10; index++ {
		keywords = append(keywords, fmt.Sprintf("粤A%05d", index))
	}
	built := buildVehicleRealtimeSQL(url.Values{"keywords": {strings.Join(keywords, ",")}, "limit": {"10"}})
	if len(built.Args) != vehicleSearchKeywordLimit*3+2 {
		t.Fatalf("bounded batch args = %d, want %d", len(built.Args), vehicleSearchKeywordLimit*3+2)
	}
	if len(built.CountArgs) != vehicleSearchKeywordLimit*3 {
		t.Fatalf("bounded batch count args = %d, want %d", len(built.CountArgs), vehicleSearchKeywordLimit*3)
	}
	if slices.Contains(built.Args, "粤A00100") {
		t.Fatalf("batch query must ignore identities beyond the %d item boundary", vehicleSearchKeywordLimit)
	}
}

func TestBuildVehicleRealtimeSQLFiltersServiceStatus(t *testing.T) {
	query := url.Values{"serviceStatus": {"degraded"}, "limit": {"8"}}
	built := buildVehicleRealtimeSQL(query)
	for _, want := range []string{
		"HAVING",
		"COUNT(DISTINCT l.protocol) > 0",
		"COUNT(DISTINCT CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN l.protocol END) > 0",
		"COUNT(DISTINCT CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN l.protocol END) < COUNT(DISTINCT l.protocol)",
	} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("SQL missing realtime service status predicate %q: %s", want, built.Text)
		}
	}
	if !strings.Contains(built.CountText, "vehicle_realtime_count") || !strings.Contains(built.CountText, "HAVING") {
		t.Fatalf("count SQL should include realtime service status HAVING: %s", built.CountText)
	}
}

func TestBuildVehicleRealtimeSQLFiltersMotionStatusBeforePagination(t *testing.T) {
	built := buildVehicleRealtimeSQL(url.Values{"status": {"driving"}, "limit": {"200"}})
	for _, want := range []string{"HAVING", "INTERVAL 1 MINUTE", "GROUP_CONCAT(CAST(l.speed_kmh AS CHAR)", "> 3"} {
		if !strings.Contains(built.Text, want) || !strings.Contains(built.CountText, want) {
			t.Fatalf("motion filter must constrain rows and count before LIMIT, missing %q: %s / %s", want, built.Text, built.CountText)
		}
	}
}

func TestBuildVehicleRealtimeSQLFiltersVehiclesWithoutRealtimeLocation(t *testing.T) {
	built := buildVehicleRealtimeSQL(url.Values{"status": {"no_location"}, "limit": {"200"}})
	for _, text := range []string{built.Text, built.CountText} {
		for _, want := range []string{"vehicle_identity_binding b0", "LEFT JOIN vehicle_realtime_location l ON l.vin = v.vin", "= 0"} {
			if !strings.Contains(text, want) {
				t.Fatalf("no-location filter must retain identity population and filter after the left join, missing %q: %s", want, text)
			}
		}
	}
	if !strings.Contains(built.Text, "AS location_available") {
		t.Fatalf("realtime row must expose explicit location availability: %s", built.Text)
	}
	if strings.Contains(built.Text, "SELECT l0.vin") || strings.Contains(built.CountText, "SELECT l0.vin") {
		t.Fatalf("default monitor population must stay on authoritative bound vehicles: %s / %s", built.Text, built.CountText)
	}
}

func TestBuildVehicleRealtimeSQLCanStillAuditUnboundRealtimeIdentities(t *testing.T) {
	built := buildVehicleRealtimeSQL(url.Values{"serviceStatus": {"identity_required"}, "limit": {"20"}})
	for _, text := range []string{built.Text, built.CountText} {
		for _, want := range []string{"UNION ALL", "SELECT l0.vin", "b1.vin IS NULL", "v.binding_status = 'bound'"} {
			if !strings.Contains(text, want) {
				t.Fatalf("identity-required audit must explicitly add unbound realtime identities, missing %q: %s", want, text)
			}
		}
	}
}

func TestBuildDailyMileageSQL(t *testing.T) {
	query := url.Values{"vin": {"VIN001"}, "protocol": {"JT808"}, "dateFrom": {"2026-07-01"}, "dateTo": {"2026-07-03"}}
	built := buildDailyMileageSQL(query)
	if !strings.Contains(built.Text, "vehicle_daily_mileage") || !strings.Contains(built.Text, "vehicle_identity_binding") {
		t.Fatalf("SQL = %s", built.Text)
	}
	if !strings.Contains(built.Text, "ORDER BY m.stat_date DESC, m.vin ASC, m.protocol ASC") {
		t.Fatalf("SQL should keep stable pagination order: %s", built.Text)
	}
	if strings.Contains(built.Text, "m.first_total_mileage_km") || !strings.Contains(built.Text, "m.latest_total_mileage_km - m.daily_mileage_km") {
		t.Fatalf("daily mileage must follow the current projection schema and derive its start value: %s", built.Text)
	}
	if !strings.Contains(built.CountText, "COUNT(*)") || strings.Contains(built.CountText, "LIMIT") {
		t.Fatalf("count SQL = %s", built.CountText)
	}
	if len(built.Args) != 9 || built.Args[0] != "%VIN001%" || built.Args[4] != "JT808" || built.Args[7] != 20 || built.Args[8] != 0 {
		t.Fatalf("args = %#v", built.Args)
	}
	if len(built.CountArgs) != 7 || built.CountArgs[0] != "%VIN001%" || built.CountArgs[4] != "JT808" {
		t.Fatalf("count args = %#v", built.CountArgs)
	}
}

func TestMileageQueriesFilterExactVINSelection(t *testing.T) {
	query := url.Values{"vins": {"VIN001,VIN002,VIN001"}, "dateFrom": {"2026-07-01"}, "dateTo": {"2026-07-03"}}
	daily := buildDailyMileageSQL(query)
	if !strings.Contains(daily.Text, "m.vin IN (?,?)") || daily.Args[0] != "VIN001" || daily.Args[1] != "VIN002" {
		t.Fatalf("daily mileage should use an exact de-duplicated VIN list: %s %#v", daily.Text, daily.Args)
	}
	statistics := buildMileageStatisticsSummarySQL(query)
	if !strings.Contains(statistics.Text, "m.vin IN (?,?)") || len(statistics.Args) != 4 {
		t.Fatalf("statistics should share the selected VIN scope: %s %#v", statistics.Text, statistics.Args)
	}
	fleet := buildFleetLatestMileageSQL(query)
	if !strings.Contains(fleet.Text, "l.vin IN (?,?)") || len(fleet.Args) != 2 {
		t.Fatalf("latest mileage should share the selected VIN scope: %s %#v", fleet.Text, fleet.Args)
	}
}

func TestMileageQueriesApplyPerVehicleAuthorizationDates(t *testing.T) {
	query := url.Values{
		"scopeVins":        {"VIN001,VIN002"},
		"scopeVehicleFrom": {`{"vin002":"2026-07-12","VIN001":"2026-07-11"}`},
	}
	for name, built := range map[string]SQLQuery{
		"daily":      buildDailyMileageSQL(query),
		"summary":    buildMileageSummarySQL(query),
		"statistics": buildMileageStatisticsSummarySQL(query),
	} {
		for _, want := range []string{
			"(m.vin = ? AND m.stat_date >= ?)",
			"VIN001 2026-07-11",
			"VIN002 2026-07-12",
		} {
			text := built.Text
			if strings.HasPrefix(want, "VIN") {
				text = fmt.Sprint(built.Args)
			}
			if !strings.Contains(text, want) {
				t.Fatalf("%s authorization scope missing %q: %s args=%#v", name, want, built.Text, built.Args)
			}
		}
	}
}

func TestMileageQueriesFailClosedForMalformedAuthorizationDates(t *testing.T) {
	built := buildDailyMileageSQL(url.Values{
		"scopeVins":        {"VIN001"},
		"scopeVehicleFrom": {"not-json"},
	})
	if !strings.Contains(built.Text, "1 = 0") || !strings.Contains(built.CountText, "1 = 0") {
		t.Fatalf("malformed authorization scope must fail closed: %s / %s", built.Text, built.CountText)
	}
}

func TestMileageQueriesCanRestrictFleetScopeToAuthoritativelyBoundVehicles(t *testing.T) {
	query := url.Values{"vehicleScope": {"bound"}, "dateFrom": {"2026-07-01"}, "dateTo": {"2026-07-03"}}
	for name, built := range map[string]SQLQuery{
		"daily":      buildDailyMileageSQL(query),
		"statistics": buildMileageStatisticsSummarySQL(query),
		"latest":     buildFleetLatestMileageSQL(query),
	} {
		if !strings.Contains(built.Text, "b.vin IS NOT NULL") || !strings.Contains(built.Text, "b.vin <> ''") {
			t.Fatalf("%s mileage SQL must exclude unbound realtime-only identities: %s", name, built.Text)
		}
	}
}

func TestBuildDailyMileageSQLCanMatchStatisticsVehicleDayScope(t *testing.T) {
	built := buildDailyMileageSQL(url.Values{"deduplicate": {"1"}, "limit": {"50"}})
	for _, want := range []string{"GROUP BY m.vin, m.stat_date", "MAX(COALESCE(m.daily_mileage_km, 0))", "GROUP_CONCAT(m.protocol ORDER BY m.daily_mileage_km DESC", "vehicle_daily_mileage_count"} {
		if !strings.Contains(built.Text+built.CountText, want) {
			t.Fatalf("deduplicated daily mileage SQL missing %q: %s / %s", want, built.Text, built.CountText)
		}
	}
	if built.Args[len(built.Args)-2] != 50 || built.Args[len(built.Args)-1] != 0 {
		t.Fatalf("deduplicated daily mileage pagination args = %#v", built.Args)
	}
}

func TestMySQLListBuildersClampUntrustedPageSizes(t *testing.T) {
	query := url.Values{"limit": {"1000000000"}, "offset": {"7"}}
	builders := map[string]SQLQuery{
		"vehicles":          buildVehicleListSQL(query),
		"vehicle coverage":  buildVehicleCoverageSQL(query),
		"realtime location": buildRealtimeLocationSQL(query),
		"vehicle realtime":  buildVehicleRealtimeSQL(query),
		"daily mileage":     buildDailyMileageSQL(query),
	}
	for name, built := range builders {
		if len(built.Args) < 2 || built.Args[len(built.Args)-2] != maxSQLPageSize || built.Args[len(built.Args)-1] != 7 {
			t.Fatalf("%s pagination was not clamped: %#v", name, built.Args)
		}
	}
	limit, offset := buildLimitOffset(query)
	if limit != maxSQLPageSize || offset != 7 {
		t.Fatalf("response pagination metadata must match SQL: limit=%d offset=%d", limit, offset)
	}
	if limit, _ := parseLimitOffset("0", "0"); limit != 20 {
		t.Fatalf("zero page size must use the safe default, got %d", limit)
	}
}

func TestMileageQueriesRespectEnabledProtocolPriority(t *testing.T) {
	query := url.Values{
		"protocols":   {"JT808,GB32960"},
		"dateFrom":    {"2026-07-01"},
		"dateTo":      {"2026-07-03"},
		"deduplicate": {"1"},
	}
	daily := buildDailyMileageSQL(query)
	for _, want := range []string{
		"m.protocol IN (?,?)",
		"CASE m.protocol WHEN 'JT808' THEN 1 WHEN 'GB32960' THEN 2 ELSE 999 END",
		"ELSE 999 END, m.protocol ASC",
		"GROUP_CONCAT(CAST(COALESCE(m.daily_mileage_km, 0) AS CHAR) ORDER BY",
	} {
		if !strings.Contains(daily.Text, want) {
			t.Fatalf("priority daily mileage SQL missing %q: %s", want, daily.Text)
		}
	}
	if daily.Args[0] != "JT808" || daily.Args[1] != "GB32960" {
		t.Fatalf("enabled protocol filter should preserve priority order: %#v", daily.Args)
	}

	statistics := buildMileageStatisticsSummarySQL(query)
	for _, want := range []string{
		"m.protocol IN (?,?)",
		"CASE m.protocol WHEN 'JT808' THEN 1 WHEN 'GB32960' THEN 2 ELSE 999 END",
		"SUBSTRING_INDEX(GROUP_CONCAT",
	} {
		if !strings.Contains(statistics.Text, want) {
			t.Fatalf("priority statistics SQL missing %q: %s", want, statistics.Text)
		}
	}
	if strings.Contains(statistics.Text, "MAX(COALESCE(m.daily_mileage_km, 0))") {
		t.Fatalf("configured priority must select the preferred source instead of the maximum mileage: %s", statistics.Text)
	}

	fleet := buildFleetLatestMileageSQL(query)
	if !strings.Contains(fleet.Text, "l.protocol IN (?,?)") || !strings.Contains(fleet.Text, "CASE l.protocol WHEN 'JT808' THEN 1 WHEN 'GB32960' THEN 2") {
		t.Fatalf("latest mileage should share the configured source strategy: %s", fleet.Text)
	}
}

func TestMileageProtocolsRejectUnknownValues(t *testing.T) {
	protocols := parseMileageProtocols("JT808,invalid');DROP TABLE vehicle_daily_mileage;--,JT808,YUTONG_MQTT")
	if fmt.Sprint(protocols) != "[JT808 YUTONG_MQTT]" {
		t.Fatalf("unexpected sanitized mileage protocols: %#v", protocols)
	}
}

func TestBuildMileageSummarySQL(t *testing.T) {
	query := url.Values{"vin": {"粤A"}, "protocol": {"GB32960"}, "dateFrom": {"2026-07-01"}, "dateTo": {"2026-07-03"}}
	built := buildMileageSummarySQL(query)
	for _, want := range []string{"COUNT(DISTINCT m.vin)", "COUNT(DISTINCT m.protocol)", "SUM(m.daily_mileage_km)", "vehicle_daily_mileage", "vehicle_identity_binding"} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("SQL missing %q: %s", want, built.Text)
		}
	}
	if strings.Contains(built.Text, "LIMIT") || strings.Contains(built.Text, "OFFSET") {
		t.Fatalf("summary SQL should not paginate: %s", built.Text)
	}
	if len(built.Args) != 7 || built.Args[0] != "%粤A%" || built.Args[4] != "GB32960" {
		t.Fatalf("args = %#v", built.Args)
	}
}

func TestBuildMileageStatisticsSQLDeduplicatesVehicleDays(t *testing.T) {
	query := url.Values{"vin": {"粤A"}, "protocol": {"GB32960"}, "dateFrom": {"2026-07-01"}, "dateTo": {"2026-07-31"}}
	summary := buildMileageStatisticsSummarySQL(query)
	for _, want := range []string{"GROUP BY m.vin, m.stat_date", "MAX(COALESCE(m.daily_mileage_km", "COUNT(DISTINCT d.vin)", "SUM(d.daily_mileage_km)"} {
		if !strings.Contains(summary.Text, want) {
			t.Fatalf("statistics summary SQL missing %q: %s", want, summary.Text)
		}
	}
	if len(summary.Args) != 7 || summary.Args[0] != "%粤A%" || summary.Args[4] != "GB32960" {
		t.Fatalf("statistics args = %#v", summary.Args)
	}
	trend := buildMileageStatisticsTrendSQL(query)
	if !strings.Contains(trend.Text, "GROUP BY d.stat_date ORDER BY d.stat_date ASC") {
		t.Fatalf("statistics trend SQL should be chronologically stable: %s", trend.Text)
	}
	ranking := buildMileageStatisticsRankingSQL(query)
	if !strings.Contains(ranking.Text, "ORDER BY SUM(d.daily_mileage_km) DESC") || !strings.Contains(ranking.Text, "ORDER BY d.stat_date DESC") || !strings.Contains(ranking.Text, "LIMIT 20") {
		t.Fatalf("statistics ranking SQL should be bounded and stable: %s", ranking.Text)
	}
}

func TestNormalizeMileageStatisticsWindow(t *testing.T) {
	now := time.Date(2026, 7, 14, 12, 0, 0, 0, time.Local)
	defaults, err := normalizeMileageStatisticsWindow(url.Values{}, now)
	if err != nil || defaults.Get("dateFrom") != "2026-06-15" || defaults.Get("dateTo") != "2026-07-14" {
		t.Fatalf("default statistics window = %v, err=%v", defaults, err)
	}
	if _, err := normalizeMileageStatisticsWindow(url.Values{"dateFrom": {"2025-01-01"}, "dateTo": {"2026-07-14"}}, now); err == nil {
		t.Fatal("statistics window over 366 days must be rejected")
	}
	if _, err := normalizeMileageStatisticsWindow(url.Values{"dateFrom": {"2026-07-15"}, "dateTo": {"2026-07-14"}}, now); err == nil {
		t.Fatal("reversed statistics window must be rejected")
	}
}

func TestNormalizeDailyMileageWindowBoundsOnlyDateFilteredQueries(t *testing.T) {
	now := time.Date(2026, 7, 14, 12, 0, 0, 0, time.Local)
	unfiltered, err := normalizeDailyMileageWindow(url.Values{"limit": {"20"}}, now)
	if err != nil || unfiltered.Get("dateFrom") != "" || unfiltered.Get("dateTo") != "" {
		t.Fatalf("unfiltered daily mileage must stay compatible: %v, err=%v", unfiltered, err)
	}
	toOnly, err := normalizeDailyMileageWindow(url.Values{"dateTo": {"2026-07-14"}}, now)
	if err != nil || toOnly.Get("dateFrom") != "2026-06-15" {
		t.Fatalf("dateTo-only daily mileage should receive a bounded default start: %v, err=%v", toOnly, err)
	}
	if _, err := normalizeDailyMileageWindow(url.Values{"dateFrom": {"2025-01-01"}, "dateTo": {"2026-07-14"}}, now); err == nil {
		t.Fatal("date-filtered daily mileage over 366 days must be rejected")
	}
}

func TestNormalizeMileageVINSelection(t *testing.T) {
	normalized, err := normalizeMileageVINSelection(url.Values{"vins": {" VIN001,VIN002,VIN001 "}})
	if err != nil || normalized.Get("vins") != "VIN001,VIN002" {
		t.Fatalf("normalize VIN selection = %q, %v", normalized.Get("vins"), err)
	}
	tooMany := make([]string, 21)
	for index := range tooMany {
		tooMany[index] = fmt.Sprintf("VIN%03d", index)
	}
	if _, err := normalizeMileageVINSelection(url.Values{"vins": {strings.Join(tooMany, ",")}}); err == nil {
		t.Fatal("more than 20 selected vehicles should be rejected")
	}
}

func TestBuildFleetLatestMileageSQLUsesOneLatestValuePerVehicle(t *testing.T) {
	built := buildFleetLatestMileageSQL(url.Values{"vin": {"VIN001"}, "protocol": {"JT808"}})
	for _, want := range []string{"GROUP_CONCAT", "ORDER BY l.updated_at DESC", "GROUP BY l.vin", "SUM(x.latest_mileage_km)"} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("fleet latest mileage SQL missing %q: %s", want, built.Text)
		}
	}
	if len(built.Args) != 5 || built.Args[0] != "%VIN001%" || built.Args[4] != "JT808" {
		t.Fatalf("fleet latest mileage args = %#v", built.Args)
	}
}

func TestBuildRawFrameSQL(t *testing.T) {
	built := buildRawFrameSQL("lingniu_vehicle_ts", RawFrameQuery{
		Protocol: "GB32960",
		VIN:      "VIN001",
		Limit:    1,
	})
	if !strings.Contains(built.Text, "lingniu_vehicle_ts.raw_gb32960_") || !strings.Contains(built.Text, "parsed_fields") {
		t.Fatalf("SQL = %s", built.Text)
	}
	if !strings.Contains(built.CountText, "COUNT(*)") || strings.Contains(built.CountText, "LIMIT") {
		t.Fatalf("count SQL = %s", built.CountText)
	}
	if len(built.Args) != 0 || !strings.Contains(built.Text, "protocol = 'GB32960'") || !strings.Contains(built.Text, "vin = 'VIN001'") || !strings.Contains(built.Text, "ORDER BY ts DESC, frame_id ASC LIMIT 1 OFFSET 0") {
		t.Fatalf("args = %#v", built.Args)
	}
}

func TestBuildRawFrameSQLCanSkipUnneededCount(t *testing.T) {
	built := buildRawFrameSQL("lingniu_vehicle_ts", RawFrameQuery{VIN: "VIN001", IncludeFields: true, Limit: 100, SkipCount: true})
	if built.CountText != "" || !strings.Contains(built.Text, "LIMIT 100") {
		t.Fatalf("bounded latest query should skip count: %+v", built)
	}
}

func TestBuildHistoryLocationSQL(t *testing.T) {
	built := buildHistoryLocationSQL("lingniu_vehicle_ts", map[string]string{
		"protocol": "JT808",
		"vin":      "VIN001",
		"limit":    "2",
		"offset":   "4",
	})
	if !strings.Contains(built.Text, "lingniu_vehicle_ts.loc_jt808_") || !strings.Contains(built.Text, "vin = 'VIN001'") {
		t.Fatalf("SQL = %s", built.Text)
	}
	for _, field := range []string{"soc_percent", "direction_deg", "alarm_flag"} {
		if !strings.Contains(built.Text, field) {
			t.Fatalf("track evidence field %s missing from SQL: %s", field, built.Text)
		}
	}
	if !strings.Contains(built.Text, "CAST(ts AS BIGINT)") || !strings.Contains(built.Text, "CAST(received_at AS BIGINT)") {
		t.Fatalf("history location time must use epoch scans: %s", built.Text)
	}
	if !strings.Contains(built.CountText, "COUNT(*)") || strings.Contains(built.CountText, "LIMIT") {
		t.Fatalf("count SQL = %s", built.CountText)
	}
	if !strings.Contains(built.Text, "ORDER BY ts DESC, vin ASC, protocol ASC LIMIT 2 OFFSET 4") {
		t.Fatalf("SQL should keep stable pagination order: %s", built.Text)
	}
}

func TestBuildHistoryLocationSQLNormalizesDatetimeLocalMinutePrecision(t *testing.T) {
	built := buildHistoryLocationSQL("lingniu_vehicle_ts", map[string]string{
		"vin": "VIN001", "dateFrom": "2026-07-14T00:00", "dateTo": "2026-07-14T05:56", "limit": "10",
	})
	for _, want := range []string{"ts >= '2026-07-14T00:00:00+08:00'", "ts <= '2026-07-14T05:56:00+08:00'"} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("datetime-local minute precision must be normalized for TDengine, missing %q in %s", want, built.Text)
		}
	}
}

func TestTDengineTimeLiteralsAlwaysCarryAnExplicitOffset(t *testing.T) {
	for _, value := range []string{"2026-07-14T08:24", "2026-07-14 08:24:00", "2026-07-14T00:24:00Z"} {
		normalized := normalizeTDengineTime(value)
		if !strings.HasSuffix(normalized, "+08:00") && !strings.HasSuffix(normalized, "Z") {
			t.Fatalf("TDengine time literal lost timezone: input=%s normalized=%s", value, normalized)
		}
	}
}

func TestBuildHistoryLocationSeriesSQLUsesWhitelistedAggregationWindow(t *testing.T) {
	built := buildHistoryLocationSeriesSQL("lingniu_vehicle_ts", HistoryLocationSeriesQuery{VIN: "VIN'001", Protocol: "jt808", DateFrom: "2026-07-14T00:00", DateTo: "2026-07-14T05:56", GrainSeconds: 60})
	for _, want := range []string{"SELECT _wstart, protocol, COUNT(*)", "AVG(speed_kmh)", "LAST(total_mileage_km)", "vin = 'VIN''001'", "protocol = 'JT808'", "ts >= '2026-07-14T00:00:00+08:00'", "PARTITION BY protocol INTERVAL(60s)", "ORDER BY _wstart ASC, protocol ASC"} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("series SQL missing %q: %s", want, built.Text)
		}
	}
	if strings.Contains(built.Text, "FILL") || strings.Contains(built.Text, "GROUP BY") {
		t.Fatalf("series SQL must preserve missing windows and use TDengine window syntax: %s", built.Text)
	}
}

func TestBuildHistoryExportBatchSQLUsesStableForwardCursor(t *testing.T) {
	built := buildHistoryExportBatchSQL("lingniu_vehicle_ts", HistoryExportStoreQuery{Category: "location", VIN: "VIN001", DateFrom: "2026-07-14T00:00", DateTo: "2026-07-14T06:00"}, HistoryExportCursor{Time: "2026-07-14T01:00:00+08:00", Protocol: "GB32960"}, 5000)
	for _, want := range []string{"ts >= '2026-07-14T00:00:00+08:00'", "ts <= '2026-07-14T06:00:00+08:00'", "ts > '2026-07-14T01:00:00+08:00'", "protocol > 'GB32960'", "ORDER BY ts ASC, protocol ASC LIMIT 5000"} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("location export SQL missing %q: %s", want, built.Text)
		}
	}
	if strings.Contains(built.Text, "OFFSET") {
		t.Fatalf("million-row export must not use OFFSET: %s", built.Text)
	}

	raw := buildHistoryExportBatchSQL("lingniu_vehicle_ts", HistoryExportStoreQuery{Category: "raw", VIN: "VIN001"}, HistoryExportCursor{Time: "2026-07-14T01:00:00+08:00", Protocol: "JT808", ID: "frame-9"}, 5000)
	for _, want := range []string{"frame_id > 'frame-9'", "ORDER BY ts ASC, protocol ASC, frame_id ASC", "parsed_json"} {
		if !strings.Contains(raw.Text, want) {
			t.Fatalf("raw export SQL missing %q: %s", want, raw.Text)
		}
	}
}

func TestBuildTodayRawFrameCountSQL(t *testing.T) {
	now := time.Date(2026, 7, 3, 22, 30, 0, 0, time.FixedZone("Asia/Shanghai", 8*3600))
	built := buildTodayRawFrameCountSQL("lingniu_vehicle_ts", now)
	for _, want := range []string{"SELECT COUNT(*)", "lingniu_vehicle_ts.raw_frames", "ts >= '2026-07-03T00:00:00+08:00'"} {
		if !strings.Contains(built.Text, want) {
			t.Fatalf("SQL missing %q: %s", want, built.Text)
		}
	}
}
