package com.lingniu.ingest.protocol.gb32960.codec;

import com.lingniu.ingest.api.spi.DecodeException;
import com.lingniu.ingest.protocol.gb32960.model.CommandBody;
import com.lingniu.ingest.protocol.gb32960.model.CommandType;
import com.lingniu.ingest.protocol.gb32960.model.EncryptType;

import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 非实时上报的命令数据单元解析器。覆盖：
 * <ul>
 *   <li>0x01 车辆登入（表 6）
 *   <li>0x04 车辆登出（表 28）
 *   <li>0x05 平台登入（表 29）
 *   <li>0x06 平台登出（表 30）
 *   <li>0x07 心跳（空）
 *   <li>0x08 终端校时（空）
 *   <li>0x09 激活（表 B.3）
 *   <li>0x0A 激活应答（表 B.4）
 *   <li>0x0B 密钥交换（表 31）
 * </ul>
 *
 * <p>其它命令（查询/设置/控制/自定义）保留为 {@link CommandBody.RawCommand}。
 */
public final class Gb32960CommandParser {

    private static final ZoneId ZONE_GMT8 = ZoneId.of("Asia/Shanghai");

    public CommandBody parse(CommandType command, ByteBuffer buf) {
        return switch (command) {
            case VEHICLE_LOGIN -> parseVehicleLogin(buf);
            case VEHICLE_LOGOUT -> parseVehicleLogout(buf);
            case PLATFORM_LOGIN -> parsePlatformLogin(buf);
            case PLATFORM_LOGOUT -> parsePlatformLogout(buf);
            case HEARTBEAT -> new CommandBody.Heartbeat();
            case TIME_CALIBRATION -> new CommandBody.TimeCalibration();
            case ACTIVATION -> parseActivation(buf);
            case ACTIVATION_RESPONSE -> parseActivationResponse(buf);
            case KEY_EXCHANGE -> parseKeyExchange(buf);
            case QUERY -> parseQueryParams(buf);
            case SET_PARAMS -> parseSetParams(buf);
            case TERMINAL_CONTROL -> parseTerminalControl(buf);
            default -> captureRaw(command, buf);
        };
    }

    /** 表 6：车辆登入。 */
    private CommandBody.VehicleLogin parseVehicleLogin(ByteBuffer buf) {
        Instant time = readTime(buf);
        int serialNo = buf.getShort() & 0xFFFF;
        String iccid = readAscii(buf, 20);
        // 2025 版新增：电池管理系统数 n + 各系统包数 + 编码列表
        int batterySystemCount = buf.hasRemaining() ? (buf.get() & 0xFF) : 0;
        List<Integer> packCounts = new ArrayList<>();
        int totalPacks = 0;
        for (int i = 0; i < batterySystemCount && buf.hasRemaining(); i++) {
            int c = buf.get() & 0xFF;
            packCounts.add(c);
            totalPacks += c;
        }
        List<String> codes = new ArrayList<>(totalPacks);
        for (int i = 0; i < totalPacks && buf.remaining() >= 24; i++) {
            codes.add(readAscii(buf, 24));
        }
        return new CommandBody.VehicleLogin(time, serialNo, iccid, batterySystemCount, packCounts, codes);
    }

    private CommandBody.VehicleLogout parseVehicleLogout(ByteBuffer buf) {
        return new CommandBody.VehicleLogout(readTime(buf), buf.getShort() & 0xFFFF);
    }

    private CommandBody.PlatformLogin parsePlatformLogin(ByteBuffer buf) {
        Instant time = readTime(buf);
        int serial = buf.getShort() & 0xFFFF;
        String user = readAscii(buf, 12);
        // 标准密码字段为 20B，但现场存在扩展到 32B 的平台登入包；这里按剩余长度-1 读取，
        // 最后一字节仍保留给加密规则，兼容两种形态。
        int passwordLength = Math.max(0, buf.remaining() - 1);
        String pwd = readAscii(buf, passwordLength);
        EncryptType enc = EncryptType.of(buf.get() & 0xFF);
        return new CommandBody.PlatformLogin(time, serial, user, pwd, enc);
    }

    private CommandBody.PlatformLogout parsePlatformLogout(ByteBuffer buf) {
        return new CommandBody.PlatformLogout(readTime(buf), buf.getShort() & 0xFFFF);
    }

    private CommandBody.Activation parseActivation(ByteBuffer buf) {
        Instant time = readTime(buf);
        String chipId = readAscii(buf, 16);
        int keyLen = buf.getShort() & 0xFFFF;
        byte[] pubKey = new byte[Math.min(keyLen, buf.remaining())];
        buf.get(pubKey);
        String vin = readAscii(buf, 17);
        byte[] signature = new byte[buf.remaining()];
        buf.get(signature);
        return new CommandBody.Activation(time, chipId, pubKey, vin, signature);
    }

