package com.usky.ems.protocol;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.usky.ems.util.AesUtil;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * TCP客户端
 * 用于与能耗监管系统建立连接并发送数据
 *
 * @author system
 * @since 2024-01-01
 */
public class TcpClient {

    private static final Logger logger = LoggerFactory.getLogger(TcpClient.class);

    private String host;
    private int port;
    private String authKey;
    private Socket socket;
    private InputStream inputStream;
    private OutputStream outputStream;
    private AtomicBoolean connected = new AtomicBoolean(false);
    private AtomicBoolean authenticated = new AtomicBoolean(false);

    public TcpClient(String host, int port, String authKey) {
        this.host = host;
        this.port = port;
        this.authKey = authKey;
    }

    /**
     * 建立TCP连接
     *
     * @return 是否连接成功
     */
    public boolean connect() {
        try {
            if (socket != null && !socket.isClosed()) {
                socket.close();
            }

            socket = new Socket(host, port);
            socket.setSoTimeout(30000); // 30秒超时
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();
            connected.set(true);
            authenticated.set(false);

            logger.info("TCP连接成功: {}:{}", host, port);
            return true;
        } catch (IOException e) {
            logger.error("TCP连接失败: {}:{}", host, port, e);
            connected.set(false);
            return false;
        }
    }

    /**
     * 关闭连接
     */
    public void close() {
        try {
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
            if (socket != null && !socket.isClosed()) {
                socket.close();
            }
            connected.set(false);
            authenticated.set(false);
            logger.info("TCP连接已关闭");
        } catch (IOException e) {
            logger.error("关闭TCP连接失败", e);
        }
    }

    /**
     * 身份认证
     *
     * @param buildingId 建筑ID
     * @param gatewayId  网关ID
     * @return 是否认证成功
     */
    public boolean authenticate(String buildingId, String gatewayId) {
        if (!connected.get()) {
            logger.error("未建立TCP连接，无法进行身份认证");
            return false;
        }

        try {
            // 1. 发送身份认证请求
            String requestXml = com.usky.ems.util.XmlBuilder.buildAuthRequest(buildingId, gatewayId);
            sendPacket(NetworkPacket.TYPE_AUTH, requestXml, false);

            // 2. 接收服务端返回的随机序列
            NetworkPacket response = receivePacket();
            if (response == null || response.getType() != NetworkPacket.TYPE_AUTH) {
                logger.error("接收身份认证响应失败");
                return false;
            }

            // 认证响应不加密
            String responseXml = new String(response.getData(), java.nio.charset.StandardCharsets.UTF_8);
            String sequence = parseSequenceFromXml(responseXml);
            if (sequence == null || sequence.isEmpty()) {
                logger.error("解析随机序列失败");
                return false;
            }

            // 3. 计算MD5：密钥 + 随机序列
            String md5 = com.usky.ems.util.Md5Util.md5(authKey + sequence);

            // 4. 发送MD5值
            String md5Xml = com.usky.ems.util.XmlBuilder.buildAuthMd5Request(buildingId, gatewayId, md5);
            sendPacket(NetworkPacket.TYPE_AUTH, md5Xml, false);

            // 5. 接收认证结果
            NetworkPacket result = receivePacket();
            if (result == null || result.getType() != NetworkPacket.TYPE_AUTH) {
                logger.error("接收认证结果失败");
                return false;
            }

            // 认证响应不加密
            String resultXml = new String(result.getData(), java.nio.charset.StandardCharsets.UTF_8);
            boolean success = parseAuthResult(resultXml);

            if (success) {
                authenticated.set(true);
                logger.info("身份认证成功");
            } else {
                logger.error("身份认证失败");
            }

            return success;
        } catch (Exception e) {
            logger.error("身份认证过程异常", e);
            return false;
        }
    }

