package platform

import (
	"crypto/sha1"
	"encoding/hex"
	"strconv"
	"strings"
	"time"
)

func buildRawFrameSQL(database string, query RawFrameQuery) SQLQuery {
	table := rawFrameTable(database, query)
	where := rawFrameWhere(query)
	parsedFieldsSelect := "'' AS parsed_fields"
	if query.IncludeFields || len(query.Fields) > 0 {
		parsedFieldsSelect = "parsed_json AS parsed_fields"
	}
	args := []any{}
	limit := query.Limit
	if limit <= 0 {
		limit = 100
	}
	offset := query.Offset
	if offset < 0 {
		offset = 0
	}
	text := `SELECT ts, frame_id, event_time, received_at, raw_size_bytes, ` + parsedFieldsSelect +
		`, parse_status, parse_error, source_endpoint, protocol, vehicle_key, vin, phone FROM ` + table
	if len(where) > 0 {
		text += ` WHERE ` + strings.Join(where, " AND ")
	}
	countText := `SELECT COUNT(*) FROM ` + table
	if len(where) > 0 {
		countText += ` WHERE ` + strings.Join(where, " AND ")
	}
	if query.SkipCount {
		countText = ""
	}
	text += ` ORDER BY ts DESC, frame_id ASC LIMIT ` + strconv.Itoa(limit) + ` OFFSET ` + strconv.Itoa(offset)
	return SQLQuery{Text: text, Args: args, CountText: countText}
}

func rawFrameWhere(query RawFrameQuery) []string {
	where := make([]string, 0, 4)
	if query.Protocol != "" {
		where = append(where, "protocol = '"+quoteTDengine(strings.ToUpper(strings.TrimSpace(query.Protocol)))+"'")
	}
	if query.VIN != "" {
		where = append(where, "vin = '"+quoteTDengine(strings.TrimSpace(query.VIN))+"'")
	}
	if query.DateFrom != "" {
		where = append(where, "ts >= '"+quoteTDengine(normalizeTDengineTime(query.DateFrom))+"'")
	}
	if query.DateTo != "" {
		where = append(where, "ts <= '"+quoteTDengine(normalizeTDengineTime(query.DateTo))+"'")
	}
	return where
}

func buildHistoryLocationSQL(database string, query map[string]string) SQLQuery {
	table := locationTable(database, query)
	where := make([]string, 0, 4)
	args := []any{}
	if protocol := strings.TrimSpace(query["protocol"]); protocol != "" {
		where = append(where, "protocol = '"+quoteTDengine(strings.ToUpper(protocol))+"'")
	}
	if vin := strings.TrimSpace(query["vin"]); vin != "" {
		where = append(where, "vin = '"+quoteTDengine(vin)+"'")
	}
	if dateFrom := strings.TrimSpace(query["dateFrom"]); dateFrom != "" {
		where = append(where, "ts >= '"+quoteTDengine(normalizeTDengineTime(dateFrom))+"'")
	}
	if dateTo := strings.TrimSpace(query["dateTo"]); dateTo != "" {
		where = append(where, "ts <= '"+quoteTDengine(normalizeTDengineTime(dateTo))+"'")
	}
	limit, offset := parseLimitOffset(query["limit"], query["offset"])
	text := `SELECT CAST(ts AS BIGINT), vin, protocol, longitude, latitude, speed_kmh, soc_percent, direction_deg, alarm_flag, total_mileage_km, CAST(received_at AS BIGINT) FROM ` + table
	if len(where) > 0 {
		text += ` WHERE ` + strings.Join(where, " AND ")
	}
	countText := `SELECT COUNT(*) FROM ` + table
	if len(where) > 0 {
		countText += ` WHERE ` + strings.Join(where, " AND ")
	}
	text += ` ORDER BY ts DESC, vin ASC, protocol ASC LIMIT ` + strconv.Itoa(limit) + ` OFFSET ` + strconv.Itoa(offset)
	return SQLQuery{Text: text, Args: args, CountText: countText}
}

