package platform

import (
	"encoding/json"
	"net/url"
	"sort"
	"strconv"
	"strings"
)

type SQLQuery struct {
	Text      string
	Args      []any
	CountText string
	CountArgs []any
}

const maxSQLPageSize = 10_000

func buildVehicleListSQL(query url.Values) SQLQuery {
	limit := parseSQLPageSize(query.Get("limit"), 20)
	offset := parsePositive(query.Get("offset"), 0)
	canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
	args := []any{}
	where := []string{"1 = 1"}
	where, args = appendVINListFilter(where, args, "s.vin", query.Get("scopeVins"))
	if keyword := strings.TrimSpace(query.Get("keyword")); keyword != "" {
		where = append(where, "(b.vin LIKE ? OR b.plate LIKE ? OR b.phone LIKE ? OR b.oem LIKE ?)")
		like := "%" + keyword + "%"
		args = append(args, like, like, like, like)
	}
	if protocol := strings.TrimSpace(query.Get("protocol")); protocol != "" {
		where = append(where, "s.protocol = ?")
		args = append(args, protocol)
	}
	switch strings.TrimSpace(query.Get("serviceStatus")) {
	case "identity_required":
		where = append(where, "(b.vin IS NULL OR b.vin = '')")
	case "offline":
		where = append(where, "b.vin IS NOT NULL", "b.vin <> ''", "COALESCE(vs.source_count, 0) > 0", "COALESCE(vs.online_source_count, 0) = 0")
	case "degraded":
		where = append(where, "b.vin IS NOT NULL", "b.vin <> ''", "vs.source_count > 0", "vs.online_source_count > 0", "(vs.online_source_count < vs.source_count OR vs.source_count < "+canonicalSourceCount+")")
	case "healthy":
		where = append(where, "b.vin IS NOT NULL", "b.vin <> ''", "vs.source_count = "+canonicalSourceCount, "vs.online_source_count = vs.source_count")
	}
	countArgs := append([]any(nil), args...)
	args = append(args, limit, offset)
	fromSQL := `FROM vehicle_realtime_snapshot s ` +
		`LEFT JOIN vehicle_identity_binding b ON b.vin = s.vin ` +
		`LEFT JOIN vehicle_realtime_location l ON l.vin = s.vin AND l.protocol = s.protocol ` +
		`LEFT JOIN (` +
		`SELECT vin, COUNT(DISTINCT protocol) AS source_count, ` +
		`COUNT(DISTINCT CASE WHEN updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN protocol END) AS online_source_count ` +
		`FROM vehicle_realtime_snapshot WHERE vin IS NOT NULL AND vin <> '' GROUP BY vin` +
		`) vs ON vs.vin = s.vin ` +
		`WHERE ` + strings.Join(where, " AND ")
	return SQLQuery{
		Text: `SELECT s.vin, COALESCE(NULLIF(s.plate, ''), b.plate, '') AS plate, COALESCE(b.phone, '') AS phone, COALESCE(b.oem, '') AS oem, s.protocol, ` +
			`CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN 1 ELSE 0 END AS online, ` +
			`COALESCE(DATE_FORMAT(s.updated_at, '%Y-%m-%d %H:%i:%s'), '') AS last_seen, ` +
			`COALESCE(CONCAT(l.longitude, ',', l.latitude), '') AS location_text, ` +
			`CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 100 ELSE 60 END AS binding_score, ` +
			`COALESCE(vs.source_count, 0) AS source_count, COALESCE(vs.online_source_count, 0) AS online_source_count, ` +
			`CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 'bound' ELSE 'unbound' END AS binding_status ` +
			fromSQL + ` ORDER BY s.updated_at DESC, s.vin ASC, s.protocol ASC LIMIT ? OFFSET ?`,
		Args:      args,
		CountText: `SELECT COUNT(*) ` + fromSQL,
		CountArgs: countArgs,
	}
}