    /**
     * 发送心跳
     *
     * @param buildingId 建筑ID
     * @param gatewayId  网关ID
     * @return 是否发送成功
     */
    public boolean sendHeartbeat(String buildingId, String gatewayId) {
        if (!connected.get() || !authenticated.get()) {
            logger.warn("连接未建立或未认证，无法发送心跳");
            return false;
        }

        try {
            String heartbeatXml = com.usky.ems.util.XmlBuilder.buildHeartbeatRequest(buildingId, gatewayId);
            sendPacket(NetworkPacket.TYPE_HEARTBEAT, heartbeatXml, false);

            NetworkPacket response1 = receivePacket();
            if (response1 != null  && response1.getType() == NetworkPacket.TYPE_HEARTBEAT) {
                // 直接转换为可读XML（无需解密）
                String heartbeatResponseXml = new String(response1.getData(), java.nio.charset.StandardCharsets.UTF_8);
                System.out.println("可读报文：\n" + heartbeatResponseXml);
                boolean success = this.parseHeartbeatResult(heartbeatResponseXml);
                if (success) {
                    logger.info("发送心跳成功");
                    return true;
                }else{
                    logger.info("发送心跳失败");
                    return false;
                }
            }
            return false;
        } catch (Exception e) {
            logger.error("发送心跳失败", e);
            return false;
        }
    }

    /**
     * 发送能耗数据
     *
     * @param xmlData XML数据（未加密）
     * @return 是否发送成功
     */
    public boolean sendEnergyData(String xmlData) {
        if (!connected.get() || !authenticated.get()) {
            logger.warn("连接未建立或未认证，无法发送能耗数据");
            return false;
        }

        try {
            sendPacket(NetworkPacket.TYPE_ENERGY_DATA, xmlData, true);

            NetworkPacket response = receivePacket();
            if (response != null  && response.getType() == NetworkPacket.TYPE_ENERGY_DATA) {
                // 直接转换为可读XML（无需解密）
                String readable = new String(response.getData(), java.nio.charset.StandardCharsets.UTF_8);
                System.out.println("可读报文：\n" + readable);
                boolean success = this.parseEnergyDataResult(readable);
                if (success) {
                    logger.info("能耗数据发送成功");
                    return true;
                }else{
                    logger.info("能耗数据发送失败");
                    return false;
                }
            }
            return false;

        } catch (Exception e) {
            logger.error("发送能耗数据失败", e);
            return false;
        }
    }

    /**
     * 将TCP报文字节数组直接转换为可读格式（无需解密）
     * @param dataBytes 原始报文字节数组
     * @param charset 报文字符编码（默认UTF-8）
     * @param format 报文格式（xml/json/plain，plain为普通文本）
     * @return 可读的报文字符串
     */
    public String convertToReadablePacket(byte[] dataBytes, String charset, String format) {
        if (dataBytes == null || dataBytes.length == 0) {
            return "空报文";
        }

        // 兜底处理字符编码，默认用UTF-8
        String actualCharset = (charset == null || charset.isEmpty()) ? "UTF-8" : charset;

        try {
            // 第一步：直接将字节数组转字符串（核心，无需解密）
            String rawText = new String(dataBytes, actualCharset);
            logger.debug("原始文本报文：{}", rawText);

            // 第二步：根据格式美化
            switch (format.toLowerCase()) {
                case "xml":
                    return formatXml(rawText); // XML格式化（带缩进）
                case "json":
                    return formatJson(rawText); // JSON格式化（带缩进）
                case "plain":
                default:
                    return rawText; // 普通文本直接返回
            }
        } catch (Exception e) {
            logger.warn("转换为文本格式失败，返回十六进制格式：{}", e.getMessage());
            // 转换失败时，返回十六进制（所有二进制数据都能读）
            return bytesToHex(dataBytes);
        }
    }

    /**
     * XML格式化（美化缩进）
     */
    private String formatXml(String xmlText) throws DocumentException {
        Document document = DocumentHelper.parseText(xmlText);
        return document.asXML();
    }

