/*
 * 文 件 名:  TransactionRecordFrame
 * 版    权:  华设设计集团股份有限公司
 * 描    述:  交易记录帧 (0x3B)
 * 修 改 人:  lvwenbin
 * 修改时间:  2026/01/08
 */
package com.ruoyi.ems.charging.model.req;

import com.ruoyi.ems.charging.model.BaseFrame;
import com.ruoyi.ems.charging.protocol.ProtocolConstants;
import com.ruoyi.ems.charging.utils.ByteUtils;
import io.netty.buffer.ByteBuf;

import java.nio.charset.StandardCharsets;
import java.util.Date;

/**
 * 交易记录帧 (0x3B)
 * 充电结束后由充电桩上送的完整交易数据
 *
 * 消息体结构:
 * - 交易流水号: 16字节 BCD码
 * - 桩编号: 7字节 BCD码
 * - 枪号: 1字节 BCD码
 * - 开始时间: 7字节 CP56Time2a
 * - 结束时间: 7字节 CP56Time2a
 * - 分时电量区: 64字节 (尖/峰/平/谷各16字节)
 * - 电表起止值: 10字节
 * - 汇总区: 12字节
 * - 附加信息区: 变长
 *
 * @author lvwenbin
 * @version [版本号, 2026/01/08]
 */
public class TransactionRecordFrame extends BaseFrame {

    // ==================== 基础信息 ====================

    /**
     * 交易流水号 (BCD 16字节)
     */
    private String transactionNo;

    /**
     * 桩编号 (BCD 7字节)
     */
    private String pileCode;

    /**
     * 枪号 (BCD 1字节)
     */
    private String gunNo;

    /**
     * 开始时间
     */
    private Date startTime;

    /**
     * 结束时间
     */
    private Date endTime;

    // ==================== 分时电量区（每时段16字节） ====================

    // 尖时段
    private int sharpUnitPrice;      // 单价 (5位小数, 原始值)
    private int sharpEnergy;         // 电量 (4位小数, 原始值)
    private int sharpEnergyLoss;     // 计损电量 (4位小数, 原始值)
    private int sharpAmount;         // 金额 (4位小数, 原始值)

    // 峰时段
    private int peakUnitPrice;
    private int peakEnergy;
    private int peakEnergyLoss;
    private int peakAmount;

    // 平时段
    private int flatUnitPrice;
    private int flatEnergy;
    private int flatEnergyLoss;
    private int flatAmount;

    // 谷时段
    private int valleyUnitPrice;
    private int valleyEnergy;
    private int valleyEnergyLoss;
    private int valleyAmount;

    // ==================== 电表读数区 ====================

    /**
     * 电表总起值 (BIN 5字节, 4位小数)
     */
    private long meterStartValue;

    /**
     * 电表总止值 (BIN 5字节, 4位小数)
     */
    private long meterEndValue;

    // ==================== 汇总区 ====================

    /**
     * 总电量 (BIN 4字节, 4位小数)
     */
    private int totalEnergy;

    /**
     * 计损总电量 (BIN 4字节, 4位小数)
     */
    private int totalEnergyLoss;

    /**
     * 消费金额 (BIN 4字节, 4位小数)
     */
    private int totalAmount;

    // ==================== 附加信息区 ====================

    /**
     * VIN码 (ASCII 17字节)
     */
    private String vin;

    /**
     * 交易标识 (1字节)
     * 0x01: APP启动
     * 0x02: 刷卡启动
     * 0x04: 离线卡启动
     * 0x05: VIN码启动
     */
    private byte transactionType;

    /**
     * 交易时间
     */
    private Date transactionTime;

    /**
     * 停止原因 (1字节)
     */
    private byte stopReason;

    /**
     * 物理卡号 (BIN 8字节)
     */
    private String physicalCardNo;

    /**
     * 厂家自定义停止原因 (1字节, 可选)
     */
    private byte vendorStopReason;

    // ==================== 帧类型 ====================

    @Override
    public byte getFrameType() {
        return ProtocolConstants.FRAME_TYPE_TRANSACTION;
    }