func buildVehicleCoverageSQL(query url.Values) SQLQuery {
	limit := parseSQLPageSize(query.Get("limit"), 20)
	offset := parsePositive(query.Get("offset"), 0)
	canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
	args := []any{}
	where := []string{"v.vin IS NOT NULL", "v.vin <> ''"}
	where, args = appendVINListFilter(where, args, "v.vin", query.Get("scopeVins"))
	having := []string{}
	if keyword := strings.TrimSpace(query.Get("keyword")); keyword != "" {
		where = append(where, "(v.vin LIKE ? OR s.plate LIKE ? OR b.vin LIKE ? OR b.plate LIKE ? OR b.phone LIKE ? OR b.oem LIKE ?)")
		like := "%" + keyword + "%"
		args = append(args, like, like, like, like, like, like)
	}
	if protocol := strings.TrimSpace(query.Get("protocol")); protocol != "" {
		where = append(where, "s.protocol = ?")
		args = append(args, protocol)
	}
	switch strings.TrimSpace(query.Get("coverage")) {
	case "single":
		having = append(having, "COUNT(DISTINCT s.protocol) = 1")
	case "multi":
		having = append(having, "COUNT(DISTINCT s.protocol) > 1")
	}
	if missingProtocol := strings.TrimSpace(query.Get("missingProtocol")); missingProtocol != "" {
		having = append(having, "COUNT(DISTINCT CASE WHEN s.protocol = ? THEN s.protocol END) = 0")
		args = append(args, missingProtocol)
	}
	switch strings.TrimSpace(query.Get("online")) {
	case "online":
		having = append(having, "COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) > 0")
	case "offline":
		having = append(having, "COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) = 0")
	}
	switch strings.TrimSpace(query.Get("bindingStatus")) {
	case "bound":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 1")
	case "unbound":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 0")
	}
	switch strings.TrimSpace(query.Get("archiveStatus")) {
	case "complete":
		having = append(having, "v.vin IS NOT NULL AND v.vin <> ''")
		having = append(having, "COALESCE(NULLIF(MAX(NULLIF(s.plate, '')), ''), NULLIF(MAX(NULLIF(b.plate, '')), '')) IS NOT NULL")
		having = append(having, "NULLIF(MAX(NULLIF(b.phone, '')), '') IS NOT NULL")
		having = append(having, "NULLIF(MAX(NULLIF(b.oem, '')), '') IS NOT NULL")
	case "incomplete":
		having = append(having, "(v.vin IS NULL OR v.vin = '' OR COALESCE(NULLIF(MAX(NULLIF(s.plate, '')), ''), NULLIF(MAX(NULLIF(b.plate, '')), '')) IS NULL OR NULLIF(MAX(NULLIF(b.phone, '')), '') IS NULL OR NULLIF(MAX(NULLIF(b.oem, '')), '') IS NULL)")
	}
	if predicate := archiveMissingFieldPredicate(query.Get("archiveMissing")); predicate != "" {
		having = append(having, predicate)
	}
	switch strings.TrimSpace(query.Get("serviceStatus")) {
	case "identity_required":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 0")
	case "no_data":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 1")
		having = append(having, "COUNT(DISTINCT s.protocol) = 0")
	case "offline":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 1")
		having = append(having, "COUNT(DISTINCT s.protocol) > 0")
		having = append(having, "COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) = 0")
	case "degraded":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 1")
		having = append(having, "COUNT(DISTINCT s.protocol) > 0")
		having = append(having, "COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) > 0")
		having = append(having, "(COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) < COUNT(DISTINCT s.protocol) OR COUNT(DISTINCT s.protocol) < "+canonicalSourceCount+")")
	case "healthy":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 1")
		having = append(having, "COUNT(DISTINCT s.protocol) = "+canonicalSourceCount)
		having = append(having, "COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) = COUNT(DISTINCT s.protocol)")
	}
	countArgs := append([]any(nil), args...)
	args = append(args, limit, offset)
	havingSQL := ""
	if len(having) > 0 {
		havingSQL = ` HAVING ` + strings.Join(having, " AND ") + ` `
	}
	vehicleSetSQL := `SELECT vin FROM vehicle_identity_binding WHERE vin IS NOT NULL AND vin <> '' ` +
		`UNION SELECT vin FROM vehicle_realtime_snapshot WHERE vin IS NOT NULL AND vin <> ''`
	groupSQL := `FROM (` + vehicleSetSQL + `) v ` +
		`LEFT JOIN vehicle_identity_binding b ON b.vin = v.vin ` +
		`LEFT JOIN vehicle_realtime_snapshot s ON s.vin = v.vin ` +
		`WHERE ` + strings.Join(where, " AND ") + ` ` +
		`GROUP BY v.vin, b.plate, b.phone, b.oem, b.vin ` +
		havingSQL
	return SQLQuery{
		Text: `SELECT v.vin, ` +
			`COALESCE(NULLIF(MAX(NULLIF(s.plate, '')), ''), b.plate, '') AS plate, ` +
			`COALESCE(b.phone, '') AS phone, COALESCE(b.oem, '') AS oem, ` +
			`COALESCE(GROUP_CONCAT(DISTINCT s.protocol ORDER BY s.protocol SEPARATOR ','), '') AS protocols, ` +
			`COALESCE(GROUP_CONCAT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END ORDER BY s.protocol SEPARATOR ','), '') AS online_protocols, ` +
			`COUNT(DISTINCT s.protocol) AS source_count, ` +
			`COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) AS online_source_count, ` +
			`CASE WHEN COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) > 0 THEN 1 ELSE 0 END AS online, ` +
			`COALESCE(DATE_FORMAT(MAX(s.updated_at), '%Y-%m-%d %H:%i:%s'), '') AS last_seen, ` +
			`CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 'bound' ELSE 'unbound' END AS binding_status ` +
			groupSQL +
			`ORDER BY MAX(s.updated_at) DESC, v.vin ASC LIMIT ? OFFSET ?`,
		Args:      args,
		CountText: `SELECT COUNT(*) FROM (SELECT v.vin ` + groupSQL + `) vehicle_coverage_count`,
		CountArgs: countArgs,
	}
}

