package envelope

import (
	"encoding/json"
	"testing"
	"time"
)

func TestFrameEnvelopeVehicleKeyPrefersVIN(t *testing.T) {
	e := FrameEnvelope{Protocol: ProtocolJT808, VIN: "LNBVIN00000000001", Phone: "13307795425"}
	if got := e.VehicleKey(); got != "LNBVIN00000000001" {
		t.Fatalf("VehicleKey() = %q", got)
	}
}

func TestFrameEnvelopeVehicleKeyFallsBackToPhone(t *testing.T) {
	e := FrameEnvelope{Protocol: ProtocolJT808, Phone: "13307795425"}
	if got := e.VehicleKey(); got != "JT808:13307795425" {
		t.Fatalf("VehicleKey() = %q", got)
	}
}

func TestFrameEnvelopeEventIDStable(t *testing.T) {
	e := FrameEnvelope{
		Protocol:     ProtocolJT808,
		MessageID:    "0x0200",
		Phone:        "013307795425",
		Sequence:     1,
		EventTimeMS:  1782745114000,
		ReceivedAtMS: 1782745114999,
		RawHex:       "7e02000000ff7e",
	}
	a := e.StableEventID()
	b := e.StableEventID()
	if a == "" || a != b {
		t.Fatalf("event id must be non-empty and stable: %q %q", a, b)
	}
}

func TestFrameEnvelopeMarshalDefaults(t *testing.T) {
	e := FrameEnvelope{Protocol: ProtocolGB32960, VIN: "LNBVIN00000000002", MessageID: "0x02"}
	payload, err := e.MarshalJSONBytes()
	if err != nil {
		t.Fatalf("MarshalJSONBytes() error = %v", err)
	}
	var decoded FrameEnvelope
	if err := json.Unmarshal(payload, &decoded); err != nil {
		t.Fatalf("json.Unmarshal() error = %v", err)
	}
	if decoded.EventID == "" {
		t.Fatal("event id should be filled")
	}
	if decoded.ParseStatus != ParseOK {
		t.Fatalf("parse status = %q", decoded.ParseStatus)
	}
	if decoded.EventKind != EventKindRaw {
		t.Fatalf("event kind = %q, want %q", decoded.EventKind, EventKindRaw)
	}
}

func TestNormalizedEventTimeMSFallsBackToReceivedWhenEventIsFarFuture(t *testing.T) {
	received := time.Date(2026, 7, 12, 9, 30, 0, 0, time.UTC).UnixMilli()
	event := time.Date(2026, 7, 13, 9, 30, 0, 0, time.UTC).UnixMilli()

	got, ok := NormalizedEventTimeMS(FrameEnvelope{EventTimeMS: event, ReceivedAtMS: received})
	if !ok {
		t.Fatal("NormalizedEventTimeMS() ok = false")
	}
	if got != received {
		t.Fatalf("normalized event time = %d, want received %d", got, received)
	}
	_, reason, ok := NormalizedEventTimeMSWithReason(FrameEnvelope{EventTimeMS: event, ReceivedAtMS: received})
	if !ok || reason != EventTimeReasonReceivedFutureEvent {
		t.Fatalf("reason = %q ok=%v, want future fallback", reason, ok)
	}
}

func TestNormalizedEventTimeMSKeepsSmallFutureSkew(t *testing.T) {
	received := time.Date(2026, 7, 12, 9, 30, 0, 0, time.UTC).UnixMilli()
	event := time.Date(2026, 7, 12, 9, 35, 0, 0, time.UTC).UnixMilli()

	got, ok := NormalizedEventTimeMS(FrameEnvelope{EventTimeMS: event, ReceivedAtMS: received})
	if !ok {
		t.Fatal("NormalizedEventTimeMS() ok = false")
	}
	if got != event {
		t.Fatalf("normalized event time = %d, want event %d", got, event)
	}
}

func TestIsRealtimeTelemetryFrameRejectsKnownNonRealtimeMessageIDs(t *testing.T) {
	fields := map[string]any{
		FieldLatitude:       30.590151,
		FieldLongitude:      121.069881,
		FieldTotalMileageKM: 10241.2,
	}
	tests := []FrameEnvelope{
		{Protocol: ProtocolJT808, MessageID: "0x0100", Fields: fields, ParseStatus: ParseOK},
		{Protocol: ProtocolGB32960, MessageID: "0x01", Fields: fields, Parsed: map[string]any{"data_units": []any{}}, ParseStatus: ParseOK},
	}
	for _, env := range tests {
		if IsRealtimeTelemetryFrame(env) {
			t.Fatalf("IsRealtimeTelemetryFrame(%s/%s) = true, want false", env.Protocol, env.MessageID)
		}
	}
}