func buildHistoryLocationSeriesSQL(database string, query HistoryLocationSeriesQuery) SQLQuery {
	tableQuery := map[string]string{"vin": query.VIN, "protocol": query.Protocol}
	where := []string{"vin = '" + quoteTDengine(strings.TrimSpace(query.VIN)) + "'"}
	if protocol := strings.TrimSpace(query.Protocol); protocol != "" {
		where = append(where, "protocol = '"+quoteTDengine(strings.ToUpper(protocol))+"'")
	}
	where = append(where,
		"ts >= '"+quoteTDengine(normalizeTDengineSeriesTime(query.DateFrom))+"'",
		"ts <= '"+quoteTDengine(normalizeTDengineSeriesTime(query.DateTo))+"'",
	)
	grain := query.GrainSeconds
	if grain <= 0 {
		grain = 60
	}
	text := `SELECT _wstart, protocol, COUNT(*), ` +
		`AVG(speed_kmh), MIN(speed_kmh), MAX(speed_kmh), LAST(speed_kmh), ` +
		`AVG(total_mileage_km), MIN(total_mileage_km), MAX(total_mileage_km), LAST(total_mileage_km) ` +
		`FROM ` + locationTable(database, tableQuery) + ` WHERE ` + strings.Join(where, " AND ") +
		` PARTITION BY protocol INTERVAL(` + strconv.Itoa(grain) + `s) ORDER BY _wstart ASC, protocol ASC`
	return SQLQuery{Text: text}
}

func buildHistoryExportBatchSQL(database string, query HistoryExportStoreQuery, cursor HistoryExportCursor, limit int) SQLQuery {
	if limit <= 0 || limit > 5000 {
		limit = 5000
	}
	switch query.Category {
	case "raw":
		return buildRawExportBatchSQL(database, query, cursor, limit)
	default:
		return buildLocationExportBatchSQL(database, query, cursor, limit)
	}
}

func buildLocationExportBatchSQL(database string, query HistoryExportStoreQuery, cursor HistoryExportCursor, limit int) SQLQuery {
	tableQuery := map[string]string{"vin": query.VIN, "protocol": query.Protocol}
	where := historyExportTDengineWhere(query)
	if cursor.Time != "" {
		cursorTime := quoteTDengine(cursor.Time)
		cursorProtocol := quoteTDengine(cursor.Protocol)
		where = append(where, "(ts > '"+cursorTime+"' OR (ts = '"+cursorTime+"' AND protocol > '"+cursorProtocol+"'))")
	}
	table := locationTable(database, tableQuery)
	return SQLQuery{
		Text:      `SELECT ts, vin, protocol, longitude, latitude, speed_kmh, total_mileage_km, received_at FROM ` + table + ` WHERE ` + strings.Join(where, " AND ") + ` ORDER BY ts ASC, protocol ASC LIMIT ` + strconv.Itoa(limit),
		CountText: `SELECT COUNT(*) FROM ` + table + ` WHERE ` + strings.Join(historyExportTDengineWhere(query), " AND "),
	}
}

func buildRawExportBatchSQL(database string, query HistoryExportStoreQuery, cursor HistoryExportCursor, limit int) SQLQuery {
	rawQuery := RawFrameQuery{VIN: query.VIN, Protocol: query.Protocol}
	where := historyExportTDengineWhere(query)
	if cursor.Time != "" {
		timeValue := quoteTDengine(cursor.Time)
		protocol := quoteTDengine(cursor.Protocol)
		identifier := quoteTDengine(cursor.ID)
		where = append(where, "(ts > '"+timeValue+"' OR (ts = '"+timeValue+"' AND (protocol > '"+protocol+"' OR (protocol = '"+protocol+"' AND frame_id > '"+identifier+"'))))")
	}
	table := rawFrameTable(database, rawQuery)
	selectText := `SELECT ts, frame_id, event_time, received_at, raw_size_bytes, parsed_json, parse_status, parse_error, source_endpoint, protocol, vehicle_key, vin, phone FROM ` + table + ` WHERE ` + strings.Join(where, " AND ")
	return SQLQuery{Text: selectText + ` ORDER BY ts ASC, protocol ASC, frame_id ASC LIMIT ` + strconv.Itoa(limit), CountText: `SELECT COUNT(*) FROM ` + table + ` WHERE ` + strings.Join(historyExportTDengineWhere(query), " AND ")}
}