func buildVehicleCoverageSummarySQL(query url.Values) SQLQuery {
	canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
	args := []any{}
	where := []string{"v.vin IS NOT NULL", "v.vin <> ''"}
	where, args = appendVINListFilter(where, args, "v.vin", query.Get("scopeVins"))
	having := []string{}
	if keyword := strings.TrimSpace(query.Get("keyword")); keyword != "" {
		where = append(where, "(v.vin LIKE ? OR s.plate LIKE ? OR b.vin LIKE ? OR b.plate LIKE ? OR b.phone LIKE ? OR b.oem LIKE ?)")
		like := "%" + keyword + "%"
		args = append(args, like, like, like, like, like, like)
	}
	if protocol := strings.TrimSpace(query.Get("protocol")); protocol != "" {
		where = append(where, "s.protocol = ?")
		args = append(args, protocol)
	}
	switch strings.TrimSpace(query.Get("coverage")) {
	case "single":
		having = append(having, "COUNT(DISTINCT s.protocol) = 1")
	case "multi":
		having = append(having, "COUNT(DISTINCT s.protocol) > 1")
	}
	if missingProtocol := strings.TrimSpace(query.Get("missingProtocol")); missingProtocol != "" {
		having = append(having, "COUNT(DISTINCT CASE WHEN s.protocol = ? THEN s.protocol END) = 0")
		args = append(args, missingProtocol)
	}
	switch strings.TrimSpace(query.Get("online")) {
	case "online":
		having = append(having, "COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) > 0")
	case "offline":
		having = append(having, "COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) = 0")
	}
	switch strings.TrimSpace(query.Get("bindingStatus")) {
	case "bound":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 1")
	case "unbound":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 0")
	}
	switch strings.TrimSpace(query.Get("archiveStatus")) {
	case "complete":
		having = append(having, "v.vin IS NOT NULL AND v.vin <> ''")
		having = append(having, "COALESCE(NULLIF(MAX(NULLIF(s.plate, '')), ''), NULLIF(MAX(NULLIF(b.plate, '')), '')) IS NOT NULL")
		having = append(having, "NULLIF(MAX(NULLIF(b.phone, '')), '') IS NOT NULL")
		having = append(having, "NULLIF(MAX(NULLIF(b.oem, '')), '') IS NOT NULL")
	case "incomplete":
		having = append(having, "(v.vin IS NULL OR v.vin = '' OR COALESCE(NULLIF(MAX(NULLIF(s.plate, '')), ''), NULLIF(MAX(NULLIF(b.plate, '')), '')) IS NULL OR NULLIF(MAX(NULLIF(b.phone, '')), '') IS NULL OR NULLIF(MAX(NULLIF(b.oem, '')), '') IS NULL)")
	}
	if predicate := archiveMissingFieldPredicate(query.Get("archiveMissing")); predicate != "" {
		having = append(having, predicate)
	}
	switch strings.TrimSpace(query.Get("serviceStatus")) {
	case "identity_required":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 0")
	case "no_data":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 1")
		having = append(having, "COUNT(DISTINCT s.protocol) = 0")
	case "offline":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 1")
		having = append(having, "COUNT(DISTINCT s.protocol) > 0")
		having = append(having, "COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) = 0")
	case "degraded":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 1")
		having = append(having, "COUNT(DISTINCT s.protocol) > 0")
		having = append(having, "COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) > 0")
		having = append(having, "(COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) < COUNT(DISTINCT s.protocol) OR COUNT(DISTINCT s.protocol) < "+canonicalSourceCount+")")
	case "healthy":
		having = append(having, "MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) = 1")
		having = append(having, "COUNT(DISTINCT s.protocol) = "+canonicalSourceCount)
		having = append(having, "COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) = COUNT(DISTINCT s.protocol)")
	}
	havingSQL := ""
	if len(having) > 0 {
		havingSQL = ` HAVING ` + strings.Join(having, " AND ") + ` `
	}
	vehicleSetSQL := `SELECT vin FROM vehicle_identity_binding WHERE vin IS NOT NULL AND vin <> '' ` +
		`UNION SELECT vin FROM vehicle_realtime_snapshot WHERE vin IS NOT NULL AND vin <> ''`
	groupSQL := `SELECT v.vin, ` +
		`COUNT(DISTINCT s.protocol) AS source_count, ` +
		`COUNT(DISTINCT CASE WHEN s.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN s.protocol END) AS online_source_count, ` +
		`MAX(CASE WHEN b.vin IS NOT NULL AND b.vin <> '' THEN 1 ELSE 0 END) AS bound, ` +
		`CASE WHEN v.vin IS NOT NULL AND v.vin <> '' ` +
		`AND COALESCE(NULLIF(MAX(NULLIF(s.plate, '')), ''), NULLIF(MAX(NULLIF(b.plate, '')), '')) IS NOT NULL ` +
		`AND NULLIF(MAX(NULLIF(b.phone, '')), '') IS NOT NULL ` +
		`AND NULLIF(MAX(NULLIF(b.oem, '')), '') IS NOT NULL ` +
		`THEN 1 ELSE 0 END AS archive_complete, ` +
		`CASE WHEN COALESCE(NULLIF(MAX(NULLIF(s.plate, '')), ''), NULLIF(MAX(NULLIF(b.plate, '')), '')) IS NULL THEN 1 ELSE 0 END AS missing_plate, ` +
		`CASE WHEN NULLIF(MAX(NULLIF(b.phone, '')), '') IS NULL THEN 1 ELSE 0 END AS missing_phone, ` +
		`CASE WHEN NULLIF(MAX(NULLIF(b.oem, '')), '') IS NULL THEN 1 ELSE 0 END AS missing_oem ` +
		`FROM (` + vehicleSetSQL + `) v ` +
		`LEFT JOIN vehicle_identity_binding b ON b.vin = v.vin ` +
		`LEFT JOIN vehicle_realtime_snapshot s ON s.vin = v.vin ` +
		`WHERE ` + strings.Join(where, " AND ") + ` ` +
		`GROUP BY v.vin ` + havingSQL
	return SQLQuery{
		Text: `SELECT COUNT(*) AS total_vehicles, ` +
			`COALESCE(SUM(CASE WHEN online_source_count > 0 THEN 1 ELSE 0 END), 0) AS online_vehicles, ` +
			`COALESCE(SUM(CASE WHEN source_count = 1 THEN 1 ELSE 0 END), 0) AS single_source_vehicles, ` +
			`COALESCE(SUM(CASE WHEN source_count > 1 THEN 1 ELSE 0 END), 0) AS multi_source_vehicles, ` +
			`COALESCE(SUM(CASE WHEN source_count = 0 THEN 1 ELSE 0 END), 0) AS no_data_vehicles, ` +
			`COALESCE(SUM(CASE WHEN bound = 0 THEN 1 ELSE 0 END), 0) AS unbound_vehicles, ` +
			`COALESCE(SUM(CASE WHEN archive_complete = 0 THEN 1 ELSE 0 END), 0) AS archive_incomplete_vehicles, ` +
			`COALESCE(SUM(CASE WHEN missing_plate = 1 THEN 1 ELSE 0 END), 0) AS missing_plate_vehicles, ` +
			`COALESCE(SUM(CASE WHEN missing_phone = 1 THEN 1 ELSE 0 END), 0) AS missing_phone_vehicles, ` +
			`COALESCE(SUM(CASE WHEN missing_oem = 1 THEN 1 ELSE 0 END), 0) AS missing_oem_vehicles ` +
			`FROM (` + groupSQL + `) vehicle_coverage_summary`,
		Args: args,
	}
}

func archiveMissingFieldPredicate(field string) string {
	switch strings.TrimSpace(field) {
	case "plate":
		return "COALESCE(NULLIF(MAX(NULLIF(s.plate, '')), ''), NULLIF(MAX(NULLIF(b.plate, '')), '')) IS NULL"
	case "phone":
		return "NULLIF(MAX(NULLIF(b.phone, '')), '') IS NULL"
	case "oem":
		return "NULLIF(MAX(NULLIF(b.oem, '')), '') IS NULL"
	default:
		return ""
	}
}