func TestIsRealtimeTelemetryFrameAcceptsKnownRealtimeMessageIDs(t *testing.T) {
	tests := []FrameEnvelope{
		{Protocol: ProtocolJT808, MessageID: "0x0200", ParsedFields: map[string]any{"jt808.location.speed_kmh": 1}, ParseStatus: ParseOK},
		{Protocol: ProtocolGB32960, MessageID: "0x02", ParsedFields: map[string]any{"gb32960.vehicle.speed_kmh": 1}, ParseStatus: ParseOK},
		{Protocol: ProtocolGB32960, MessageID: "0x03", ParsedFields: map[string]any{"gb32960.vehicle.speed_kmh": 1}, ParseStatus: ParseOK},
	}
	for _, env := range tests {
		if !IsRealtimeTelemetryFrame(env) {
			t.Fatalf("IsRealtimeTelemetryFrame(%s/%s) = false, want true", env.Protocol, env.MessageID)
		}
	}
}

func TestIsRealtimeTelemetryFrameUsesCanonicalMQTTParsedFields(t *testing.T) {
	realtime := FrameEnvelope{
		Protocol:    ProtocolYutongMQTT,
		MessageID:   "MQTT",
		ParseStatus: ParseOK,
		ParsedFields: map[string]any{
			"yutong_mqtt.data.latitude": 30.590151,
		},
	}
	if !IsRealtimeTelemetryFrame(realtime) {
		t.Fatal("canonical MQTT data field should be classified as realtime")
	}

	metadataOnly := realtime
	metadataOnly.ParsedFields = map[string]any{"yutong_mqtt.metadata.topic": "/ytforward/shln/3"}
	if IsRealtimeTelemetryFrame(metadataOnly) {
		t.Fatal("MQTT metadata-only field must not be classified as realtime")
	}
}

func TestRequiresVehicleIdentity(t *testing.T) {
	tests := []struct {
		name string
		env  FrameEnvelope
		want bool
	}{
		{
			name: "gb32960 realtime upload",
			env:  FrameEnvelope{Protocol: ProtocolGB32960, MessageID: "0x02", Parsed: map[string]any{"data_units": []any{map[string]any{"name": "vehicle"}}}, ParseStatus: ParseOK},
			want: true,
		},
		{
			name: "gb32960 platform login",
			env:  FrameEnvelope{Protocol: ProtocolGB32960, MessageID: "0x05", ParseStatus: ParseOK},
			want: false,
		},
		{
			name: "gb32960 command response",
			env:  FrameEnvelope{Protocol: ProtocolGB32960, MessageID: "0x07", ParseStatus: ParseOK},
			want: false,
		},
		{
			name: "jt808 registration",
			env:  FrameEnvelope{Protocol: ProtocolJT808, MessageID: "0x0100", ParseStatus: ParseOK},
			want: true,
		},
		{
			name: "jt808 location",
			env:  FrameEnvelope{Protocol: ProtocolJT808, MessageID: "0x0200", Parsed: map[string]any{"location": map[string]any{"speed_kmh": 1}}, ParseStatus: ParseOK},
			want: true,
		},
		{
			name: "yutong empty data",
			env:  FrameEnvelope{Protocol: ProtocolYutongMQTT, MessageID: "MQTT", Parsed: map[string]any{"data": map[string]any{}}, ParseStatus: ParseOK},
			want: false,
		},
		{
			name: "yutong telemetry data",
			env:  FrameEnvelope{Protocol: ProtocolYutongMQTT, MessageID: "MQTT", Parsed: map[string]any{"data": map[string]any{"TOTAL_MILEAGE": 123}}, ParseStatus: ParseOK},
			want: true,
		},
		{
			name: "bad frame",
			env:  FrameEnvelope{Protocol: ProtocolJT808, MessageID: "0x0200", ParseStatus: ParseBadFrame},
			want: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := RequiresVehicleIdentity(tt.env); got != tt.want {
				t.Fatalf("RequiresVehicleIdentity() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestNormalizeSourceEndpointKey(t *testing.T) {
	tests := map[string]string{
		"115.231.168.135:20215":          "115.231.168.135",
		"115.231.168.135":                "115.231.168.135",
		" 115.159.85.149:28316 ":         "115.159.85.149",
		"mqtt://yutong/ytforward/shln/3": "mqtt",
		"MQTT://YUTONG/topic":            "mqtt",
		"":                               "",
		"   ":                            "",
	}
	for input, want := range tests {
		if got := NormalizeSourceEndpointKey(input); got != want {
			t.Fatalf("NormalizeSourceEndpointKey(%q) = %q, want %q", input, got, want)
		}
	}
}