    private CommandBody.ActivationResponse parseActivationResponse(ByteBuffer buf) {
        return new CommandBody.ActivationResponse(buf.get() & 0xFF, buf.get() & 0xFF);
    }

    private CommandBody.KeyExchange parseKeyExchange(ByteBuffer buf) {
        EncryptType keyType = EncryptType.of(buf.get() & 0xFF);
        int keyLen = buf.getShort() & 0xFFFF;
        byte[] key = new byte[Math.min(keyLen, buf.remaining() - 12)];
        buf.get(key);
        Instant activateAt = readTime(buf);
        Instant expireAt = readTime(buf);
        return new CommandBody.KeyExchange(keyType, key, activateAt, expireAt);
    }

    private CommandBody.RawCommand captureRaw(CommandType command, ByteBuffer buf) {
        byte[] bytes = new byte[buf.remaining()];
        buf.get(bytes);
        return new CommandBody.RawCommand(command.code(), bytes);
    }

    // =========================================================================
    // 表 B.5-B.13 下行命令
    // =========================================================================

    /**
     * 参数查询 0x80（表 B.5）：{@code time(6) + count(1) + paramId(count)}。
     * 如果 count > 0，则是查询命令；如果 count == 0 可能是终端应答（表 B.6），
     * 应答带参数项列表（表 B.7），paramValue 长度根据 paramId 查表 B.8 决定。
     */
    private CommandBody parseQueryParams(ByteBuffer buf) {
        Instant time = readTime(buf);
        int count = buf.get() & 0xFF;

        // 判定是查询命令还是应答：命令方向下 count 后紧跟 paramId 列表（每项 1 字节）；
        // 应答方向下 count 后是完整的 paramId + paramValue 列表（表 B.7）。
        // 通过剩余字节数判断：命令形态 remaining == count，应答形态 remaining > count
        if (buf.remaining() == count) {
            List<Integer> ids = new ArrayList<>(count);
            for (int i = 0; i < count; i++) ids.add(buf.get() & 0xFF);
            return new CommandBody.QueryParams(time, ids);
        }
        // 作为应答处理
        Map<Integer, byte[]> params = readParamItems(buf, count);
        return new CommandBody.QueryParamsResponse(time, params);
    }

    /** 参数设置 0x81（表 B.9）：{@code time(6) + count(1) + paramItems}。 */
    private CommandBody.SetParams parseSetParams(ByteBuffer buf) {
        Instant time = readTime(buf);
        int count = buf.get() & 0xFF;
        Map<Integer, byte[]> params = readParamItems(buf, count);
        return new CommandBody.SetParams(time, params);
    }

    /** 读取 N 个参数项（表 B.7）：{@code paramId(1) + paramValue(按表 B.8 解释)}。 */
    private Map<Integer, byte[]> readParamItems(ByteBuffer buf, int count) {
        Map<Integer, byte[]> out = new HashMap<>(count);
        for (int i = 0; i < count && buf.hasRemaining(); i++) {
            int paramId = buf.get() & 0xFF;
            int len = paramValueLength(paramId, buf);
            if (len < 0 || len > buf.remaining()) {
                // 长度未知或越界：吞剩余字节作为该参数值后终止
                byte[] rest = new byte[buf.remaining()];
                buf.get(rest);
                out.put(paramId, rest);
                break;
            }
            byte[] value = new byte[len];
            buf.get(value);
            out.put(paramId, value);
        }
        return out;
    }

    /**
     * 按表 B.8 返回参数值长度。
     *
     * <p>对于变长参数（0x05 域名 / 0x0E 公共平台域名），前一个参数（0x04/0x0D）给出长度；
     * 这里简化处理：变长参数使用 {@code buf.get() & 0xFF + 1} 预读 + 取值。
     */
    private int paramValueLength(int paramId, ByteBuffer buf) {
        return switch (paramId) {
            case 0x01 -> 2; // 本地存储时间周期 WORD
            case 0x02 -> 1; // 正常信息上报时间周期
            case 0x03 -> 1; // 报警信息上报时间周期
            case 0x04 -> 1; // 平台域名长度 M
            case 0x05 -> {
                // 平台域名 M 字节，M 由前一个参数的值给出。
                // 安全起见这里一次吞掉剩余字节（实际场景参数 0x04 和 0x05 通常一起下发）
                yield buf.remaining();
            }
            case 0x06 -> 2; // 平台端口 WORD
            case 0x07, 0x08 -> 5; // 硬件/固件版本 5 字节
            case 0x09 -> 1; // 心跳发送周期
            case 0x0A -> 2; // 终端应答超时时间
            case 0x0B -> 2; // 平台应答超时时间
            case 0x0C -> 1; // 登入失败后下次登入间隔
            case 0x0D -> 1; // 公共平台域名长度 N
            case 0x0E -> buf.remaining(); // 公共平台域名
            case 0x0F -> 2; // 公共平台端口
            case 0x10 -> 1; // 是否抽样监测中
            default -> -1; // 未知参数：无法决定长度
        };
    }

