/**
 * 开源代码，仅供学习和交流研究使用，商用请联系三丙
 * 微信：mohan_88888
 * 抖音：程序员三丙
 * 付费课程知识星球：https://t.zsxq.com/aKtXo
 */
package sanbing.jcpp.protocol.provider;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sanbing.jcpp.proto.gen.DownlinkProto.DownlinkRequestMessage;
import sanbing.jcpp.proto.gen.UplinkProto.SessionCloseReason;
import sanbing.jcpp.protocol.domain.ProtocolSession;
import sanbing.jcpp.protocol.provider.impl.DefaultProtocolSessionRegistryProvider;

import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.jupiter.api.Assertions.*;

/**
 * 会话关闭回调单元测试
 * 测试 ProtocolSession 关闭时自动清除 Caffeine 缓存功能
 *
 * @author 九筒
 */
class ProtocolSessionCloseCallbackTest {

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    private DefaultProtocolSessionRegistryProvider sessionRegistryProvider;

    @BeforeEach
    void setUp() {
        sessionRegistryProvider = new DefaultProtocolSessionRegistryProvider();
    }

    /**
     * 测试：会话关闭后应自动从缓存中移除
     */
    @Test
    void testSessionRemovedFromCacheOnClose() {
        log.info("开始测试：会话关闭后应自动从缓存中移除");

        // 1. 创建并注册会话
        TestProtocolSession session = new TestProtocolSession("testProtocol");
        UUID sessionId = session.getId();

        sessionRegistryProvider.register(session);
        log.info("会话已注册: sessionId={}", sessionId);

        // 2. 验证会话存在于缓存中
        ProtocolSession sessionFromCache = sessionRegistryProvider.get(sessionId);
        assertNotNull(sessionFromCache, "注册后会话应存在于缓存中");
        assertEquals(sessionId, sessionFromCache.getId(), "会话ID应匹配");
        log.info("验证会话存在于缓存中");

        // 3. 关闭会话
        session.close(SessionCloseReason.SESSION_CLOSE_ON_CHANNEL_INACTIVE);
        log.info("会话已关闭");

        // 4. 验证会话已从缓存中移除
        ProtocolSession sessionAfterClose = sessionRegistryProvider.get(sessionId);
        assertNull(sessionAfterClose, "关闭后会话应从缓存中移除");
        log.info("验证会话已从缓存中移除");

        // 5. 验证会话状态
        assertTrue(session.isClosed(), "会话 closed 状态应为 true");
        log.info("验证会话 closed 状态为 true");

        log.info("测试通过：会话关闭后自动从缓存中移除");
    }

    /**
     * 测试：重复关闭会话不应抛出异常，且回调只执行一次
     */
    @Test
    void testDuplicateCloseExecutesCallbackOnce() {
        log.info("开始测试：重复关闭会话回调只执行一次");

        // 使用计数器跟踪回调执行次数
        AtomicInteger callbackCount = new AtomicInteger(0);

        // 1. 创建会话并设置自定义回调
        TestProtocolSession session = new TestProtocolSession("testProtocol");
        session.setCloseCallback(id -> {
            callbackCount.incrementAndGet();
            log.info("回调执行，当前次数: {}", callbackCount.get());
        });

        // 2. 第一次关闭
        session.close(SessionCloseReason.SESSION_CLOSE_ON_CHANNEL_INACTIVE);
        assertEquals(1, callbackCount.get(), "第一次关闭后回调应执行1次");
        assertTrue(session.isClosed(), "第一次关闭后应为 closed 状态");

        // 3. 第二次关闭（应被忽略）
        assertDoesNotThrow(() -> session.close(SessionCloseReason.SESSION_CLOSE_DESTRUCTION));
        assertEquals(1, callbackCount.get(), "重复关闭后回调仍应只执行1次");
        assertTrue(session.isClosed(), "重复关闭后仍应为 closed 状态");

        // 4. 第三次关闭（应被忽略）
        assertDoesNotThrow(() -> session.close());
        assertEquals(1, callbackCount.get(), "再次重复关闭后回调仍应只执行1次");

        log.info("测试通过：重复关闭只触发一次回调");
    }

    /**
     * 测试：register 时会自动设置 closeCallback
     */
    @Test
    void testRegisterSetsCloseCallback() {
        log.info("开始测试：register 时会自动设置 closeCallback");

        // 1. 创建会话（此时 closeCallback 为 null）
        TestProtocolSession session = new TestProtocolSession("testProtocol");
        assertNull(session.getCloseCallback(), "注册前 closeCallback 应为 null");

        // 2. 注册会话
        sessionRegistryProvider.register(session);

        // 3. 验证 closeCallback 已设置
        assertNotNull(session.getCloseCallback(), "注册后 closeCallback 应已设置");
        log.info("验证 closeCallback 已设置");

        // 4. 验证关闭时能正确清除缓存
        UUID sessionId = session.getId();
        assertNotNull(sessionRegistryProvider.get(sessionId), "关闭前会话应存在");

        session.close();

        assertNull(sessionRegistryProvider.get(sessionId), "关闭后会话应被移除");
        log.info("验证关闭时正确清除缓存");

        log.info("测试通过：register 自动设置 closeCallback");
    }

    /**
     * 测试：isClosed 方法正确反映会话状态
     */
    @Test
    void testIsClosedReflectsSessionState() {
        log.info("开始测试：isClosed 方法正确反映会话状态");

        TestProtocolSession session = new TestProtocolSession("testProtocol");

        // 新建会话应未关闭
        assertFalse(session.isClosed(), "新建会话应未关闭");

        // 关闭后应为已关闭
        session.close();
        assertTrue(session.isClosed(), "关闭后应为已关闭");

        log.info("测试通过：isClosed 方法正确反映会话状态");
    }

    /**
     * 测试用的 ProtocolSession 实现
     */
    private static class TestProtocolSession extends ProtocolSession {

        public TestProtocolSession(String protocolName) {
            super(protocolName);
        }

        @Override
        public void onDownlink(DownlinkRequestMessage downlinkMsg) {
            // 测试用空实现
        }
    }
}