func buildRealtimeLocationSQL(query url.Values) SQLQuery {
	limit := parseSQLPageSize(query.Get("limit"), 20)
	offset := parsePositive(query.Get("offset"), 0)
	args := []any{}
	where := []string{"1 = 1"}
	where, args = appendVINListFilter(where, args, "l.vin", query.Get("scopeVins"))
	if protocol := strings.TrimSpace(query.Get("protocol")); protocol != "" {
		where = append(where, "l.protocol = ?")
		args = append(args, protocol)
	}
	if vin := strings.TrimSpace(query.Get("vin")); vin != "" {
		where = append(where, "(l.vin LIKE ? OR l.plate LIKE ? OR b.plate LIKE ?)")
		like := "%" + vin + "%"
		args = append(args, like, like, like)
	}
	countArgs := append([]any(nil), args...)
	args = append(args, limit, offset)
	fromSQL := `FROM vehicle_realtime_location l LEFT JOIN vehicle_identity_binding b ON b.vin = l.vin WHERE ` + strings.Join(where, " AND ")
	return SQLQuery{
		Text: `SELECT l.vin, COALESCE(NULLIF(l.plate, ''), b.plate, '') AS plate, l.protocol, l.longitude, l.latitude, ` +
			`COALESCE(l.speed_kmh, 0), COALESCE(l.soc_percent, 0), COALESCE(l.total_mileage_km, 0), ` +
			`COALESCE(DATE_FORMAT(l.updated_at, '%Y-%m-%d %H:%i:%s'), '') ` +
			fromSQL + ` ORDER BY l.updated_at DESC, l.vin ASC, l.protocol ASC LIMIT ? OFFSET ?`,
		Args:      args,
		CountText: `SELECT COUNT(*) ` + fromSQL,
		CountArgs: countArgs,
	}
}

