/*
 * 文 件 名:  ChargingPileSessionManager
 * 版    权:  华设设计集团股份有限公司
 * 描    述:  充电桩会话管理器
 * 修 改 人:  lvwenbin
 * 修改时间:  2025/12/23
 * 修改内容:  添加会话清理机制，防止内存泄漏
 */
package com.ruoyi.ems.charging.core;

import io.netty.channel.Channel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 充电桩会话管理器
 * 管理已连接的充电桩会话，包括通道映射、心跳超时检测等
 *
 * @author lvwenbin
 * @version [版本号, 2025/12/23]
 */
@Component
public class ChargingPileSessionManager {

    private static final Logger log = LoggerFactory.getLogger(ChargingPileSessionManager.class);

    /**
     * 离线会话保留时间(小时) - 超过此时间的离线会话将被清理
     */
    private static final int OFFLINE_SESSION_EXPIRE_HOURS = 24;

    /**
     * 桩编号 -> 会话信息
     */
    private final Map<String, PileSession> pileSessionMap = new ConcurrentHashMap<>();

    /**
     * Channel ID -> 桩编号
     */
    private final Map<String, String> channelPileMap = new ConcurrentHashMap<>();

    /**
     * 注册充电桩会话
     *
     * @param pileCode 桩编号
     * @param channel  通道
     * @param gunCount 枪数量
     */
    public void registerSession(String pileCode, Channel channel, int gunCount) {
        // 清理旧会话
        PileSession oldSession = pileSessionMap.get(pileCode);
        if (oldSession != null && oldSession.getChannel() != null && oldSession.getChannel() != channel) {
            log.info("充电桩[{}]重新连接，关闭旧连接", pileCode);
            try {
                String oldChannelId = oldSession.getChannel().id().asLongText();
                channelPileMap.remove(oldChannelId);
                oldSession.getChannel().close();
            }
            catch (Exception e) {
                log.warn("关闭旧连接异常", e);
            }
        }

        // 创建新会话
        PileSession session = new PileSession();
        session.setPileCode(pileCode);
        session.setChannel(channel);
        session.setGunCount(gunCount);
        session.setLoginTime(LocalDateTime.now());
        session.setLastHeartbeatTime(LocalDateTime.now());
        session.setOnline(true);

        pileSessionMap.put(pileCode, session);
        channelPileMap.put(channel.id().asLongText(), pileCode);

        log.info("充电桩[{}]会话注册成功，枪数量: {}, 通道: {}, 当前在线桩数: {}",
            pileCode, gunCount, channel.remoteAddress(), getOnlineCount());
    }

    /**
     * 注销充电桩会话
     *
     * @param channel 通道
     */
    public void unregisterSession(Channel channel) {
        String channelId = channel.id().asLongText();
        String pileCode = channelPileMap.remove(channelId);
        if (pileCode != null) {
            PileSession session = pileSessionMap.get(pileCode);
            if (session != null && session.getChannel() == channel) {
                session.setOnline(false);
                session.setChannel(null);
                session.setOfflineTime(LocalDateTime.now()); // 记录下线时间
                log.info("充电桩[{}]会话注销，当前在线桩数: {}", pileCode, getOnlineCount());
            }
        }
    }

    /**
     * 更新心跳时间
     *
     * @param pileCode 桩编号
     */
    public void updateHeartbeat(String pileCode) {
        PileSession session = pileSessionMap.get(pileCode);
        if (session != null) {
            session.setLastHeartbeatTime(LocalDateTime.now());
        }
    }

    /**
     * 获取会话
     *
     * @param pileCode 桩编号
     * @return 会话信息
     */
    public PileSession getSession(String pileCode) {
        return pileSessionMap.get(pileCode);
    }

    /**
     * 根据Channel获取桩编号
     *
     * @param channel 通道
     * @return 桩编号
     */
    public String getPileCodeByChannel(Channel channel) {
        return channelPileMap.get(channel.id().asLongText());
    }

    /**
     * 获取通道
     *
     * @param pileCode 桩编号
     * @return 通道
     */
    public Channel getChannel(String pileCode) {
        PileSession session = pileSessionMap.get(pileCode);
        return session != null ? session.getChannel() : null;
    }

    /**
     * 检查是否在线
     *
     * @param pileCode 桩编号
     * @return 是否在线
     */
    public boolean isOnline(String pileCode) {
        PileSession session = pileSessionMap.get(pileCode);
        return session != null && session.isOnline() && session.getChannel() != null && session.getChannel().isActive();
    }