func historyExportTDengineWhere(query HistoryExportStoreQuery) []string {
	where := []string{"vin = '" + quoteTDengine(strings.TrimSpace(query.VIN)) + "'"}
	if protocol := strings.TrimSpace(query.Protocol); protocol != "" {
		where = append(where, "protocol = '"+quoteTDengine(strings.ToUpper(protocol))+"'")
	}
	if query.DateFrom != "" {
		where = append(where, "ts >= '"+quoteTDengine(normalizeTDengineSeriesTime(query.DateFrom))+"'")
	}
	if query.DateTo != "" {
		where = append(where, "ts <= '"+quoteTDengine(normalizeTDengineSeriesTime(query.DateTo))+"'")
	}
	return where
}

func normalizeTDengineSeriesTime(value string) string {
	if parsed, ok := parseTrackRequestTime(value); ok {
		return parsed.Format(time.RFC3339)
	}
	return strings.TrimSpace(value)
}

func buildTodayRawFrameCountSQL(database string, now time.Time) SQLQuery {
	start := shanghaiDayStartUTC(now)
	return SQLQuery{
		Text: `SELECT COUNT(*) FROM ` + qualifyTDengine(database, "raw_frames") + ` WHERE ts >= '` + quoteTDengine(start) + `'`,
	}
}

func qualifyTDengine(database, table string) string {
	if strings.TrimSpace(database) == "" {
		return table
	}
	return database + "." + table
}

func rawFrameTable(database string, query RawFrameQuery) string {
	protocol := strings.ToUpper(strings.TrimSpace(query.Protocol))
	vin := strings.TrimSpace(query.VIN)
	if protocol == "" || vin == "" {
		return qualifyTDengine(database, "raw_frames")
	}
	if protocol == "JT808" {
		return qualifyTDengine(database, "raw_frames")
	}
	return qualifyTDengine(database, "raw_"+strings.ToLower(protocol)+"_"+hash16(vin))
}

func locationTable(database string, query map[string]string) string {
	protocol := strings.ToUpper(strings.TrimSpace(query["protocol"]))
	vin := strings.TrimSpace(query["vin"])
	if protocol == "" || vin == "" {
		return qualifyTDengine(database, "vehicle_locations")
	}
	return qualifyTDengine(database, "loc_"+strings.ToLower(protocol)+"_"+hash16(vin))
}

func hash16(value string) string {
	sum := sha1.Sum([]byte(value))
	return hex.EncodeToString(sum[:8])
}

func quoteTDengine(value string) string {
	return strings.ReplaceAll(value, "'", "''")
}

func normalizeTDengineTime(value string) string {
	value = strings.TrimSpace(value)
	if value == "" {
		return ""
	}
	shanghai := time.FixedZone("Asia/Shanghai", 8*3600)
	for _, layout := range []string{"2006-01-02T15:04:05", "2006-01-02T15:04", "2006-01-02 15:04:05", "2006-01-02 15:04", "2006-01-02"} {
		if parsed, err := time.ParseInLocation(layout, value, shanghai); err == nil {
			return parsed.Format(time.RFC3339)
		}
	}
	if parsed, err := time.Parse(time.RFC3339, value); err == nil {
		return parsed.Format(time.RFC3339)
	}
	return value
}

func shanghaiDayStartUTC(now time.Time) string {
	shanghai := time.FixedZone("Asia/Shanghai", 8*3600)
	local := now.In(shanghai)
	start := time.Date(local.Year(), local.Month(), local.Day(), 0, 0, 0, 0, shanghai)
	return start.Format(time.RFC3339)
}