func buildVehicleRealtimeSQL(query url.Values) SQLQuery {
	limit := parseSQLPageSize(query.Get("limit"), 20)
	offset := parsePositive(query.Get("offset"), 0)
	canonicalSourceCount := strconv.Itoa(len(canonicalVehicleProtocols))
	args := []any{}
	where := []string{"v.vin IS NOT NULL", "v.vin <> ''"}
	where, args = appendVINListFilter(where, args, "v.vin", query.Get("scopeVins"))
	having := []string{}
	if protocol := strings.TrimSpace(query.Get("protocol")); protocol != "" {
		where = append(where, "l.protocol = ?")
		args = append(args, protocol)
	}
	if keywords := vehicleSearchKeywords(query); len(keywords) > 0 {
		if strings.TrimSpace(query.Get("keywords")) != "" {
			placeholders := strings.TrimSuffix(strings.Repeat("?,", len(keywords)), ",")
			where = append(where, "(v.vin IN ("+placeholders+") OR l.plate IN ("+placeholders+") OR v.plate IN ("+placeholders+"))")
			for range 3 {
				for _, keyword := range keywords {
					args = append(args, keyword)
				}
			}
		} else {
			keyword := keywords[0]
			like := "%" + keyword + "%"
			where = append(where, "(v.vin LIKE ? OR l.plate LIKE ? OR v.plate LIKE ? OR v.phone LIKE ? OR v.oem LIKE ?)")
			args = append(args, like, like, like, like, like)
		}
	}
	switch strings.TrimSpace(query.Get("online")) {
	case "online":
		having = append(having, "COUNT(DISTINCT CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN l.protocol END) > 0")
	case "offline":
		having = append(having,
			"COUNT(DISTINCT l.protocol) > 0",
			"COUNT(DISTINCT CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN l.protocol END) = 0",
		)
	}
	orderExpr := `CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 10 MINUTE) THEN 0 ELSE 1 END ASC, ` +
		`CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 10 MINUTE) THEN CASE l.protocol ` +
		`WHEN 'GB32960' THEN 10 WHEN 'YUTONG_MQTT' THEN 20 WHEN 'JT808' THEN 30 ELSE 100 END ELSE 100 END ASC, ` +
		`l.updated_at DESC, l.protocol ASC`
	primarySpeed := "CAST(SUBSTRING_INDEX(GROUP_CONCAT(CAST(l.speed_kmh AS CHAR) ORDER BY " + orderExpr + "), ',', 1) AS DECIMAL(18,6))"
	primaryLocationAvailable := `COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(` +
		`CASE WHEN l.vin IS NOT NULL AND l.longitude BETWEEN -180 AND 180 AND l.latitude BETWEEN -90 AND 90 ` +
		`AND NOT (l.longitude = 0 AND l.latitude = 0) THEN 1 ELSE 0 END ORDER BY ` + orderExpr + `), ',', 1) AS UNSIGNED), 0)`
	switch strings.TrimSpace(query.Get("status")) {
	case "driving":
		having = append(having,
			"COUNT(DISTINCT CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN l.protocol END) > 0",
			primarySpeed+" > 3",
		)
	case "idle":
		having = append(having,
			"COUNT(DISTINCT CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN l.protocol END) > 0",
			primarySpeed+" <= 3",
		)
	case "no_location":
		having = append(having, primaryLocationAvailable+" = 0")
	}
	switch strings.TrimSpace(query.Get("serviceStatus")) {
	case "healthy":
		having = append(having,
			"MAX(CASE WHEN v.binding_status = 'bound' THEN 1 ELSE 0 END) = 1",
			"COUNT(DISTINCT l.protocol) = "+canonicalSourceCount,
			"COUNT(DISTINCT CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN l.protocol END) = COUNT(DISTINCT l.protocol)",
		)
	case "degraded":
		having = append(having,
			"MAX(CASE WHEN v.binding_status = 'bound' THEN 1 ELSE 0 END) = 1",
			"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) OR COUNT(DISTINCT l.protocol) < "+canonicalSourceCount+")",
		)
	case "offline":
		having = append(having,
			"MAX(CASE WHEN v.binding_status = 'bound' THEN 1 ELSE 0 END) = 1",
			"COUNT(DISTINCT l.protocol) > 0",
			"COUNT(DISTINCT CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN l.protocol END) = 0",
		)
	case "identity_required":
		having = append(having, "MAX(CASE WHEN v.binding_status = 'bound' THEN 1 ELSE 0 END) = 0")
	}
	countArgs := append([]any(nil), args...)
	args = append(args, limit, offset)
	havingSQL := ""
	if len(having) > 0 {
		havingSQL = ` HAVING ` + strings.Join(having, " AND ") + ` `
	}
	populationRowsSQL := `SELECT b0.vin, COALESCE(MAX(NULLIF(b0.plate, '')), '') AS plate, ` +
		`COALESCE(MAX(NULLIF(b0.phone, '')), '') AS phone, COALESCE(MAX(NULLIF(b0.oem, '')), '') AS oem, 'bound' AS binding_status ` +
		`FROM vehicle_identity_binding b0 WHERE b0.vin IS NOT NULL AND b0.vin <> '' GROUP BY b0.vin`
	if strings.TrimSpace(query.Get("serviceStatus")) == "identity_required" {
		populationRowsSQL += ` UNION ALL ` +
			`SELECT l0.vin, COALESCE(MAX(NULLIF(l0.plate, '')), '') AS plate, '' AS phone, '' AS oem, 'unbound' AS binding_status ` +
			`FROM vehicle_realtime_location l0 LEFT JOIN vehicle_identity_binding b1 ON b1.vin = l0.vin ` +
			`WHERE l0.vin IS NOT NULL AND l0.vin <> '' AND b1.vin IS NULL GROUP BY l0.vin`
	}
	populationSQL := `(` + populationRowsSQL + `) v`
	baseGroupSQL := `FROM ` + populationSQL + ` ` +
		`LEFT JOIN vehicle_realtime_location l ON l.vin = v.vin ` +
		`LEFT JOIN vehicle_realtime_snapshot s ON s.vin = v.vin AND s.protocol = l.protocol `
	groupSuffixSQL := `WHERE ` + strings.Join(where, " AND ") + ` ` +
		`GROUP BY v.vin, v.plate, v.phone, v.oem, v.binding_status ` +
		havingSQL
	groupSQL := baseGroupSQL +
		`LEFT JOIN (` +
		`SELECT vin, protocol, MAX(daily_mileage_km) AS daily_mileage_km FROM vehicle_daily_mileage ` +
		`WHERE stat_date = CURDATE() GROUP BY vin, protocol` +
		`) m ON m.vin = v.vin AND m.protocol = l.protocol ` +
		groupSuffixSQL
	// Keep one authoritative coordinate source while all protocols are healthy.
	// Ordering only by updated_at makes the selected point flap whenever protocols
	// report at different cadences. When every source is stale, recency remains the
	// safest fallback so an old high-priority source cannot mask newer evidence.
	return SQLQuery{
		Text: `SELECT v.vin, ` +
			`COALESCE(NULLIF(MAX(NULLIF(l.plate, '')), ''), v.plate, '') AS plate, ` +
			`COALESCE(v.phone, '') AS phone, COALESCE(v.oem, '') AS oem, ` +
			`COALESCE(GROUP_CONCAT(DISTINCT l.protocol ORDER BY l.protocol SEPARATOR ','), '') AS protocols, ` +
			`COALESCE(GROUP_CONCAT(DISTINCT CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN l.protocol END ORDER BY l.protocol SEPARATOR ','), '') AS online_protocols, ` +
			`COUNT(DISTINCT l.protocol) AS source_count, ` +
			`COUNT(DISTINCT CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN l.protocol END) AS online_source_count, ` +
			`CASE WHEN COUNT(DISTINCT CASE WHEN l.updated_at >= DATE_SUB(NOW(), INTERVAL 1 MINUTE) THEN l.protocol END) > 0 THEN 1 ELSE 0 END AS online, ` +
			`v.binding_status, ` +
			`COALESCE(SUBSTRING_INDEX(GROUP_CONCAT(l.protocol ORDER BY ` + orderExpr + `), ',', 1), '') AS primary_protocol, ` +
			`COALESCE(SUBSTRING_INDEX(GROUP_CONCAT(COALESCE(NULLIF(l.source_key, ''), CONCAT(l.protocol, ':canonical')) ORDER BY ` + orderExpr + `), ',', 1), '') AS location_source, ` +
			`COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(COALESCE(l.location_conflict, 0) ORDER BY ` + orderExpr + `), ',', 1) AS UNSIGNED), 0) AS location_conflict, ` +
			`SUBSTRING_INDEX(GROUP_CONCAT(COALESCE(CAST(l.location_conflict_distance_m AS CHAR), '-1') ORDER BY ` + orderExpr + `), ',', 1) AS conflict_distance_m, ` +
			primaryLocationAvailable + ` AS location_available, ` +
			`COALESCE(SUBSTRING_INDEX(GROUP_CONCAT(CAST(l.longitude AS CHAR) ORDER BY ` + orderExpr + `), ',', 1), '') AS longitude, ` +
			`COALESCE(SUBSTRING_INDEX(GROUP_CONCAT(CAST(l.latitude AS CHAR) ORDER BY ` + orderExpr + `), ',', 1), '') AS latitude, ` +
			`COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(CASE WHEN l.speed_kmh IS NOT NULL THEN 1 ELSE 0 END ORDER BY ` + orderExpr + `), ',', 1) AS UNSIGNED), 0) AS speed_available, ` +
			`COALESCE(SUBSTRING_INDEX(GROUP_CONCAT(CAST(l.speed_kmh AS CHAR) ORDER BY ` + orderExpr + `), ',', 1), '') AS speed_kmh, ` +
			`COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(CASE WHEN l.protocol IN ('GB32960','YUTONG_MQTT') AND l.soc_percent IS NOT NULL THEN 1 ELSE 0 END ORDER BY ` + orderExpr + `), ',', 1) AS UNSIGNED), 0) AS soc_available, ` +
			`COALESCE(SUBSTRING_INDEX(GROUP_CONCAT(CAST(l.soc_percent AS CHAR) ORDER BY ` + orderExpr + `), ',', 1), '') AS soc_percent, ` +
			`COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(CASE WHEN l.total_mileage_km IS NOT NULL THEN 1 ELSE 0 END ORDER BY ` + orderExpr + `), ',', 1) AS UNSIGNED), 0) AS mileage_available, ` +
			`COALESCE(SUBSTRING_INDEX(GROUP_CONCAT(CAST(l.total_mileage_km AS CHAR) ORDER BY ` + orderExpr + `), ',', 1), '') AS total_mileage_km, ` +
			`COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(CASE WHEN m.daily_mileage_km IS NOT NULL THEN 1 ELSE 0 END ORDER BY ` + orderExpr + `), ',', 1) AS UNSIGNED), 0) AS today_mileage_available, ` +
			`COALESCE(SUBSTRING_INDEX(GROUP_CONCAT(CAST(m.daily_mileage_km AS CHAR) ORDER BY ` + orderExpr + `), ',', 1), '') AS today_mileage_km, ` +
			`COALESCE(DATE_FORMAT(MAX(l.updated_at), '%Y-%m-%d %H:%i:%s'), '') AS last_seen, ` +
			`CAST(SUBSTRING_INDEX(GROUP_CONCAT(COALESCE(s.access_report_interval_ms, -1) ORDER BY ` + orderExpr + `), ',', 1) AS SIGNED) AS report_interval_ms ` +
			groupSQL +
			`ORDER BY MAX(l.updated_at) IS NULL ASC, MAX(l.updated_at) DESC, v.vin ASC LIMIT ? OFFSET ?`,
		Args:      args,
		CountText: `SELECT COUNT(*) FROM (SELECT v.vin ` + baseGroupSQL + groupSuffixSQL + `) vehicle_realtime_count`,
		CountArgs: countArgs,
	}
}

