package gb32960

import (
	"encoding/binary"
	"encoding/hex"
	"errors"
	"fmt"
	"strings"
	"time"

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

var (
	ErrFrameTooShort  = errors.New("gb32960 frame too short")
	ErrBadStartSymbol = errors.New("gb32960 bad start symbol")
	ErrBodyLength     = errors.New("gb32960 body length mismatch")
	ErrBCC            = errors.New("gb32960 bcc mismatch")
)

const (
	minFrameLen = 25
	headerLen   = 24
)

// ExtractFrames splits GB/T 32960 TCP streams using the protocol length field.
// Returned frames include the start symbols and trailing BCC byte.
func ExtractFrames(stream []byte) (frames [][]byte, remainder []byte, err error) {
	offset := 0
	for {
		start := indexStart(stream[offset:])
		if start < 0 {
			return frames, nil, nil
		}
		offset += start
		remaining := stream[offset:]
		if len(remaining) < headerLen {
			return frames, append([]byte(nil), remaining...), nil
		}
		bodyLen := int(binary.BigEndian.Uint16(remaining[22:24]))
		frameLen := frameLength(remaining, bodyLen)
		if len(remaining) < frameLen {
			return frames, append([]byte(nil), remaining...), nil
		}
		frames = append(frames, append([]byte(nil), remaining[:frameLen]...))
		offset += frameLen
		if offset >= len(stream) {
			return frames, nil, nil
		}
	}
}

func ParseFrame(raw []byte, receivedAtMS int64, sourceEndpoint string) (envelope.FrameEnvelope, error) {
	if len(raw) < minFrameLen {
		return envelope.FrameEnvelope{}, fmt.Errorf("%w: len=%d", ErrFrameTooShort, len(raw))
	}
	version, ok := version(raw[0], raw[1])
	if !ok {
		return envelope.FrameEnvelope{}, fmt.Errorf("%w: %s", ErrBadStartSymbol, hex.EncodeToString(raw[:2]))
	}
	bodyLen := int(binary.BigEndian.Uint16(raw[22:24]))
	actualBodyLen := len(raw) - headerLen - 1
	if len(raw) != headerLen+bodyLen+1 && !isExtendedPlatformLogin(raw[2], bodyLen, actualBodyLen) {
		return envelope.FrameEnvelope{}, fmt.Errorf("%w: declared=%d actual=%d", ErrBodyLength, bodyLen, len(raw)-headerLen-1)
	}
	if got, want := bcc(raw[2:len(raw)-1]), raw[len(raw)-1]; got != want {
		return envelope.FrameEnvelope{}, fmt.Errorf("%w: got=0x%02x want=0x%02x", ErrBCC, got, want)
	}

	command := raw[2]
	responseFlag := raw[3]
	vin := strings.TrimRight(string(raw[4:21]), "\x00 ")
	body := raw[headerLen : headerLen+actualBodyLen]
	parsed := map[string]any{
		"header": map[string]any{
			"version":            version,
			"command":            fmt.Sprintf("0x%02X", command),
			"response_flag":      fmt.Sprintf("0x%02X", responseFlag),
			"vin":                vin,
			"encrypt":            raw[21],
			"body_length":        bodyLen,
			"actual_body_length": actualBodyLen,
		},
	}
	fields := map[string]any{}
	eventTimeMS := receivedAtMS

	if command == 0x02 || command == 0x03 {
		eventTime, units := parseDataBody(version, body, fields)
		if !eventTime.IsZero() {
			eventTimeMS = eventTime.UnixMilli()
			fields["device_time"] = eventTime.Format(time.RFC3339)
			parsed["device_time"] = eventTime.Format(time.RFC3339)
		}
		parsed["data_units"] = units
	} else if command == 0x05 {
		login := parsePlatformLogin(body)
		parsed["platform_login"] = login
		if deviceTime, ok := login["login_time"].(string); ok && deviceTime != "" {
			fields["device_time"] = deviceTime
			parsed["device_time"] = deviceTime
		}
		if account, ok := login["username"].(string); ok && account != "" {
			fields["platform_account"] = account
		}
	}

	env := envelope.FrameEnvelope{
		Protocol:       envelope.ProtocolGB32960,
		MessageID:      fmt.Sprintf("0x%02X", command),
		VIN:            vin,
		SourceEndpoint: sourceEndpoint,
		EventTimeMS:    eventTimeMS,
		ReceivedAtMS:   receivedAtMS,
		RawHex:         strings.ToUpper(hex.EncodeToString(raw)),
		Parsed:         parsed,
		Fields:         fields,
		ParseStatus:    envelope.ParseOK,
	}
	env.EventID = env.StableEventID()
	return env, nil
}

func parseDataBody(version string, body []byte, fields map[string]any) (time.Time, []map[string]any) {
	if len(body) < 6 {
		return time.Time{}, nil
	}
	eventTime := parseGBTime(body[:6])
	cursor := 6
	var units []map[string]any
	for cursor < len(body) {
		unitType := body[cursor]
		cursor++
		switch unitType {
		case 0x01:
			size := 20
			if version == "V2025" {
				size = 18
			}
			if len(body[cursor:]) < size {
				units = append(units, map[string]any{"type": "0x01", "error": "truncated"})
				return eventTime, units
			}
			unit := parseVehicleData(body[cursor : cursor+size])
			units = append(units, map[string]any{"type": "0x01", "name": "vehicle", "value": unit})
			fields["vehicle_status"] = unit["vehicle_status"]
			fields["charge_status"] = unit["charge_status"]
			fields["running_mode"] = unit["running_mode"]
			fields[envelope.FieldSpeedKMH] = unit["speed_kmh"]
			fields[envelope.FieldTotalMileageKM] = unit["total_mileage_km"]
			fields[envelope.FieldSOCPercent] = unit["soc_percent"]
			cursor += size
		case 0x05:
			if len(body[cursor:]) < 9 {
				units = append(units, map[string]any{"type": "0x05", "error": "truncated"})
				return eventTime, units
			}
			unit := parsePositionData(body[cursor : cursor+9])
			units = append(units, map[string]any{"type": "0x05", "name": "position", "value": unit})
			fields["position_status"] = unit["position_status"]
			fields[envelope.FieldLongitude] = unit["longitude"]
			fields[envelope.FieldLatitude] = unit["latitude"]
			cursor += 9
		case 0x02:
			if len(body[cursor:]) < 1 {
				units = append(units, map[string]any{"type": "0x02", "error": "truncated"})
				return eventTime, units
			}
			size := 1 + int(body[cursor])*12
			if len(body[cursor:]) < size {
				units = append(units, map[string]any{"type": "0x02", "error": "truncated"})
				return eventTime, units
			}
			unit := parseDriveMotorData(body[cursor : cursor+size])
			units = append(units, map[string]any{"type": "0x02", "name": "drive_motor", "value": unit})
			cursor += size
		case 0x03:
			if len(body[cursor:]) < 8 {
				units = append(units, map[string]any{"type": "0x03", "error": "truncated"})
				return eventTime, units
			}
			probeCount := int(binary.BigEndian.Uint16(body[cursor+6 : cursor+8]))
			size := 8 + probeCount + 10
			if len(body[cursor:]) < size {
				units = append(units, map[string]any{"type": "0x03", "error": "truncated"})
				return eventTime, units
			}
			unit := parseFuelCellData(body[cursor : cursor+size])
			units = append(units, map[string]any{"type": "0x03", "name": "fuel_cell", "value": unit})
			fields["fuel_cell_hydrogen_consumption_kg_per_100km"] = unit["hydrogen_consumption_kg_per_100km"]
			fields["fuel_cell_voltage_v"] = unit["fuel_cell_voltage_v"]
			fields["fuel_cell_current_a"] = unit["fuel_cell_current_a"]
			cursor += size
		case 0x04:
			if len(body[cursor:]) < 5 {
				units = append(units, map[string]any{"type": "0x04", "error": "truncated"})
				return eventTime, units
			}
			unit := parseEngineData(body[cursor : cursor+5])
			units = append(units, map[string]any{"type": "0x04", "name": "engine", "value": unit})
			cursor += 5
		case 0x06:
			if len(body[cursor:]) < 14 {
				units = append(units, map[string]any{"type": "0x06", "error": "truncated"})
				return eventTime, units
			}
			unit := parseExtremeData(body[cursor : cursor+14])
			units = append(units, map[string]any{"type": "0x06", "name": "extreme", "value": unit})
			cursor += 14
		case 0x07:
			size, ok := alarmDataSize(body[cursor:])
			if !ok {
				units = append(units, map[string]any{"type": "0x07", "error": "truncated"})
				return eventTime, units
			}
			unit := parseAlarmData(body[cursor : cursor+size])
			units = append(units, map[string]any{"type": "0x07", "name": "alarm", "value": unit})
			cursor += size
		case 0x08:
			size, ok := voltageDataSize(body[cursor:])
			if !ok {
				units = append(units, map[string]any{"type": "0x08", "error": "truncated"})
				return eventTime, units
			}
			unit := parseVoltageData(body[cursor : cursor+size])
			units = append(units, map[string]any{"type": "0x08", "name": "voltage", "value": unit})
			cursor += size
		case 0x09:
			size, ok := temperatureDataSize(body[cursor:])
			if !ok {
				units = append(units, map[string]any{"type": "0x09", "error": "truncated"})
				return eventTime, units
			}
			unit := parseTemperatureData(body[cursor : cursor+size])
			units = append(units, map[string]any{"type": "0x09", "name": "temperature", "value": unit})
			cursor += size
		case 0x30:
			size, ok := gdFCStackDataSize(body[cursor:])
			if !ok {
				units = append(units, map[string]any{"type": "0x30", "error": "truncated"})
				return eventTime, units
			}
			unit := parseGdFCStackData(body[cursor : cursor+size])
			units = append(units, map[string]any{"type": "0x30", "name": "gd_fc_stack", "value": unit})
			if summaries, ok := unit["summaries"].([]map[string]any); ok && len(summaries) > 0 {
				fields["gd_fc_stack_hydrogen_inlet_pressure_kpa"] = summaries[0]["hydrogen_inlet_pressure_kpa"]
				fields["gd_fc_stack_water_outlet_temp_c"] = summaries[0]["stack_water_outlet_temp_c"]
				fields["gd_fc_stack_cell_count"] = summaries[0]["cell_count"]
				fields["gd_fc_stack_frame_cell_count"] = summaries[0]["frame_cell_count"]
			}
			cursor += size
		case 0x31:
			size, ok := gdFCAuxiliaryDataSize(body[cursor:])
			if !ok {
				units = append(units, map[string]any{"type": "0x31", "error": "truncated"})
				return eventTime, units
			}
			unit := parseGdFCAuxiliaryData(body[cursor : cursor+size])
			units = append(units, map[string]any{"type": "0x31", "name": "gd_fc_auxiliary", "value": unit})
			cursor += size
		case 0x32:
			if len(body[cursor:]) < 9 {
				units = append(units, map[string]any{"type": "0x32", "error": "truncated"})
				return eventTime, units
			}
			unit := parseGdFCDcDcData(body[cursor : cursor+9])
			units = append(units, map[string]any{"type": "0x32", "name": "gd_fc_dcdc", "value": unit})
			fields["gd_fc_dcdc_controller_temp_c"] = unit["controller_temp_c"]
			cursor += 9
		case 0x33:
			if len(body[cursor:]) < 5 {
				units = append(units, map[string]any{"type": "0x33", "error": "truncated"})
				return eventTime, units
			}
			unit := parseGdFCAirConditionerData(body[cursor : cursor+5])
			units = append(units, map[string]any{"type": "0x33", "name": "gd_fc_air_conditioner", "value": unit})
			cursor += 5
		case 0x34:
			if len(body[cursor:]) < 7 {
				units = append(units, map[string]any{"type": "0x34", "error": "truncated"})
				return eventTime, units
			}
			unit := parseGdFCVehicleInfoData(body[cursor : cursor+7])
			units = append(units, map[string]any{"type": "0x34", "name": "gd_fc_vehicle_info", "value": unit})
			fields["gd_fc_vehicle_hydrogen_mass_kg"] = unit["hydrogen_mass_kg"]
			cursor += 7
		case 0x80:
			if len(body[cursor:]) < 11 {
				units = append(units, map[string]any{"type": "0x80", "error": "truncated"})
				return eventTime, units
			}
			unit := parseGdFCDemoExtensionData(body[cursor : cursor+11])
			units = append(units, map[string]any{"type": "0x80", "name": "gd_fc_demo_extension", "value": unit})
			fields["gd_fc_demo_stack_temp_c"] = unit["stack_temp_c"]
			cursor += 11
		case 0x83:
			size, ok := gdFCVendorTLVDataSize(body[cursor:])
			if !ok {
				units = append(units, map[string]any{"type": "0x83", "error": "truncated"})
				return eventTime, units
			}
			unit := parseGdFCVendorTLVData(0x83, body[cursor:cursor+size])
			units = append(units, map[string]any{"type": "0x83", "name": "gd_fc_vendor_tlv", "value": unit})
			cursor += size
		default:
			units = append(units, map[string]any{
				"type":      fmt.Sprintf("0x%02X", unitType),
				"raw_tail":  strings.ToUpper(hex.EncodeToString(body[cursor:])),
				"parse":     "unknown_unit",
				"byte_size": len(body) - cursor,
			})
			return eventTime, units
		}
	}
	return eventTime, units
}

func frameLength(frameStart []byte, bodyLen int) int {
	standardLen := headerLen + bodyLen + 1
	if len(frameStart) < headerLen || frameStart[2] != 0x05 || bodyLen != 41 {
		return standardLen
	}
	// Some field platform-login frames declare the standard 41-byte body but
	// carry a 32-byte password, making the actual body 53 bytes. Only consume
	// the extended form when its own BCC verifies so the splitter does not eat
	// bytes from the next frame.
	extendedLen := headerLen + 53 + 1
	if len(frameStart) < extendedLen {
		return standardLen
	}
	extended := frameStart[:extendedLen]
	if bcc(extended[2:len(extended)-1]) == extended[len(extended)-1] {
		return extendedLen
	}
	return standardLen
}

func isExtendedPlatformLogin(command byte, declaredBodyLen int, actualBodyLen int) bool {
	return command == 0x05 && declaredBodyLen == 41 && actualBodyLen == 53
}

func parsePlatformLogin(body []byte) map[string]any {
	login := map[string]any{
		"raw_body_length": len(body),
	}
	if len(body) < 9 {
		login["error"] = "truncated"
		return login
	}
	loginTime := parseGBTime(body[:6])
	if !loginTime.IsZero() {
		login["login_time"] = loginTime.Format(time.RFC3339)
	}
	login["serial_no"] = binary.BigEndian.Uint16(body[6:8])
	if len(body) >= 21 {
		login["username"] = trimASCII(body[8:20])
	}
	if len(body) > 21 {
		passwordEnd := len(body) - 1
		login["password"] = trimASCII(body[20:passwordEnd])
		login["encrypt_rule"] = body[passwordEnd]
	}
	return login
}

func parseVehicleData(data []byte) map[string]any {
	return map[string]any{
		"vehicle_status":   int(data[0]),
		"charge_status":    int(data[1]),
		"running_mode":     int(data[2]),
		"speed_kmh":        scaledU16(data[3:5], 10),
		"total_mileage_km": scaledU32(data[5:9], 10),
		"total_voltage_v":  scaledU16(data[9:11], 10),
		"total_current_a":  scaledU16WithOffset(data[11:13], 10, -1000),
		"soc_percent":      int(data[13]),
		"dc_dc_status":     int(data[14]),
		"gear":             int(data[15]),
		"insulation_kohm":  binary.BigEndian.Uint16(data[16:18]),
		"accelerator_pct":  int(data[18]),
		"brake_pct":        int(data[19]),
	}
}

func parseDriveMotorData(data []byte) map[string]any {
	count := int(data[0])
	motors := make([]map[string]any, 0, count)
	cursor := 1
	for i := 0; i < count; i++ {
		motors = append(motors, map[string]any{
			"serial_no":                int(data[cursor]),
			"state":                    int(data[cursor+1]),
			"controller_temperature_c": int(data[cursor+2]) - 40,
			"speed_rpm":                int(binary.BigEndian.Uint16(data[cursor+3:cursor+5])) - 20000,
			"torque_nm":                scaledIntWithOffset(int64(binary.BigEndian.Uint16(data[cursor+5:cursor+7])), 10, -20000),
			"motor_temperature_c":      int(data[cursor+7]) - 40,
			"controller_voltage_v":     scaledU16(data[cursor+8:cursor+10], 10),
			"controller_current_a":     scaledU16WithOffset(data[cursor+10:cursor+12], 10, -1000),
		})
		cursor += 12
	}
	return map[string]any{
		"count":  count,
		"motors": motors,
	}
}

func parseFuelCellData(data []byte) map[string]any {
	probeCount := int(binary.BigEndian.Uint16(data[6:8]))
	probes := make([]int, 0, probeCount)
	cursor := 8
	for i := 0; i < probeCount; i++ {
		probes = append(probes, int(data[cursor])-40)
		cursor++
	}
	out := map[string]any{
		"fuel_cell_voltage_v":                 scaledU16(data[0:2], 10),
		"fuel_cell_current_a":                 scaledU16(data[2:4], 10),
		"hydrogen_consumption_kg_per_100km":   scaledU16(data[4:6], 100),
		"temperature_probe_count":             probeCount,
		"temperature_probe_values_c":          probes,
		"max_hydrogen_temperature_c":          scaledU16WithOffset(data[cursor:cursor+2], 10, -40),
		"max_hydrogen_temperature_probe_id":   int(data[cursor+2]),
		"max_hydrogen_concentration_fraction": scaledU16(data[cursor+3:cursor+5], 1_000_000),
		"max_hydrogen_concentration_probe_id": int(data[cursor+5]),
		"max_hydrogen_pressure_mpa":           scaledU16(data[cursor+6:cursor+8], 10),
		"max_hydrogen_pressure_probe_id":      int(data[cursor+8]),
		"dc_dc_status":                        int(data[cursor+9]),
	}
	return out
}

func parseEngineData(data []byte) map[string]any {
	return map[string]any{
		"engine_status":   int(data[0]),
		"crank_speed_rpm": binary.BigEndian.Uint16(data[1:3]),
		"fuel_rate":       binary.BigEndian.Uint16(data[3:5]),
	}
}

func parseExtremeData(data []byte) map[string]any {
	return map[string]any{
		"max_voltage_subsystem_no": int(data[0]),
		"max_voltage_cell_no":      int(data[1]),
		"max_voltage_v":            scaledU16(data[2:4], 1000),
		"min_voltage_subsystem_no": int(data[4]),
		"min_voltage_cell_no":      int(data[5]),
		"min_voltage_v":            scaledU16(data[6:8], 1000),
		"max_temp_subsystem_no":    int(data[8]),
		"max_temp_probe_no":        int(data[9]),
		"max_temp_c":               int(data[10]) - 40,
		"min_temp_subsystem_no":    int(data[11]),
		"min_temp_probe_no":        int(data[12]),
		"min_temp_c":               int(data[13]) - 40,
	}
}

func alarmDataSize(data []byte) (int, bool) {
	if len(data) < 5 {
		return 0, false
	}
	cursor := 5
	for i := 0; i < 4; i++ {
		if len(data[cursor:]) < 1 {
			return 0, false
		}
		count := int(data[cursor])
		cursor++
		cursor += count * 4
		if cursor > len(data) {
			return 0, false
		}
	}
	return cursor, true
}

func parseAlarmData(data []byte) map[string]any {
	cursor := 5
	readList := func() []string {
		count := int(data[cursor])
		cursor++
		values := make([]string, 0, count)
		for i := 0; i < count; i++ {
			values = append(values, fmt.Sprintf("0x%08X", binary.BigEndian.Uint32(data[cursor:cursor+4])))
			cursor += 4
		}
		return values
	}
	return map[string]any{
		"max_alarm_level":    int(data[0]),
		"general_alarm_flag": fmt.Sprintf("0x%08X", binary.BigEndian.Uint32(data[1:5])),
		"battery_faults":     readList(),
		"motor_faults":       readList(),
		"engine_faults":      readList(),
		"other_faults":       readList(),
	}
}

func voltageDataSize(data []byte) (int, bool) {
	if len(data) < 1 {
		return 0, false
	}
	cursor := 1
	for i := 0; i < int(data[0]); i++ {
		if len(data[cursor:]) < 10 {
			return 0, false
		}
		frameCellCount := int(data[cursor+9])
		cursor += 10 + frameCellCount*2
		if cursor > len(data) {
			return 0, false
		}
	}
	return cursor, true
}

func parseVoltageData(data []byte) map[string]any {
	subCount := int(data[0])
	cursor := 1
	totalVoltageTenths := int64(0)
	maxCell := 0.0
	minCell := 0.0
	cellSeen := false
	for i := 0; i < subCount; i++ {
		totalVoltageTenths += int64(binary.BigEndian.Uint16(data[cursor+1 : cursor+3]))
		cellCount := int(binary.BigEndian.Uint16(data[cursor+5 : cursor+7]))
		frameCellCount := int(data[cursor+9])
		cursor += 10
		for c := 0; c < frameCellCount; c++ {
			voltage := scaledU16(data[cursor:cursor+2], 1000)
			if !cellSeen || voltage > maxCell {
				maxCell = voltage
			}
			if !cellSeen || voltage < minCell {
				minCell = voltage
			}
			cellSeen = true
			cursor += 2
		}
		_ = cellCount
	}
	return map[string]any{
		"subsystem_count": subCount,
		"total_voltage_v": scaledInt(totalVoltageTenths, 10),
		"max_cell_v":      maxCell,
		"min_cell_v":      minCell,
	}
}

func temperatureDataSize(data []byte) (int, bool) {
	if len(data) < 1 {
		return 0, false
	}
	cursor := 1
	for i := 0; i < int(data[0]); i++ {
		if len(data[cursor:]) < 3 {
			return 0, false
		}
		probeCount := int(binary.BigEndian.Uint16(data[cursor+1 : cursor+3]))
		cursor += 3 + probeCount
		if cursor > len(data) {
			return 0, false
		}
	}
	return cursor, true
}

func parseTemperatureData(data []byte) map[string]any {
	subCount := int(data[0])
	cursor := 1
	maxTemp := 0
	minTemp := 0
	seen := false
	for i := 0; i < subCount; i++ {
		probeCount := int(binary.BigEndian.Uint16(data[cursor+1 : cursor+3]))
		cursor += 3
		for p := 0; p < probeCount; p++ {
			temp := int(data[cursor]) - 40
			if !seen || temp > maxTemp {
				maxTemp = temp
			}
			if !seen || temp < minTemp {
				minTemp = temp
			}
			seen = true
			cursor++
		}
	}
	return map[string]any{
		"subsystem_count": subCount,
		"max_temp_c":      maxTemp,
		"min_temp_c":      minTemp,
	}
}

func gdFCStackDataSize(data []byte) (int, bool) {
	if len(data) < 1 {
		return 0, false
	}
	cursor := 1
	for i := 0; i < int(data[0]); i++ {
		if len(data[cursor:]) < 22 {
			return 0, false
		}
		frameCellCount := int(data[cursor+21])
		cursor += 22 + frameCellCount*2
		if cursor > len(data) {
			return 0, false
		}
	}
	return cursor, true
}

func parseGdFCStackData(data []byte) map[string]any {
	count := int(data[0])
	cursor := 1
	summaries := make([]map[string]any, 0, count)
	for i := 0; i < count; i++ {
		frameCellCount := int(data[cursor+21])
		summary := map[string]any{
			"engine_work_state":           int(data[cursor]),
			"stack_water_outlet_temp_c":   nullableTempOffset40(data[cursor+1]),
			"hydrogen_inlet_pressure_kpa": nullableScaledU16WithOffset(data[cursor+2:cursor+4], 10, -100),
			"air_inlet_pressure_kpa":      nullableScaledU16WithOffset(data[cursor+4:cursor+6], 10, -100),
			"air_inlet_temp_c":            nullableTempOffset40(data[cursor+6]),
			"max_cell_voltage_id":         nullableU16(data[cursor+7 : cursor+9]),
			"min_cell_voltage_id":         nullableU16(data[cursor+9 : cursor+11]),
			"max_cell_voltage_v":          nullableScaledU16(data[cursor+11:cursor+13], 1000),
			"min_cell_voltage_v":          nullableScaledU16(data[cursor+13:cursor+15], 1000),
			"avg_cell_voltage_v":          nullableScaledU16(data[cursor+15:cursor+17], 1000),
			"cell_count":                  nullableU16(data[cursor+17 : cursor+19]),
			"frame_cell_start":            nullableU16(data[cursor+19 : cursor+21]),
			"frame_cell_count":            frameCellCount,
		}
		cursor += 22
		maxCell := 0.0
		minCell := 0.0
		seen := false
		for c := 0; c < frameCellCount; c++ {
			voltage := scaledU16(data[cursor:cursor+2], 1000)
			if !seen || voltage > maxCell {
				maxCell = voltage
			}
			if !seen || voltage < minCell {
				minCell = voltage
			}
			seen = true
			cursor += 2
		}
		if seen {
			summary["frame_max_cell_voltage_v"] = maxCell
			summary["frame_min_cell_voltage_v"] = minCell
		}
		summaries = append(summaries, summary)
	}
	return map[string]any{
		"stack_count": count,
		"summaries":   summaries,
	}
}

func gdFCAuxiliaryDataSize(data []byte) (int, bool) {
	if len(data) < 1 {
		return 0, false
	}
	size := 1 + int(data[0])*16
	return size, len(data) >= size
}

func parseGdFCAuxiliaryData(data []byte) map[string]any {
	count := int(data[0])
	cursor := 1
	subsystems := make([]map[string]any, 0, count)
	for i := 0; i < count; i++ {
		subsystems = append(subsystems, map[string]any{
			"air_compressor_motor_voltage_v": nullableScaledU16(data[cursor:cursor+2], 10),
			"air_compressor_power_kw":        nullableU16WithOffset(data[cursor+2:cursor+4], -5),
			"hydrogen_pump_motor_voltage_v":  nullableScaledU16(data[cursor+4:cursor+6], 5),
			"hydrogen_pump_power_kw":         nullableU16WithOffset(data[cursor+6:cursor+8], -5),
			"water_pump_voltage_v":           nullableScaledU16(data[cursor+8:cursor+10], 10),
			"ptc_voltage_v":                  nullableScaledU16(data[cursor+10:cursor+12], 10),
			"ptc_power_kw":                   nullableU16WithOffset(data[cursor+12:cursor+14], -5),
			"low_voltage_battery_voltage_v":  nullableScaledU16(data[cursor+14:cursor+16], 10),
		})
		cursor += 16
	}
	return map[string]any{
		"subsystem_count": count,
		"subsystems":      subsystems,
	}
}

func parseGdFCDcDcData(data []byte) map[string]any {
	return map[string]any{
		"input_voltage_v":   nullableScaledU16(data[0:2], 10),
		"input_current_a":   nullableScaledU16(data[2:4], 10),
		"output_voltage_v":  nullableScaledU16(data[4:6], 10),
		"output_current_a":  nullableScaledU16(data[6:8], 10),
		"controller_temp_c": nullableTempOffset40(data[8]),
	}
}

func parseGdFCAirConditionerData(data []byte) map[string]any {
	return map[string]any{
		"status":                     int(data[0]),
		"power_kw":                   nullableU16WithOffset(data[1:3], -5),
		"compressor_input_voltage_v": nullableScaledU16(data[3:5], 10),
	}
}

func parseGdFCVehicleInfoData(data []byte) map[string]any {
	return map[string]any{
		"collision_alarm":      int(data[0]),
		"ambient_temp_c":       nullableU16WithOffset(data[1:3], -40),
		"ambient_pressure_kpa": nullableScaledU16WithOffset(data[3:5], 10, -100),
		"hydrogen_mass_kg":     nullableScaledU16(data[5:7], 10),
	}
}

func parseGdFCDemoExtensionData(data []byte) map[string]any {
	return map[string]any{
		"declared_length":          int(binary.BigEndian.Uint16(data[0:2])),
		"stack_temp_c":             nullableTempOffset40(data[2]),
		"air_compressor_voltage_v": nullableScaledU16(data[3:5], 10),
		"air_compressor_current_a": nullableScaledU16WithOffset(data[5:7], 10, -1000),
		"hydrogen_pump_voltage_v":  nullableScaledU16(data[7:9], 10),
		"hydrogen_pump_current_a":  nullableScaledU16WithOffset(data[9:11], 10, -1000),
	}
}

func gdFCVendorTLVDataSize(data []byte) (int, bool) {
	if len(data) < 2 {
		return 0, false
	}
	size := 2 + int(binary.BigEndian.Uint16(data[0:2]))
	return size, len(data) >= size
}

func parseGdFCVendorTLVData(typeCode byte, data []byte) map[string]any {
	length := int(binary.BigEndian.Uint16(data[0:2]))
	return map[string]any{
		"type":            fmt.Sprintf("0x%02X", typeCode),
		"declared_length": length,
		"payload_hex":     strings.ToUpper(hex.EncodeToString(data[2:])),
	}
}

func nullableU16(data []byte) any {
	value := binary.BigEndian.Uint16(data)
	if value == 0xfffe || value == 0xffff {
		return nil
	}
	return int(value)
}

func nullableU16WithOffset(data []byte, offset int) any {
	value := binary.BigEndian.Uint16(data)
	if value == 0xfffe || value == 0xffff {
		return nil
	}
	return int(value) + offset
}

func scaledU16(data []byte, divisor int64) float64 {
	return scaledInt(int64(binary.BigEndian.Uint16(data)), divisor)
}

func scaledU32(data []byte, divisor int64) float64 {
	return scaledInt(int64(binary.BigEndian.Uint32(data)), divisor)
}

func scaledU16WithOffset(data []byte, divisor int64, offset int64) float64 {
	return scaledIntWithOffset(int64(binary.BigEndian.Uint16(data)), divisor, offset)
}

func scaledIntWithOffset(value int64, divisor int64, offset int64) float64 {
	return scaledInt(value+offset*divisor, divisor)
}

func scaledInt(value int64, divisor int64) float64 {
	if divisor <= 0 {
		return float64(value)
	}
	return float64(value) / float64(divisor)
}

func nullableScaledU16(data []byte, divisor int64) any {
	value := binary.BigEndian.Uint16(data)
	if value == 0xfffe || value == 0xffff {
		return nil
	}
	return scaledInt(int64(value), divisor)
}

func nullableScaledU16WithOffset(data []byte, divisor int64, offset int64) any {
	value := binary.BigEndian.Uint16(data)
	if value == 0xfffe || value == 0xffff {
		return nil
	}
	return scaledIntWithOffset(int64(value), divisor, offset)
}

func nullableTempOffset40(value byte) any {
	if value == 0xfe || value == 0xff {
		return nil
	}
	return int(value) - 40
}

func parsePositionData(data []byte) map[string]any {
	return map[string]any{
		"position_status": int(data[0]),
		"longitude":       float64(binary.BigEndian.Uint32(data[1:5])) / 1_000_000,
		"latitude":        float64(binary.BigEndian.Uint32(data[5:9])) / 1_000_000,
	}
}

func indexStart(data []byte) int {
	for i := 0; i+1 < len(data); i++ {
		if _, ok := version(data[i], data[i+1]); ok {
			return i
		}
	}
	return -1
}

func version(a byte, b byte) (string, bool) {
	switch {
	case a == '#' && b == '#':
		return "V2016", true
	case a == '$' && b == '$':
		return "V2025", true
	default:
		return "", false
	}
}

func parseGBTime(data []byte) time.Time {
	if len(data) != 6 {
		return time.Time{}
	}
	year := 2000 + int(data[0])
	month := time.Month(data[1])
	day := int(data[2])
	hour := int(data[3])
	minute := int(data[4])
	second := int(data[5])
	if month < 1 || month > 12 || day < 1 || day > 31 || hour > 23 || minute > 59 || second > 59 {
		return time.Time{}
	}
	return time.Date(year, month, day, hour, minute, second, 0, time.FixedZone("Asia/Shanghai", 8*3600))
}

func trimASCII(data []byte) string {
	return strings.TrimRight(string(data), "\x00 ")
}

func bcc(data []byte) byte {
	var out byte
	for _, value := range data {
		out ^= value
	}
	return out
}