    // ==================== 编解码 ====================

    @Override
    protected void encodeBody(ByteBuf buf) {
        // 交易记录通常不需要平台发送，此方法留空
    }

    @Override
    protected void decodeBody(ByteBuf buf) {
        // 交易流水号 (16字节 BCD)
        byte[] transNoBytes = new byte[16];
        buf.readBytes(transNoBytes);
        this.transactionNo = ByteUtils.bcdToStr(transNoBytes);

        // 桩编号 (7字节 BCD)
        byte[] pileCodeBytes = new byte[7];
        buf.readBytes(pileCodeBytes);
        this.pileCode = ByteUtils.bcdToStr(pileCodeBytes);

        // 枪号 (1字节 BCD)
        this.gunNo = ByteUtils.bcdToStr(new byte[]{buf.readByte()});

        // 开始时间 (7字节 CP56Time2a)
        byte[] startTimeBytes = new byte[7];
        buf.readBytes(startTimeBytes);
        this.startTime = ByteUtils.cp56Time2aToDate(startTimeBytes);

        // 结束时间 (7字节 CP56Time2a)
        byte[] endTimeBytes = new byte[7];
        buf.readBytes(endTimeBytes);
        this.endTime = ByteUtils.cp56Time2aToDate(endTimeBytes);

        // ========== 分时电量区 (64字节) ==========

        // 尖时段 (16字节)
        this.sharpUnitPrice = buf.readIntLE();
        this.sharpEnergy = buf.readIntLE();
        this.sharpEnergyLoss = buf.readIntLE();
        this.sharpAmount = buf.readIntLE();

        // 峰时段 (16字节)
        this.peakUnitPrice = buf.readIntLE();
        this.peakEnergy = buf.readIntLE();
        this.peakEnergyLoss = buf.readIntLE();
        this.peakAmount = buf.readIntLE();

        // 平时段 (16字节)
        this.flatUnitPrice = buf.readIntLE();
        this.flatEnergy = buf.readIntLE();
        this.flatEnergyLoss = buf.readIntLE();
        this.flatAmount = buf.readIntLE();

        // 谷时段 (16字节)
        this.valleyUnitPrice = buf.readIntLE();
        this.valleyEnergy = buf.readIntLE();
        this.valleyEnergyLoss = buf.readIntLE();
        this.valleyAmount = buf.readIntLE();

        // ========== 电表读数区 (10字节) ==========

        // 电表起值 (5字节, 小端序)
        this.meterStartValue = ByteUtils.readLongLE(buf, 5);

        // 电表止值 (5字节, 小端序)
        this.meterEndValue = ByteUtils.readLongLE(buf, 5);

        // ========== 汇总区 (12字节) ==========

        this.totalEnergy = buf.readIntLE();
        this.totalEnergyLoss = buf.readIntLE();
        this.totalAmount = buf.readIntLE();

        // ========== 附加信息区 ==========

        // VIN码 (17字节 ASCII)
        byte[] vinBytes = new byte[17];
        buf.readBytes(vinBytes);
        this.vin = new String(vinBytes, StandardCharsets.US_ASCII).trim();

        // 交易标识 (1字节)
        this.transactionType = buf.readByte();

        // 交易时间 (7字节 CP56Time2a)
        byte[] transTimeBytes = new byte[7];
        buf.readBytes(transTimeBytes);
        this.transactionTime = ByteUtils.cp56Time2aToDate(transTimeBytes);

        // 停止原因 (1字节)
        this.stopReason = buf.readByte();

        // 物理卡号 (8字节 BIN)
        byte[] cardNoBytes = new byte[8];
        buf.readBytes(cardNoBytes);
        this.physicalCardNo = ByteUtils.bytesToHex(cardNoBytes);

        // 厂家自定义停止原因 (1字节, 可选)
        if (buf.readableBytes() >= 1) {
            this.vendorStopReason = buf.readByte();
        }
    }

    // ==================== 数值转换方法（原始值 -> 实际值） ====================