    /**
     * JSON格式化（美化缩进）
     */
    private String formatJson(String jsonText) throws Exception {
        ObjectMapper mapper = new ObjectMapper();
        Object jsonObj = mapper.readValue(jsonText, Object.class);
        return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonObj);
    }

    /**
     * 字节数组转十六进制字符串（兜底方案）
     * 格式：00 1A 2B ... （便于排查二进制数据问题）
     */
    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X ", b));
        }
        return "十六进制格式：" + sb.toString().trim();
    }

    /**
     * 发送数据包
     *
     * @param type     消息类型
     * @param xmlData  XML数据
     * @param encrypt  是否加密
     */
    private void sendPacket(byte type, String xmlData, boolean encrypt) throws IOException {
        byte[] data;
        if (encrypt) {
            // 能耗数据需要AES加密
            try {
                data = AesUtil.encryptEnergyXml(xmlData, authKey);
            } catch (Exception e) {
                throw new IOException("加密能耗数据失败", e);
            }
        } else {
            // 身份认证和心跳不加密
            data = xmlData.getBytes(java.nio.charset.StandardCharsets.UTF_8);
        }

        NetworkPacket packet = new NetworkPacket();
        packet.setType(type);
        packet.setData(data);

        byte[] packetBytes = packet.encode();
        outputStream.write(packetBytes);
        outputStream.flush();
    }

    /**
     * 接收数据包
     *
     * @return 接收到的数据包
     */
    private NetworkPacket receivePacket() throws IOException {
        // 先读取7字节（Head + Type + Length）
        byte[] header = new byte[7];
        int bytesRead = 0;
        while (bytesRead < 7) {
            int n = inputStream.read(header, bytesRead, 7 - bytesRead);
            if (n == -1) {
                throw new IOException("连接已关闭");
            }
            bytesRead += n;
        }

        // 解析Length
        int length = ((header[3] & 0xFF) << 24) |
                ((header[4] & 0xFF) << 16) |
                ((header[5] & 0xFF) << 8) |
                (header[6] & 0xFF);

        // 读取Data
        byte[] data = new byte[length];
        bytesRead = 0;
        while (bytesRead < length) {
            int n = inputStream.read(data, bytesRead, length - bytesRead);
            if (n == -1) {
                throw new IOException("连接已关闭");
            }
            bytesRead += n;
        }

        // 组装完整数据包
        byte[] packetBytes = new byte[7 + length];
        System.arraycopy(header, 0, packetBytes, 0, 7);
        System.arraycopy(data, 0, packetBytes, 7, length);

        return NetworkPacket.decode(packetBytes);
    }


    /**
     * 从XML中解析随机序列
     */
    private String parseSequenceFromXml(String xml) {
        try {
            Document document = DocumentHelper.parseText(xml);
            Element root = document.getRootElement();
            Element idValidate = root.element("id_validate");
            if (idValidate != null) {
                Element sequence = idValidate.element("sequence");
                if (sequence != null) {
                    return sequence.getTextTrim();
                }
            }
        } catch (Exception e) {
            logger.error("解析随机序列失败", e);
        }
        return null;
    }

    /**
     * 从XML中解析认证结果
     */
    private boolean parseAuthResult(String xml) {
        try {
            Document document = DocumentHelper.parseText(xml);
            Element root = document.getRootElement();
            Element idValidate = root.element("id_validate");
            if (idValidate != null && "result".equals(idValidate.attributeValue("operation"))) {
                Element result = idValidate.element("result");
                if (result != null) {
                    return "pass".equals(result.getTextTrim());
                }
            }
        } catch (Exception e) {
            logger.error("解析认证结果失败", e);
        }
        return false;
    }

    /**
     * 从XML中解析推送能耗数据响应结果
     */
    private boolean parseEnergyDataResult(String xml) {
        try {
            Document document = DocumentHelper.parseText(xml);
            Element root = document.getRootElement();
            Element dataElement = root.element("data");
            if (dataElement == null) {
                logger.warn("XML中未找到<data>节点");
            }

            // 4. 获取<ack>子节点的文本值
            Element ackElement = dataElement.element("ack");
            if (ackElement == null) {
                logger.warn("XML中未找到<ack>节点");
            }

            return "OK".equals(ackElement.getTextTrim());
        } catch (Exception e) {
            logger.error("解析认证结果失败", e);
        }
        return false;
    }

    /**
     * 从XML中解析推送能耗数据响应结果
     */
    private boolean parseHeartbeatResult(String xml) {
        try {
            Document document = DocumentHelper.parseText(xml);
            Element root = document.getRootElement();
            Element dataElement = root.element("heart_beat");
            if (dataElement == null) {
                logger.warn("XML中未找到<heart_beat>节点");
            }

            // 4. 获取<ack>子节点的文本值
            Element ackElement = dataElement.element("time");
            if (ackElement == null) {
                logger.warn("XML中未找到<time>节点");
            }

            return StringUtils.isNotBlank(ackElement.getTextTrim());
        } catch (Exception e) {
            logger.error("解析认证结果失败", e);
        }
        return false;
    }

    public boolean isConnected() {
        return connected.get() && socket != null && !socket.isClosed();
    }

    public boolean isAuthenticated() {
        return authenticated.get();
    }
}