    /** 车载终端控制 0x82（表 B.10）。 */
    private CommandBody.TerminalControl parseTerminalControl(ByteBuffer buf) {
        Instant time = readTime(buf);
        int cmdId = buf.get() & 0xFF;
        CommandBody.ControlCommand commandId = CommandBody.ControlCommand.of(cmdId);
        CommandBody.ControlPayload payload = switch (commandId) {
            case REMOTE_UPGRADE -> parseRemoteUpgrade(buf);
            case ALARM_COMMAND -> parseAlarmCommand(buf);
            case SHUTDOWN, RESET, FACTORY_RESET, DISCONNECT, SAMPLING_LINK_ON -> new CommandBody.ControlPayload.None();
            case UNKNOWN -> captureRawPayload(buf);
        };
        return new CommandBody.TerminalControl(time, commandId, payload);
    }

    /**
     * 表 B.12 远程升级参数。字段之间用半角分号 {@code ;} 分隔。
     *
     * <p>字段顺序：URL;apn;dialUser;dialPwd;serverAddress;serverPort;manufacturerCode;hwVer;fwVer;timeoutMin
     */
    private CommandBody.ControlPayload.RemoteUpgrade parseRemoteUpgrade(ByteBuffer buf) {
        byte[] all = new byte[buf.remaining()];
        buf.get(all);
        String s = new String(all, Charset.forName("GBK"));
        String[] parts = s.split(";", -1);
        String url = parts.length > 0 ? parts[0] : "";
        String apn = parts.length > 1 ? parts[1] : "";
        String user = parts.length > 2 ? parts[2] : "";
        String pwd = parts.length > 3 ? parts[3] : "";
        byte[] addr = parts.length > 4 ? parts[4].getBytes(StandardCharsets.US_ASCII) : new byte[0];
        int port = parts.length > 5 ? parseIntSafe(parts[5]) : 0;
        String manu = parts.length > 6 ? parts[6] : "";
        String hw = parts.length > 7 ? parts[7] : "";
        String fw = parts.length > 8 ? parts[8] : "";
        int timeoutMin = parts.length > 9 ? parseIntSafe(parts[9]) : 0;
        return new CommandBody.ControlPayload.RemoteUpgrade(
                apn, user, pwd, addr, port, manu, hw, fw, url, timeoutMin);
    }

    /** 表 B.13 报警/预警命令。 */
    private CommandBody.ControlPayload.AlarmCommand parseAlarmCommand(ByteBuffer buf) {
        int warningLevel = buf.hasRemaining() ? (buf.get() & 0xFF) : 0;
        byte[] info = new byte[buf.remaining()];
        buf.get(info);
        return new CommandBody.ControlPayload.AlarmCommand(warningLevel, info);
    }

    private CommandBody.ControlPayload.RawPayload captureRawPayload(ByteBuffer buf) {
        byte[] bytes = new byte[buf.remaining()];
        buf.get(bytes);
        return new CommandBody.ControlPayload.RawPayload(bytes);
    }

    private static int parseIntSafe(String s) {
        try { return Integer.parseInt(s.trim()); } catch (Exception e) { return 0; }
    }

    /** 表 5：时间定义 YY MM DD HH MM SS，GMT+8。 */
    private Instant readTime(ByteBuffer buf) {
        if (buf.remaining() < 6) {
            throw new DecodeException("time field requires 6 bytes, got " + buf.remaining());
        }
        int year = 2000 + (buf.get() & 0xFF);
        int month = buf.get() & 0xFF;
        int day = buf.get() & 0xFF;
        int hour = buf.get() & 0xFF;
        int minute = buf.get() & 0xFF;
        int second = buf.get() & 0xFF;
        try {
            return LocalDateTime.of(year, month, day, hour, minute, second).atZone(ZONE_GMT8).toInstant();
        } catch (Exception e) {
            throw new DecodeException("invalid time: " + year + "-" + month + "-" + day
                    + " " + hour + ":" + minute + ":" + second, e);
        }
    }

    private String readAscii(ByteBuffer buf, int len) {
        int safeLen = Math.min(len, buf.remaining());
        byte[] b = new byte[safeLen];
        buf.get(b);
        return new String(b, StandardCharsets.US_ASCII).trim();
    }
}