    /**
     * 获取尖时段单价 (元/kWh, 5位小数)
     */
    public double getSharpUnitPriceValue() {
        return sharpUnitPrice / 100000.0;
    }

    /**
     * 获取尖时段电量 (kWh, 4位小数)
     */
    public double getSharpEnergyValue() {
        return sharpEnergy / 10000.0;
    }

    /**
     * 获取尖时段计损电量 (kWh, 4位小数)
     */
    public double getSharpEnergyLossValue() {
        return sharpEnergyLoss / 10000.0;
    }

    /**
     * 获取尖时段金额 (元, 4位小数)
     */
    public double getSharpAmountValue() {
        return sharpAmount / 10000.0;
    }

    /**
     * 获取峰时段单价 (元/kWh, 5位小数)
     */
    public double getPeakUnitPriceValue() {
        return peakUnitPrice / 100000.0;
    }

    /**
     * 获取峰时段电量 (kWh, 4位小数)
     */
    public double getPeakEnergyValue() {
        return peakEnergy / 10000.0;
    }

    /**
     * 获取峰时段计损电量 (kWh, 4位小数)
     */
    public double getPeakEnergyLossValue() {
        return peakEnergyLoss / 10000.0;
    }

    /**
     * 获取峰时段金额 (元, 4位小数)
     */
    public double getPeakAmountValue() {
        return peakAmount / 10000.0;
    }

    /**
     * 获取平时段单价 (元/kWh, 5位小数)
     */
    public double getFlatUnitPriceValue() {
        return flatUnitPrice / 100000.0;
    }

    /**
     * 获取平时段电量 (kWh, 4位小数)
     */
    public double getFlatEnergyValue() {
        return flatEnergy / 10000.0;
    }

    /**
     * 获取平时段计损电量 (kWh, 4位小数)
     */
    public double getFlatEnergyLossValue() {
        return flatEnergyLoss / 10000.0;
    }

    /**
     * 获取平时段金额 (元, 4位小数)
     */
    public double getFlatAmountValue() {
        return flatAmount / 10000.0;
    }

    /**
     * 获取谷时段单价 (元/kWh, 5位小数)
     */
    public double getValleyUnitPriceValue() {
        return valleyUnitPrice / 100000.0;
    }

    /**
     * 获取谷时段电量 (kWh, 4位小数)
     */
    public double getValleyEnergyValue() {
        return valleyEnergy / 10000.0;
    }

    /**
     * 获取谷时段计损电量 (kWh, 4位小数)
     */
    public double getValleyEnergyLossValue() {
        return valleyEnergyLoss / 10000.0;
    }

    /**
     * 获取谷时段金额 (元, 4位小数)
     */
    public double getValleyAmountValue() {
        return valleyAmount / 10000.0;
    }

    /**
     * 获取电表起值 (kWh, 4位小数)
     */
    public double getMeterStartValueKwh() {
        return meterStartValue / 10000.0;
    }

    /**
     * 获取电表止值 (kWh, 4位小数)
     */
    public double getMeterEndValueKwh() {
        return meterEndValue / 10000.0;
    }

    /**
     * 获取电表差值 (kWh)
     */
    public double getMeterDeltaKwh() {
        return getMeterEndValueKwh() - getMeterStartValueKwh();
    }

    /**
     * 获取总电量 (kWh, 4位小数)
     */
    public double getTotalEnergyValue() {
        return totalEnergy / 10000.0;
    }

    /**
     * 获取计损总电量 (kWh, 4位小数)
     */
    public double getTotalEnergyLossValue() {
        return totalEnergyLoss / 10000.0;
    }

    /**
     * 获取消费总金额 (元, 4位小数)
     */
    public double getTotalAmountValue() {
        return totalAmount / 10000.0;
    }

    // ==================== 业务辅助方法 ====================

    /**
     * 获取完整枪号（桩编号+枪号）
     */
    public String getFullGunNo() {
        return pileCode + gunNo;
    }

