package realtime

import (
	"encoding/json"
	"strings"
	"testing"

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

func TestRealtimeKVFieldsFromGB32960ParsedDomains(t *testing.T) {
	rows := realtimeKVFields(envelope.FrameEnvelope{
		Protocol:     envelope.ProtocolGB32960,
		VIN:          "VIN001",
		EventTimeMS:  1000,
		ReceivedAtMS: 1100,
		EventID:      "event-1",
	}, map[string]any{
		"header": map[string]any{"vin": "VIN001", "command": "0x02"},
		"data_units": []any{
			map[string]any{"type": "0x01", "name": "vehicle", "value": map[string]any{"soc_percent": 88.0}},
			map[string]any{"type": "0x30", "name": "gd_fc_stack", "value": map[string]any{
				"stack_count": 1,
				"summaries": []any{
					map[string]any{"stack_water_outlet_temp_c": 63, "hydrogen_inlet_pressure_kpa": 130},
				},
			}},
		},
	})

	values := kvMap(rows)
	if values["gb32960.header/vin"] != "VIN001" || values["gb32960.header/command"] != "0x02" {
		t.Fatalf("gb32960 header kv missing: %#v", values)
	}
	if values["gb32960.vehicle/type"] != "0x01" || values["gb32960.vehicle/soc_percent"] != "88" {
		t.Fatalf("vehicle soc kv missing: %#v", values)
	}
	if values["gb32960.gd_fc_stack/stack_water_outlet_temp_c"] != "63" ||
		values["gb32960.gd_fc_stack/hydrogen_inlet_pressure_kpa"] != "130" {
		t.Fatalf("stack pressure kv missing: %#v", values)
	}
}

func TestBuildFieldsEnvelopeUsesOnlyProtocolMappedFieldNames(t *testing.T) {
	env := envelope.FrameEnvelope{
		Protocol:     envelope.ProtocolGB32960,
		VIN:          "VIN001",
		MessageID:    "0x02",
		EventTimeMS:  1000,
		ReceivedAtMS: 1100,
		EventID:      "event-1",
		Fields: map[string]any{
			"charge_status":              1,
			envelope.FieldSOCPercent:     88.0,
			envelope.FieldTotalMileageKM: 100.1,
		},
		Parsed: map[string]any{
			"data_units": []any{
				map[string]any{"type": "0x01", "name": "vehicle", "value": map[string]any{
					"charge_status":    1,
					"soc_percent":      88.0,
					"total_mileage_km": 100.1,
				}},
			},
		},
	}
	if !EnsureParsedFields(&env) {
		t.Fatal("EnsureParsedFields() should compute ingress fields")
	}
	fieldsEnv, ok := BuildFieldsEnvelope(env)
	if !ok {
		t.Fatal("BuildFieldsEnvelope() should emit mapped fields")
	}
	if len(fieldsEnv.ParsedFields) != 0 || len(fieldsEnv.ParsedFieldTypes) != 0 || len(fieldsEnv.Parsed) != 0 {
		t.Fatalf("fields envelope should keep only slim fields payload, parsed=%#v parsed_fields=%#v parsed_field_types=%#v", fieldsEnv.Parsed, fieldsEnv.ParsedFields, fieldsEnv.ParsedFieldTypes)
	}
	for _, bareKey := range []string{"charge_status", "soc_percent", "total_mileage_km"} {
		if _, exists := fieldsEnv.Fields[bareKey]; exists {
			t.Fatalf("fields envelope should not expose non-protocol bare key %q: %#v", bareKey, fieldsEnv.Fields)
		}
	}
	for _, mappedKey := range []string{
		"gb32960.vehicle.charge_status",
		"gb32960.vehicle.soc_percent",
		"gb32960.vehicle.total_mileage_km",
	} {
		if _, exists := fieldsEnv.Fields[mappedKey]; !exists {
			t.Fatalf("fields envelope missing mapped key %q: %#v", mappedKey, fieldsEnv.Fields)
		}
	}
}

func TestEnsureParsedFieldsKeepsUnknownVINProtocolFieldsAndExcludesAnnotations(t *testing.T) {
	env := envelope.FrameEnvelope{
		Protocol: envelope.ProtocolJT808,
		Phone:    "13307795425",
		Parsed: map[string]any{
			"registration": map[string]any{
				"manufacturer": "YUTNG",
			},
			"identity": map[string]any{
				"resolved": false,
				"reason":   "no_binding",
			},
		},
		ParsedFields: map[string]any{
			"jt808.location.speed_kmh": "12.3",
		},
	}
	if !EnsureParsedFields(&env) {
		t.Fatal("unknown VIN raw frame should still retain parsed fields")
	}
	if got := env.ParsedFields["jt808.location.speed_kmh"]; got != "12.3" {
		t.Fatalf("precomputed field = %#v", got)
	}
	if got := env.ParsedFields["jt808.registration.manufacturer"]; got != "YUTNG" {
		t.Fatalf("merged registration field = %#v", got)
	}
	if _, exists := env.ParsedFields["jt808.identity.resolved"]; exists {
		t.Fatalf("derived identity annotations must not enter protocol fields: %#v", env.ParsedFields)
	}
	for _, field := range []string{"jt808.location.speed_kmh", "jt808.registration.manufacturer"} {
		if env.ParsedFieldTypes[field] == "" {
			t.Fatalf("field type missing for %s: %#v", field, env.ParsedFieldTypes)
		}
	}
}

func TestBuildFieldsEnvelopeCopiesSourceMetadata(t *testing.T) {
	env := envelope.FrameEnvelope{
		Protocol:       envelope.ProtocolYutongMQTT,
		VIN:            "LMRKH9AC3R1004101",
		MessageID:      "MQTT",
		SourceEndpoint: "mqtt://yutong/ytforward/shln/1",
		SourceCode:     "yutong",
		PlatformName:   "宇通",
		SourceKind:     "PLATFORM",
		EventTimeMS:    1000,
		ReceivedAtMS:   1100,
		EventID:        "event-yutong",
		Fields: map[string]any{
			envelope.FieldTotalMileageKM: 56905,
		},
		Parsed: map[string]any{
			"data": map[string]any{"TOTAL_MILEAGE": 56905000},
		},
	}
	if !EnsureParsedFields(&env) {
		t.Fatal("EnsureParsedFields() should compute ingress fields")
	}
	fieldsEnv, ok := BuildFieldsEnvelope(env)
	if !ok {
		t.Fatal("BuildFieldsEnvelope() should emit mapped fields")
	}
	if fieldsEnv.SourceCode != "yutong" || fieldsEnv.PlatformName != "宇通" || fieldsEnv.SourceKind != "PLATFORM" {
		t.Fatalf("source metadata = code:%q platform:%q kind:%q", fieldsEnv.SourceCode, fieldsEnv.PlatformName, fieldsEnv.SourceKind)
	}
}

func TestBuildFieldsEnvelopeDropsNonPositiveTotalMileage(t *testing.T) {
	fieldsEnv, ok := BuildFieldsEnvelope(envelope.FrameEnvelope{
		Protocol:     envelope.ProtocolJT808,
		VIN:          "VIN001",
		MessageID:    "0x0200",
		EventTimeMS:  1000,
		ReceivedAtMS: 1100,
		EventID:      "event-1",
		ParsedFields: map[string]any{
			"jt808.location.latitude":         "31.259555",
			"jt808.location.longitude":        "119.892413",
			"jt808.location.total_mileage_km": "0",
		},
	})
	if !ok {
		t.Fatal("BuildFieldsEnvelope() should still emit valid fields")
	}
	if _, exists := fieldsEnv.Fields["jt808.location.total_mileage_km"]; exists {
		t.Fatalf("fields envelope should drop non-positive mileage: %#v", fieldsEnv.Fields)
	}
	if fieldsEnv.Fields["jt808.location.latitude"] != "31.259555" {
		t.Fatalf("fields envelope should keep valid location fields: %#v", fieldsEnv.Fields)
	}
}

func TestBuildFieldsEnvelopeDropsNonPositiveRawTotalMileage(t *testing.T) {
	env := envelope.FrameEnvelope{
		Protocol:     envelope.ProtocolYutongMQTT,
		VIN:          "LMRKH9AC3R1004101",
		MessageID:    "MQTT",
		EventTimeMS:  1000,
		ReceivedAtMS: 1100,
		EventID:      "event-yutong-zero-mileage",
		Parsed: map[string]any{
			"data": map[string]any{
				"LATITUDE":      30.590921,
				"LONGITUDE":     121.075044,
				"TOTAL_MILEAGE": 0,
			},
		},
	}
	if !EnsureParsedFields(&env) {
		t.Fatal("EnsureParsedFields() should compute ingress fields")
	}
	fieldsEnv, ok := BuildFieldsEnvelope(env)
	if !ok {
		t.Fatal("BuildFieldsEnvelope() should still emit non-mileage fields")
	}
	if _, exists := fieldsEnv.Fields["yutong_mqtt.data.total_mileage"]; exists {
		t.Fatalf("fields envelope should drop non-positive raw mileage: %#v", fieldsEnv.Fields)
	}
	if fieldsEnv.Fields["yutong_mqtt.data.latitude"] != "30.590921" {
		t.Fatalf("fields envelope should keep valid location fields: %#v", fieldsEnv.Fields)
	}
}

func TestBuildFieldsEnvelopeJSONOmitsDuplicateParsedPayload(t *testing.T) {
	fieldsEnv, ok := BuildFieldsEnvelope(envelope.FrameEnvelope{
		Protocol:     envelope.ProtocolJT808,
		VIN:          "VIN001",
		MessageID:    "0x0200",
		EventTimeMS:  1000,
		ReceivedAtMS: 1100,
		EventID:      "event-1",
		ParsedFields: map[string]any{
			"jt808.location.total_mileage_km": "10241.2",
			"jt808.location.speed_kmh":        "23",
		},
		ParsedFieldTypes: map[string]string{
			"jt808.location.total_mileage_km": "number",
			"jt808.location.speed_kmh":        "number",
		},
	})
	if !ok {
		t.Fatal("BuildFieldsEnvelope() should emit mapped fields")
	}
	data, err := json.Marshal(fieldsEnv)
	if err != nil {
		t.Fatalf("Marshal(fieldsEnv) error = %v", err)
	}
	text := string(data)
	for _, duplicateKey := range []string{`"parsed_fields"`, `"parsed_field_types"`, `"parsed"`} {
		if strings.Contains(text, duplicateKey) {
			t.Fatalf("fields JSON should omit duplicate %s payload: %s", duplicateKey, text)
		}
	}
	if !strings.Contains(text, `"fields"`) || !strings.Contains(text, `"source_event_id"`) || !strings.Contains(text, `"field_mapping"`) {
		t.Fatalf("fields JSON missing slim payload metadata: %s", text)
	}
}

func TestBuildFieldsEnvelopeOmitsGB32960VendorFragmentFields(t *testing.T) {
	env := envelope.FrameEnvelope{
		Protocol:     envelope.ProtocolGB32960,
		VIN:          "VIN001",
		MessageID:    "0x02",
		EventTimeMS:  1000,
		ReceivedAtMS: 1100,
		Parsed: map[string]any{
			"data_units": []any{
				map[string]any{
					"type": "0x30",
					"name": "gd_fc_stack",
					"value": map[string]any{
						"stack_count": 1,
						"summaries": []any{
							map[string]any{
								"cell_count":                    432,
								"stack_water_outlet_temp_c":     63,
								"frame_cell_start":              401,
								"frame_cell_count":              32,
								"frame_max_cell_voltage_v":      1,
								"frame_min_cell_voltage_v":      1,
								"hydrogen_inlet_pressure_kpa":   130,
								"air_inlet_pressure_kpa":        150,
								"stack_water_outlet_temp_extra": "kept",
							},
						},
					},
				},
			},
		},
	}
	if !EnsureParsedFields(&env) {
		t.Fatal("EnsureParsedFields() should compute ingress fields")
	}
	fieldsEnv, ok := BuildFieldsEnvelope(env)
	if !ok {
		t.Fatal("BuildFieldsEnvelope() should emit mapped fields")
	}
	for _, fragmentField := range []string{
		"gb32960.gd_fc_stack.frame_cell_start",
		"gb32960.gd_fc_stack.frame_cell_count",
		"gb32960.gd_fc_stack.frame_max_cell_voltage_v",
		"gb32960.gd_fc_stack.frame_min_cell_voltage_v",
	} {
		if _, exists := fieldsEnv.Fields[fragmentField]; exists {
			t.Fatalf("fields envelope should omit fragment-only field %q: %#v", fragmentField, fieldsEnv.Fields)
		}
	}
	for _, keptField := range []string{
		"gb32960.gd_fc_stack.stack_count",
		"gb32960.gd_fc_stack.stack_water_outlet_temp_c",
		"gb32960.gd_fc_stack.hydrogen_inlet_pressure_kpa",
	} {
		if _, exists := fieldsEnv.Fields[keptField]; !exists {
			t.Fatalf("fields envelope should keep current-state field %q: %#v", keptField, fieldsEnv.Fields)
		}
	}
}

func TestBuildFieldsEnvelopeRequiresIngressParsedFields(t *testing.T) {
	_, ok := BuildFieldsEnvelope(envelope.FrameEnvelope{
		Protocol:     envelope.ProtocolJT808,
		VIN:          "VIN001",
		MessageID:    "0x0200",
		EventTimeMS:  1000,
		ReceivedAtMS: 1100,
		Parsed: map[string]any{
			"location": map[string]any{"speed_kmh": 99},
		},
	})
	if ok {
		t.Fatal("BuildFieldsEnvelope() must not re-flatten parsed payload downstream")
	}
}

func TestRealtimeKVFieldsFromJT808ParsedFields(t *testing.T) {
	jtRows := realtimeKVFields(envelope.FrameEnvelope{
		Protocol: envelope.ProtocolJT808,
		VIN:      "VIN001",
		Fields: map[string]any{
			envelope.FieldLongitude:      121.1,
			envelope.FieldLatitude:       30.2,
			envelope.FieldTotalMileageKM: 10241.2,
		},
	}, map[string]any{
		"header": map[string]any{"message_id": "0x0200", "phone": "13307795425"},
		"location": map[string]any{
			"longitude":        121.1,
			"latitude":         30.2,
			"total_mileage_km": 10241.2,
			"additional":       []any{map[string]any{"id": "0x01", "value_hex": "00077235"}},
			"io_status": map[string]any{
				"value": uint16(2),
				"bits":  map[string]bool{"deep_sleep": false, "sleep": true},
			},
		},
	})
	jtValues := kvMap(jtRows)
	if jtValues["jt808.header/message_id"] != "0x0200" || jtValues["jt808.header/phone"] != "13307795425" {
		t.Fatalf("jt808 header kv missing: %#v", jtValues)
	}
	if jtValues["jt808.location/total_mileage_km"] != "10241.2" ||
		jtValues["jt808.location/longitude"] != "121.1" ||
		jtValues["jt808.location/additional.additional_1.id"] != "0x01" ||
		jtValues["jt808.location/io_status.bits.deep_sleep"] != "false" ||
		jtValues["jt808.location/io_status.bits.sleep"] != "true" {
		t.Fatalf("jt808 location kv missing: %#v", jtValues)
	}
	if _, exists := jtValues["jt808.location/io_status.bits"]; exists {
		t.Fatalf("JT808 bit fields must be flattened instead of embedded JSON: %#v", jtValues)
	}
	if jtValues["jt808.location/soc_percent"] != "" {
		t.Fatalf("jt808 kv should not include standardized env.Fields-only values: %#v", jtValues)
	}
}

func TestRealtimeKVFieldsFromYutongMQTTParsedFields(t *testing.T) {
	mqttRows := realtimeKVFields(envelope.FrameEnvelope{
		Protocol: envelope.ProtocolYutongMQTT,
		VIN:      "VIN002",
		Fields: map[string]any{
			envelope.FieldSOCPercent: 76,
			"gear":                   3,
		},
	}, map[string]any{
		"endpoint": "yutong",
		"topic":    "/ytforward/shln/3",
		"data": map[string]any{
			"ACC_PEDAL_APT":         14,
			"BATTERY_CAPACITY_SOC":  78.4,
			"CURRENT_OF_FC":         54.9,
			"HYDROGEN_LOW_PRESSURE": 1.41,
			"TOTAL_MILEAGE":         119925000,
			"fuelCellCoolInTempt":   59,
		},
		"root": map[string]any{
			"device":  "LMRKH9AC2R1004087",
			"version": "1.0",
		},
	})
	mqttValues := kvMap(mqttRows)
	if mqttValues["yutong_mqtt.data/acc_pedal_apt"] != "14" ||
		mqttValues["yutong_mqtt.data/current_of_fc"] != "54.9" ||
		mqttValues["yutong_mqtt.data/hydrogen_low_pressure"] != "1.41" ||
		mqttValues["yutong_mqtt.data/total_mileage"] != "119925000" ||
		mqttValues["yutong_mqtt.data/fuelcellcoolintempt"] != "59" {
		t.Fatalf("mqtt raw data kv missing: %#v", mqttValues)
	}
	if mqttValues["yutong_mqtt.root/device"] != "LMRKH9AC2R1004087" ||
		mqttValues["yutong_mqtt.metadata/endpoint"] != "yutong" ||
		mqttValues["yutong_mqtt.metadata/topic"] != "/ytforward/shln/3" {
		t.Fatalf("mqtt metadata kv missing: %#v", mqttValues)
	}
	if mqttValues["yutong_mqtt.data/soc_percent"] != "" || mqttValues["yutong_mqtt.data/gear"] != "" {
		t.Fatalf("mqtt kv should not include standardized env.Fields-only values: %#v", mqttValues)
	}
}

func kvMap(rows []RealtimeKVField) map[string]string {
	out := map[string]string{}
	for _, row := range rows {
		out[row.Domain+"/"+row.Field] = row.Value
	}
	return out
}