func buildDailyMileageSQL(query url.Values) SQLQuery {
	limit := parseSQLPageSize(query.Get("limit"), 20)
	offset := parsePositive(query.Get("offset"), 0)
	args := []any{}
	where := []string{"1 = 1"}
	if strings.EqualFold(strings.TrimSpace(query.Get("vehicleScope")), "bound") {
		where = append(where, "b.vin IS NOT NULL", "b.vin <> ''")
	}
	if vin := strings.TrimSpace(query.Get("vin")); vin != "" {
		where = append(where, "(m.vin LIKE ? OR b.plate LIKE ? OR b.phone LIKE ? OR b.oem LIKE ?)")
		like := "%" + vin + "%"
		args = append(args, like, like, like, like)
	}
	where, args = appendVINListFilter(where, args, "m.vin", query.Get("vins"))
	where, args = appendVINListFilter(where, args, "m.vin", query.Get("scopeVins"))
	where, args = appendVehicleDateScope(where, args, "m.vin", "m.stat_date", query.Get("scopeVehicleFrom"))
	protocols := parseMileageProtocols(query.Get("protocols"))
	if len(protocols) > 0 {
		where, args = appendMileageProtocolFilter(where, args, "m.protocol", protocols)
	} else if protocol := strings.TrimSpace(query.Get("protocol")); protocol != "" {
		where = append(where, "m.protocol = ?")
		args = append(args, protocol)
	}
	if dateFrom := strings.TrimSpace(query.Get("dateFrom")); dateFrom != "" {
		where = append(where, "m.stat_date >= ?")
		args = append(args, dateFrom)
	}
	if dateTo := strings.TrimSpace(query.Get("dateTo")); dateTo != "" {
		where = append(where, "m.stat_date <= ?")
		args = append(args, dateTo)
	}
	countArgs := append([]any(nil), args...)
	args = append(args, limit, offset)
	fromSQL := `FROM vehicle_daily_mileage m LEFT JOIN vehicle_identity_binding b ON b.vin = m.vin WHERE ` + strings.Join(where, " AND ")
	if query.Get("deduplicate") == "1" || strings.EqualFold(query.Get("deduplicate"), "true") {
		selectionOrder := `m.daily_mileage_km DESC, m.protocol ASC`
		dailyMileageExpression := `MAX(COALESCE(m.daily_mileage_km, 0))`
		if len(protocols) > 0 {
			selectionOrder = mileageProtocolOrder("m.protocol", protocols)
			dailyMileageExpression = `COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(CAST(COALESCE(m.daily_mileage_km, 0) AS CHAR) ORDER BY ` + selectionOrder + `), ',', 1) AS DECIMAL(18,3)), 0)`
		}
		groupSQL := fromSQL + ` GROUP BY m.vin, m.stat_date`
		return SQLQuery{
			Text: `SELECT m.vin, COALESCE(MAX(NULLIF(b.plate, '')), '') AS plate, DATE_FORMAT(m.stat_date, '%Y-%m-%d') AS stat_date, ` +
				`COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(CAST(m.latest_total_mileage_km - m.daily_mileage_km AS CHAR) ORDER BY ` + selectionOrder + `), ',', 1) AS DECIMAL(18,3)), 0) AS start_mileage_km, ` +
				`COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(CAST(m.latest_total_mileage_km AS CHAR) ORDER BY ` + selectionOrder + `), ',', 1) AS DECIMAL(18,3)), 0) AS end_mileage_km, ` +
				dailyMileageExpression + ` AS daily_mileage_km, ` +
				`COALESCE(SUBSTRING_INDEX(GROUP_CONCAT(m.protocol ORDER BY ` + selectionOrder + `), ',', 1), '') AS protocol ` +
				groupSQL + ` ORDER BY m.stat_date DESC, m.vin ASC LIMIT ? OFFSET ?`,
			Args:      args,
			CountText: `SELECT COUNT(*) FROM (SELECT m.vin ` + groupSQL + `) vehicle_daily_mileage_count`,
			CountArgs: countArgs,
		}
	}
	return SQLQuery{
		Text: `SELECT m.vin, COALESCE(b.plate, '') AS plate, DATE_FORMAT(m.stat_date, '%Y-%m-%d') AS stat_date, ` +
			`COALESCE(m.latest_total_mileage_km - m.daily_mileage_km, 0) AS start_mileage_km, ` +
			`COALESCE(m.latest_total_mileage_km, 0) AS end_mileage_km, m.daily_mileage_km, m.protocol ` +
			fromSQL + ` ORDER BY m.stat_date DESC, m.vin ASC, m.protocol ASC LIMIT ? OFFSET ?`,
		Args:      args,
		CountText: `SELECT COUNT(*) ` + fromSQL,
		CountArgs: countArgs,
	}
}

