package yutongmqtt

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
)

var ErrInvalidJSON = errors.New("yutong mqtt invalid json")

func ParseMessage(endpoint string, topic string, payload []byte, receivedAtMS int64) (envelope.FrameEnvelope, error) {
	root := map[string]any{}
	decoder := json.NewDecoder(bytes.NewReader(payload))
	decoder.UseNumber()
	if err := decoder.Decode(&root); err != nil {
		return envelope.FrameEnvelope{}, fmt.Errorf("%w: %v", ErrInvalidJSON, err)
	}

	data := objectValue(root, "data")
	if data == nil {
		data = root
	}
	deviceID := firstText(root, "device", "deviceId", "terminalId", "terminalID", "imei", "IMEI", "mqttDeviceId")
	externalVIN := firstText(root, "vin", "VIN", "externalVin", "vehicleVin")
	vin := externalVIN
	if vin == "" && looksLikeVIN(deviceID) {
		vin = deviceID
	}
	plate := firstText(root, "plateNo", "carNo", "plate", "vehicleNo")
	phone := firstText(root, "sim", "phone", "mobile")
	eventTimeMS := parseTimeMS(firstText(root, "time", "deviceTime", "eventTime", "gpsTime"), receivedAtMS)
	fields := fieldsFromData(data)

	parsed := map[string]any{
		"endpoint": endpoint,
		"topic":    topic,
		"root":     root,
		"data":     data,
	}
	env := envelope.FrameEnvelope{
		Protocol:       envelope.ProtocolYutongMQTT,
		MessageID:      "MQTT",
		VIN:            vin,
		VehicleKeyHint: deviceID,
		Phone:          phone,
		DeviceID:       deviceID,
		Plate:          plate,
		SourceEndpoint: "mqtt://" + endpoint + topic,
		SourceCode:     sourceCodeFromEndpoint(endpoint),
		PlatformName:   platformNameFromEndpoint(endpoint),
		SourceKind:     "PLATFORM",
		EventTimeMS:    eventTimeMS,
		ReceivedAtMS:   receivedAtMS,
		RawText:        string(payload),
		Parsed:         parsed,
		Fields:         fields,
		ParseStatus:    envelope.ParseOK,
	}
	env.EventID = env.StableEventID()
	return env, nil
}

func platformNameFromEndpoint(endpoint string) string {
	endpoint = strings.TrimSpace(endpoint)
	if strings.EqualFold(endpoint, "yutong") {
		return "宇通"
	}
	return endpoint
}

func sourceCodeFromEndpoint(endpoint string) string {
	endpoint = strings.TrimSpace(endpoint)
	var builder strings.Builder
	for _, r := range endpoint {
		switch {
		case r >= 'a' && r <= 'z':
			builder.WriteRune(r)
		case r >= 'A' && r <= 'Z':
			builder.WriteRune(r)
		case r >= '0' && r <= '9':
			builder.WriteRune(r)
		case r == '_' || r == '-' || r == '.':
			builder.WriteRune(r)
		}
	}
	return builder.String()
}

func fieldsFromData(data map[string]any) map[string]any {
	fields := map[string]any{}
	if speed, ok := firstFloat(data, "METER_SPEED", "speed", "speed_kmh"); ok {
		fields[envelope.FieldSpeedKMH] = speed
	}
	if mileage, ok := firstFloat(data, "TOTAL_MILEAGE", "totalMileage", "total_mileage_km"); ok {
		fields[envelope.FieldTotalMileageKM] = normalizeTotalMileageKM(mileage)
	}
	if soc, ok := firstFloat(data, "BATTERY_CAPACITY_SOC", "soc", "SOC", "soc_percent"); ok {
		fields[envelope.FieldSOCPercent] = soc
	}
	if longitude, ok := firstCoord(data, "LONGITUDE", "longitude", "lng"); ok {
		fields[envelope.FieldLongitude] = longitude
	}
	if latitude, ok := firstCoord(data, "LATITUDE", "latitude", "lat"); ok {
		fields[envelope.FieldLatitude] = latitude
	}
	if direction, ok := firstFloat(data, "GPSDirection", "direction", "direction_deg"); ok {
		fields["direction_deg"] = direction
	}
	if gear, ok := firstFloat(data, "ON_GEAR", "gear"); ok {
		fields["gear"] = gear
	}
	return fields
}

func objectValue(root map[string]any, key string) map[string]any {
	value, ok := root[key]
	if !ok {
		return nil
	}
	typed, ok := value.(map[string]any)
	if !ok {
		return nil
	}
	return typed
}

func firstText(root map[string]any, keys ...string) string {
	for _, key := range keys {
		if value, ok := root[key]; ok && value != nil {
			text := strings.TrimSpace(fmt.Sprint(value))
			if text != "" && text != "<nil>" {
				return text
			}
		}
	}
	return ""
}

func firstFloat(root map[string]any, keys ...string) (float64, bool) {
	for _, key := range keys {
		value, ok := root[key]
		if !ok || value == nil {
			continue
		}
		if parsed, ok := toFloat(value); ok {
			return parsed, true
		}
	}
	return 0, false
}

func firstCoord(root map[string]any, keys ...string) (float64, bool) {
	value, ok := firstFloat(root, keys...)
	if !ok {
		return 0, false
	}
	if value == 0 || int64(value) == 999999 {
		return 0, false
	}
	if value > 1000 || value < -1000 {
		value = value / 1_000_000
	}
	return value, true
}

func toFloat(value any) (float64, bool) {
	switch typed := value.(type) {
	case json.Number:
		parsed, err := typed.Float64()
		return parsed, err == nil
	case float64:
		return typed, true
	case float32:
		return float64(typed), true
	case int:
		return float64(typed), true
	case int64:
		return float64(typed), true
	case string:
		parsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)
		return parsed, err == nil
	default:
		return 0, false
	}
}

func parseTimeMS(raw string, fallback int64) int64 {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return fallback
	}
	for _, layout := range []string{
		"2006-01-02 15:04:05.000",
		"2006-01-02 15:04:05",
	} {
		if parsed, err := time.ParseInLocation(layout, raw, time.FixedZone("Asia/Shanghai", 8*3600)); err == nil {
			return parsed.UnixMilli()
		}
	}
	if len(raw) == 14 {
		if parsed, err := time.ParseInLocation("20060102150405", raw, time.FixedZone("Asia/Shanghai", 8*3600)); err == nil {
			return parsed.UnixMilli()
		}
	}
	if parsed, err := time.Parse(time.RFC3339, raw); err == nil {
		return parsed.UnixMilli()
	}
	if epoch, err := strconv.ParseInt(raw, 10, 64); err == nil {
		if epoch > 10_000_000_000 {
			return epoch
		}
		return epoch * 1000
	}
	return fallback
}

func looksLikeVIN(value string) bool {
	value = strings.TrimSpace(value)
	return len(value) == 17
}

func normalizeTotalMileageKM(value float64) float64 {
	return value / 1000
}