    /**
     * 获取所有在线会话
     *
     * @return 会话集合
     */
    public Collection<PileSession> getAllOnlineSessions() {
        List<PileSession> onlineSessions = new ArrayList<>();
        for (PileSession session : pileSessionMap.values()) {
            if (session.isOnline() && session.getChannel() != null && session.getChannel().isActive()) {
                onlineSessions.add(session);
            }
        }
        return onlineSessions;
    }

    /**
     * 获取所有会话（包括离线的）
     *
     * @return 会话集合
     */
    public Collection<PileSession> getAllSessions() {
        return pileSessionMap.values();
    }

    /**
     * 获取在线充电桩数量
     *
     * @return 数量
     */
    public int getOnlineCount() {
        int count = 0;
        for (PileSession session : pileSessionMap.values()) {
            if (session.isOnline() && session.getChannel() != null && session.getChannel().isActive()) {
                count++;
            }
        }
        return count;
    }

    /**
     * 获取总会话数量（包括离线的）
     *
     * @return 数量
     */
    public int getTotalSessionCount() {
        return pileSessionMap.size();
    }

    /**
     * 定期清理过期的离线会话 - 每小时执行一次
     * 防止内存泄漏
     */
    @Scheduled(fixedRate = 3600000) // 1小时
    public void cleanExpiredSessions() {
        LocalDateTime expireTime = LocalDateTime.now().minus(OFFLINE_SESSION_EXPIRE_HOURS, ChronoUnit.HOURS);
        int cleanedCount = 0;

        Iterator<Map.Entry<String, PileSession>> iterator = pileSessionMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, PileSession> entry = iterator.next();
            PileSession session = entry.getValue();

            // 清理已离线且超过保留时间的会话
            if (!session.isOnline() && session.getOfflineTime() != null
                && session.getOfflineTime().isBefore(expireTime)) {
                iterator.remove();
                cleanedCount++;
                log.debug("清理过期离线会话 - 桩号: {}, 离线时间: {}",
                    session.getPileCode(), session.getOfflineTime());
            }
        }

        if (cleanedCount > 0) {
            log.info("会话清理完成 - 清理过期离线会话: {}个, 剩余会话: {}个, 在线: {}个",
                cleanedCount, pileSessionMap.size(), getOnlineCount());
        }
    }

    /**
     * 手动移除会话
     *
     * @param pileCode 桩编号
     */
    public void removeSession(String pileCode) {
        PileSession session = pileSessionMap.remove(pileCode);
        if (session != null && session.getChannel() != null) {
            channelPileMap.remove(session.getChannel().id().asLongText());
            if (session.getChannel().isActive()) {
                session.getChannel().close();
            }
            log.info("手动移除会话 - 桩号: {}", pileCode);
        }
    }

    /**
     * 充电桩会话信息
     */
    public static class PileSession {
        /**
         * 桩编号
         */
        private String pileCode;

        /**
         * 通道
         */
        private Channel channel;

        /**
         * 枪数量
         */
        private int gunCount;

        /**
         * 登录时间
         */
        private LocalDateTime loginTime;

        /**
         * 最后心跳时间
         */
        private LocalDateTime lastHeartbeatTime;

        /**
         * 离线时间
         */
        private LocalDateTime offlineTime;

        /**
         * 是否在线
         */
        private boolean online;

        /**
         * 序列号计数器
         */
        private int sequenceNo = 0;

        /**
         * 获取下一个序列号
         */
        public synchronized int nextSequenceNo() {
            sequenceNo = (sequenceNo + 1) & 0xFFFF;
            return sequenceNo;
        }

        // Getters and Setters
        public String getPileCode() {
            return pileCode;
        }

        public void setPileCode(String pileCode) {
            this.pileCode = pileCode;
        }

        public Channel getChannel() {
            return channel;
        }

        public void setChannel(Channel channel) {
            this.channel = channel;
        }

        public int getGunCount() {
            return gunCount;
        }

        public void setGunCount(int gunCount) {
            this.gunCount = gunCount;
        }

        public LocalDateTime getLoginTime() {
            return loginTime;
        }

        public void setLoginTime(LocalDateTime loginTime) {
            this.loginTime = loginTime;
        }

        public LocalDateTime getLastHeartbeatTime() {
            return lastHeartbeatTime;
        }

        public void setLastHeartbeatTime(LocalDateTime lastHeartbeatTime) {
            this.lastHeartbeatTime = lastHeartbeatTime;
        }

        public LocalDateTime getOfflineTime() {
            return offlineTime;
        }

        public void setOfflineTime(LocalDateTime offlineTime) {
            this.offlineTime = offlineTime;
        }

        public boolean isOnline() {
            return online;
        }

        public void setOnline(boolean online) {
            this.online = online;
        }
    }
}