func buildMileageSummarySQL(query url.Values) SQLQuery {
	args := []any{}
	where := []string{"1 = 1"}
	if strings.EqualFold(strings.TrimSpace(query.Get("vehicleScope")), "bound") {
		where = append(where, "b.vin IS NOT NULL", "b.vin <> ''")
	}
	if vin := strings.TrimSpace(query.Get("vin")); vin != "" {
		where = append(where, "(m.vin LIKE ? OR b.plate LIKE ? OR b.phone LIKE ? OR b.oem LIKE ?)")
		like := "%" + vin + "%"
		args = append(args, like, like, like, like)
	}
	where, args = appendVINListFilter(where, args, "m.vin", query.Get("vins"))
	where, args = appendVINListFilter(where, args, "m.vin", query.Get("scopeVins"))
	where, args = appendVehicleDateScope(where, args, "m.vin", "m.stat_date", query.Get("scopeVehicleFrom"))
	if protocols := parseMileageProtocols(query.Get("protocols")); len(protocols) > 0 {
		where, args = appendMileageProtocolFilter(where, args, "m.protocol", protocols)
	} else if protocol := strings.TrimSpace(query.Get("protocol")); protocol != "" {
		where = append(where, "m.protocol = ?")
		args = append(args, protocol)
	}
	if dateFrom := strings.TrimSpace(query.Get("dateFrom")); dateFrom != "" {
		where = append(where, "m.stat_date >= ?")
		args = append(args, dateFrom)
	}
	if dateTo := strings.TrimSpace(query.Get("dateTo")); dateTo != "" {
		where = append(where, "m.stat_date <= ?")
		args = append(args, dateTo)
	}
	fromSQL := `FROM vehicle_daily_mileage m LEFT JOIN vehicle_identity_binding b ON b.vin = m.vin WHERE ` + strings.Join(where, " AND ")
	return SQLQuery{
		Text: `SELECT COUNT(DISTINCT m.vin) AS vehicle_count, COUNT(*) AS record_count, ` +
			`COUNT(DISTINCT m.protocol) AS source_count, COALESCE(SUM(m.daily_mileage_km), 0) AS total_mileage_km ` + fromSQL,
		Args: args,
	}
}

func buildMileageStatisticsWhere(query url.Values) (string, []any) {
	where := []string{"1 = 1"}
	args := []any{}
	if strings.EqualFold(strings.TrimSpace(query.Get("vehicleScope")), "bound") {
		where = append(where, "b.vin IS NOT NULL", "b.vin <> ''")
	}
	if vin := strings.TrimSpace(query.Get("vin")); vin != "" {
		where = append(where, "(m.vin LIKE ? OR b.plate LIKE ? OR b.phone LIKE ? OR b.oem LIKE ?)")
		like := "%" + vin + "%"
		args = append(args, like, like, like, like)
	}
	where, args = appendVINListFilter(where, args, "m.vin", query.Get("vins"))
	where, args = appendVINListFilter(where, args, "m.vin", query.Get("scopeVins"))
	where, args = appendVehicleDateScope(where, args, "m.vin", "m.stat_date", query.Get("scopeVehicleFrom"))
	if protocols := parseMileageProtocols(query.Get("protocols")); len(protocols) > 0 {
		where, args = appendMileageProtocolFilter(where, args, "m.protocol", protocols)
	} else if protocol := strings.TrimSpace(query.Get("protocol")); protocol != "" {
		where = append(where, "m.protocol = ?")
		args = append(args, protocol)
	}
	if dateFrom := strings.TrimSpace(query.Get("dateFrom")); dateFrom != "" {
		where = append(where, "m.stat_date >= ?")
		args = append(args, dateFrom)
	}
	if dateTo := strings.TrimSpace(query.Get("dateTo")); dateTo != "" {
		where = append(where, "m.stat_date <= ?")
		args = append(args, dateTo)
	}
	return strings.Join(where, " AND "), args
}

func buildMileageStatisticsBaseSQL(query url.Values) (string, []any) {
	where, args := buildMileageStatisticsWhere(query)
	protocols := parseMileageProtocols(query.Get("protocols"))
	dailyMileageExpression := `MAX(COALESCE(m.daily_mileage_km, 0))`
	latestMileageExpression := `MAX(COALESCE(m.latest_total_mileage_km, 0))`
	if len(protocols) > 0 {
		selectionOrder := mileageProtocolOrder("m.protocol", protocols)
		dailyMileageExpression = `COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(CAST(COALESCE(m.daily_mileage_km, 0) AS CHAR) ORDER BY ` + selectionOrder + `), ',', 1) AS DECIMAL(18,3)), 0)`
		latestMileageExpression = `COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(CAST(COALESCE(m.latest_total_mileage_km, 0) AS CHAR) ORDER BY ` + selectionOrder + `), ',', 1) AS DECIMAL(18,3)), 0)`
	}
	return `SELECT m.vin, COALESCE(MAX(NULLIF(b.plate, '')), '') AS plate, m.stat_date, ` +
		dailyMileageExpression + ` AS daily_mileage_km, ` +
		latestMileageExpression + ` AS latest_mileage_km ` +
		`FROM vehicle_daily_mileage m LEFT JOIN vehicle_identity_binding b ON b.vin = m.vin ` +
		`WHERE ` + where + ` GROUP BY m.vin, m.stat_date`, args
}

func buildMileageStatisticsSummarySQL(query url.Values) SQLQuery {
	base, args := buildMileageStatisticsBaseSQL(query)
	return SQLQuery{Text: `SELECT COUNT(DISTINCT d.vin), COUNT(*), COALESCE(SUM(d.daily_mileage_km), 0), ` +
		`COALESCE(AVG(d.daily_mileage_km), 0) FROM (` + base + `) d`, Args: args}
}

func buildMileageStatisticsTrendSQL(query url.Values) SQLQuery {
	base, args := buildMileageStatisticsBaseSQL(query)
	return SQLQuery{Text: `SELECT DATE_FORMAT(d.stat_date, '%Y-%m-%d'), COALESCE(SUM(d.daily_mileage_km), 0), ` +
		`COUNT(DISTINCT d.vin) FROM (` + base + `) d GROUP BY d.stat_date ORDER BY d.stat_date ASC`, Args: args}
}

func buildMileageStatisticsRankingSQL(query url.Values) SQLQuery {
	base, args := buildMileageStatisticsBaseSQL(query)
	return SQLQuery{Text: `SELECT d.vin, COALESCE(MAX(d.plate), ''), COALESCE(SUM(d.daily_mileage_km), 0), ` +
		`COALESCE(CAST(SUBSTRING_INDEX(GROUP_CONCAT(CAST(d.latest_mileage_km AS CHAR) ORDER BY d.stat_date DESC), ',', 1) AS DECIMAL(18,3)), 0), ` +
		`COUNT(DISTINCT d.stat_date) FROM (` + base + `) d ` +
		`GROUP BY d.vin ORDER BY SUM(d.daily_mileage_km) DESC, d.vin ASC LIMIT 20`, Args: args}
}