    /**
     * 获取充电时长（分钟）
     */
    public int getChargingDurationMinutes() {
        if (startTime == null || endTime == null) {
            return 0;
        }
        long diffMs = endTime.getTime() - startTime.getTime();
        return (int) (diffMs / 60000);
    }

    /**
     * 是否正常停止
     * 正常停止原因码: 0x40-0x49
     */
    public boolean isNormalStop() {
        int code = stopReason & 0xFF;
        return code >= 0x40 && code <= 0x49;
    }

    /**
     * 获取停止原因描述
     */
    public String getStopReasonDesc() {
        int code = stopReason & 0xFF;

        // 正常结束 0x40-0x49
        switch (code) {
            case 0x40: return "达到SOC目标";
            case 0x41: return "达到电压目标";
            case 0x42: return "单体电压达到最高设定值";
            case 0x43: return "达到金额目标";
            case 0x44: return "达到时间目标";
            case 0x45: return "达到电量目标";
            case 0x46: return "BMS主动停止";
            case 0x47: return "手动停止";
            case 0x48: return "远程停止";
            case 0x49: return "异常停止";

            // 启动失败 0x4A-0x69
            case 0x4A: return "绝缘故障";
            case 0x4B: return "充电桩急停按下";
            case 0x4C: return "门禁断开或柜门打开";
            case 0x4D: return "电表通讯失败";
            case 0x4E: return "熔断器断开";
            case 0x4F: return "接收BMS充电准备报文超时";
            case 0x50: return "接收BMS充电参数配置报文超时";
            case 0x51: return "BRM报文超时";
            case 0x52: return "接收BMS充电准备报文超时";
            case 0x53: return "接收BCS报文超时";
            case 0x54: return "BMS通讯超时";

            // 异常终止 0x6A-0x8F
            case 0x6A: return "充电模块过压保护";
            case 0x6B: return "充电模块过流保护";
            case 0x6C: return "充电模块短路保护";
            case 0x6D: return "充电接口过温保护";
            case 0x6E: return "充电模块过温保护";
            case 0x6F: return "直流电压输出异常";
            case 0x70: return "直流电流输出异常";
            case 0x71: return "输入电压异常";
            case 0x72: return "输入电流异常";
            case 0x73: return "启动充电失败";
            case 0x74: return "开关量输出异常";
            case 0x75: return "BMS通讯异常";

            case 0x90: return "其他原因";
            default: return "未知原因(0x" + String.format("%02X", code) + ")";
        }
    }

    /**
     * 获取交易类型描述
     */
    public String getTransactionTypeDesc() {
        switch (transactionType) {
            case 0x01: return "APP启动";
            case 0x02: return "刷卡启动";
            case 0x04: return "离线卡启动";
            case 0x05: return "VIN码启动";
            default: return "未知类型";
        }
    }

    // ==================== Getters ====================

    public String getTransactionNo() {
        return transactionNo;
    }

    public String getPileCode() {
        return pileCode;
    }

    public String getGunNo() {
        return gunNo;
    }

    public Date getStartTime() {
        return startTime;
    }

    public Date getEndTime() {
        return endTime;
    }

    public String getVin() {
        return vin;
    }

    public byte getTransactionType() {
        return transactionType;
    }

    public Date getTransactionTime() {
        return transactionTime;
    }

    public byte getStopReason() {
        return stopReason;
    }

    public String getPhysicalCardNo() {
        return physicalCardNo;
    }

    public byte getVendorStopReason() {
        return vendorStopReason;
    }

    @Override
    public String toString() {
        return String.format(
            "TransactionRecord[transNo=%s, pile=%s, gun=%s, " +
                "startTime=%s, endTime=%s, duration=%dmin, " +
                "totalEnergy=%.4fkWh, totalAmount=%.2f元, " +
                "vin=%s, stopReason=%s]",
            transactionNo, pileCode, gunNo,
            startTime, endTime, getChargingDurationMinutes(),
            getTotalEnergyValue(), getTotalAmountValue(),
            vin, getStopReasonDesc()
        );
    }
}