func buildMileageStatisticsSourceCountSQL(query url.Values) SQLQuery {
	where, args := buildMileageStatisticsWhere(query)
	return SQLQuery{Text: `SELECT COUNT(DISTINCT m.protocol) FROM vehicle_daily_mileage m ` +
		`LEFT JOIN vehicle_identity_binding b ON b.vin = m.vin WHERE ` + where, Args: args}
}

func buildFleetLatestMileageSQL(query url.Values) SQLQuery {
	where := []string{"l.total_mileage_km IS NOT NULL", "l.total_mileage_km >= 0"}
	args := []any{}
	if strings.EqualFold(strings.TrimSpace(query.Get("vehicleScope")), "bound") {
		where = append(where, "b.vin IS NOT NULL", "b.vin <> ''")
	}
	if vin := strings.TrimSpace(query.Get("vin")); vin != "" {
		where = append(where, "(l.vin LIKE ? OR b.plate LIKE ? OR b.phone LIKE ? OR b.oem LIKE ?)")
		like := "%" + vin + "%"
		args = append(args, like, like, like, like)
	}
	where, args = appendVINListFilter(where, args, "l.vin", query.Get("vins"))
	where, args = appendVINListFilter(where, args, "l.vin", query.Get("scopeVins"))
	protocols := parseMileageProtocols(query.Get("protocols"))
	if len(protocols) > 0 {
		where, args = appendMileageProtocolFilter(where, args, "l.protocol", protocols)
	} else if protocol := strings.TrimSpace(query.Get("protocol")); protocol != "" {
		where = append(where, "l.protocol = ?")
		args = append(args, protocol)
	}
	selectionOrder := `l.updated_at DESC, l.protocol ASC`
	if len(protocols) > 0 {
		selectionOrder = mileageProtocolOrder("l.protocol", protocols) + `, l.updated_at DESC`
	}
	inner := `SELECT l.vin, CAST(SUBSTRING_INDEX(GROUP_CONCAT(CAST(l.total_mileage_km AS CHAR) ` +
		`ORDER BY ` + selectionOrder + `), ',', 1) AS DECIMAL(18,3)) AS latest_mileage_km ` +
		`FROM vehicle_realtime_location l LEFT JOIN vehicle_identity_binding b ON b.vin = l.vin WHERE ` +
		strings.Join(where, " AND ") + ` GROUP BY l.vin`
	return SQLQuery{Text: `SELECT COALESCE(SUM(x.latest_mileage_km), 0) FROM (` + inner + `) x`, Args: args}
}

func parseMileageProtocols(raw string) []string {
	seen := map[string]bool{}
	values := make([]string, 0, 3)
	for _, item := range strings.Split(raw, ",") {
		protocol := strings.ToUpper(strings.TrimSpace(item))
		switch protocol {
		case "GB32960", "JT808", "YUTONG_MQTT":
			if !seen[protocol] {
				seen[protocol] = true
				values = append(values, protocol)
			}
		}
	}
	return values
}

func appendMileageProtocolFilter(where []string, args []any, column string, protocols []string) ([]string, []any) {
	placeholders := make([]string, len(protocols))
	for index, protocol := range protocols {
		placeholders[index] = "?"
		args = append(args, protocol)
	}
	return append(where, column+" IN ("+strings.Join(placeholders, ",")+")"), args
}

func mileageProtocolOrder(column string, protocols []string) string {
	parts := make([]string, 0, len(protocols)+1)
	parts = append(parts, "CASE "+column)
	for index, protocol := range protocols {
		parts = append(parts, "WHEN '"+protocol+"' THEN "+strconv.Itoa(index+1))
	}
	parts = append(parts, "ELSE 999 END")
	return strings.Join(parts, " ") + ", " + column + " ASC"
}

func appendVINListFilter(where []string, args []any, column string, raw string) ([]string, []any) {
	seen := map[string]bool{}
	values := make([]string, 0)
	for _, item := range strings.Split(raw, ",") {
		vin := strings.TrimSpace(item)
		if vin == "" || seen[vin] {
			continue
		}
		seen[vin] = true
		values = append(values, vin)
	}
	if len(values) == 0 {
		return where, args
	}
	placeholders := make([]string, len(values))
	for index, vin := range values {
		placeholders[index] = "?"
		args = append(args, vin)
	}
	return append(where, column+" IN ("+strings.Join(placeholders, ",")+")"), args
}

func appendVehicleDateScope(where []string, args []any, vinColumn, dateColumn, raw string) ([]string, []any) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return where, args
	}
	var parsed map[string]string
	if err := json.Unmarshal([]byte(raw), &parsed); err != nil || len(parsed) == 0 {
		return append(where, "1 = 0"), args
	}
	cutoffs := make(map[string]string, len(parsed))
	for vin, cutoff := range parsed {
		vin = strings.ToUpper(strings.TrimSpace(vin))
		cutoff = strings.TrimSpace(cutoff)
		if vin == "" || cutoff == "" {
			continue
		}
		cutoffs[vin] = cutoff
	}
	if len(cutoffs) == 0 {
		return append(where, "1 = 0"), args
	}
	vins := make([]string, 0, len(cutoffs))
	for vin := range cutoffs {
		vins = append(vins, vin)
	}
	sort.Strings(vins)
	predicates := make([]string, 0, len(vins))
	for _, vin := range vins {
		predicates = append(predicates, "("+vinColumn+" = ? AND "+dateColumn+" >= ?)")
		args = append(args, vin, cutoffs[vin])
	}
	return append(where, "("+strings.Join(predicates, " OR ")+")"), args
}

func buildLimitOffset(query url.Values) (int, int) {
	return parseSQLPageSize(query.Get("limit"), 20), parsePositive(query.Get("offset"), 0)
}

func parseLimitOffset(rawLimit, rawOffset string) (int, int) {
	return parseSQLPageSize(rawLimit, 20), parsePositive(rawOffset, 0)
}

func parseSQLPageSize(raw string, fallback int) int {
	value, err := strconv.Atoi(raw)
	if err != nil || value <= 0 {
		return fallback
	}
	if value > maxSQLPageSize {
		return maxSQLPageSize
	}
	return value
}

func mustInt(value string) int {
	n, _ := strconv.Atoi(value)
	return n
}
