package com.njcn.gather.detection.lock;

import org.junit.Before;
import org.junit.Test;

import java.lang.reflect.Field;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import static org.junit.Assert.*;

public class DetectionLockManagerTest {

    private DetectionLockManager manager;

    @Before
    public void setUp() throws Exception {
        manager = DetectionLockManager.getInstance();
        // 通过反射把 current 清零，避免不同测试方法间状态污染
        Field f = DetectionLockManager.class.getDeclaredField("current");
        f.setAccessible(true);
        ((AtomicReference<?>) f.get(manager)).set(null);
    }

    @Test
    public void tryAcquire_whenEmpty_returnsOk() {
        DetectionLockManager.AcquireResult r = manager.tryAcquire("u1", "alice", "page-1");
        assertTrue(r.isOk());
        assertNull(r.getHolder());
        assertEquals("u1", manager.getCurrent().getUserId());
    }

    @Test
    public void tryAcquire_whenHeldByOther_returnsBusyWithHolder() {
        manager.tryAcquire("u1", "alice", "page-1");
        DetectionLockManager.AcquireResult r = manager.tryAcquire("u2", "bob", "page-2");
        assertFalse(r.isOk());
        assertNotNull(r.getHolder());
        assertEquals("u1", r.getHolder().getHolderUserId());
        assertEquals("alice", r.getHolder().getHolderUserName());
    }

    @Test
    public void tryAcquire_reentrantSameUser_refreshesPageAndExpireAt() throws Exception {
        manager.tryAcquire("u1", "alice", "page-1");
        long oldAcquire = manager.getCurrent().getAcquireTime();
        long oldExpire  = manager.getCurrent().getExpireAt();
        // sleep 50ms（远超 Windows 系统时钟 ~15ms 精度），保证时间戳推进
        Thread.sleep(50);
        DetectionLockManager.AcquireResult r = manager.tryAcquire("u1", "alice", "page-2");
        assertTrue(r.isOk());
        assertEquals("page-2", manager.getCurrent().getUserPageId());
        assertTrue("acquireTime 应推进", manager.getCurrent().getAcquireTime() > oldAcquire);
        assertTrue("expireAt 应推进",   manager.getCurrent().getExpireAt() > oldExpire);
    }

    @Test
    public void tryAcquire_whenExpired_anyUserCanAcquire() throws Exception {
        // 直接构造一个 expireAt 在过去的 lock 写进去
        Field f = DetectionLockManager.class.getDeclaredField("current");
        f.setAccessible(true);
        @SuppressWarnings("unchecked")
        AtomicReference<DetectionLock> ref = (AtomicReference<DetectionLock>) f.get(manager);
        long past = System.currentTimeMillis() - 1000;
        ref.set(new DetectionLock("u1", "alice", "page-1", past - 1000, past));

        DetectionLockManager.AcquireResult r = manager.tryAcquire("u2", "bob", "page-2");
        assertTrue(r.isOk());
        assertEquals("u2", manager.getCurrent().getUserId());
    }

    @Test
    public void releaseIfHeldBy_matchingUser_clears() {
        manager.tryAcquire("u1", "alice", "page-1");
        manager.releaseIfHeldBy("u1", "TEST");
        assertNull(manager.getCurrent());
    }

    @Test
    public void releaseIfHeldBy_nonMatchingUser_isNoOp() {
        manager.tryAcquire("u1", "alice", "page-1");
        manager.releaseIfHeldBy("u2", "TEST");
        assertNotNull(manager.getCurrent());
        assertEquals("u1", manager.getCurrent().getUserId());
    }

    @Test
    public void releaseIfHeldBy_whenEmpty_isNoOp() {
        manager.releaseIfHeldBy("u1", "TEST");
        assertNull(manager.getCurrent());
    }

    @Test
    public void releaseIfMatchPage_matchingPage_clears() {
        manager.tryAcquire("u1", "alice", "page-1");
        manager.releaseIfMatchPage("page-1", "TEST");
        assertNull(manager.getCurrent());
    }

    @Test
    public void releaseIfMatchPage_nonMatchingPage_isNoOp() {
        manager.tryAcquire("u1", "alice", "page-1");
        manager.releaseIfMatchPage("page-2", "TEST");
        assertNotNull(manager.getCurrent());
    }

    @Test
    public void forceRelease_alwaysClears() {
        manager.tryAcquire("u1", "alice", "page-1");
        manager.forceRelease("admin", "TEST");
        assertNull(manager.getCurrent());
    }

    @Test
    public void concurrentTryAcquire_onlyOneWins() throws Exception {
        int n = 100;
        ExecutorService pool = Executors.newFixedThreadPool(16);
        CountDownLatch start = new CountDownLatch(1);
        CountDownLatch done  = new CountDownLatch(n);
        AtomicInteger okCount = new AtomicInteger(0);
        // 记下胜出线程的 userId，便于断言 holder 身份与胜出者一致
        AtomicReference<String> winnerUserId = new AtomicReference<>(null);
        for (int i = 0; i < n; i++) {
            final String uid = "u" + i;
            pool.submit(() -> {
                try {
                    start.await();
                    DetectionLockManager.AcquireResult r = manager.tryAcquire(uid, uid, "page-" + uid);
                    if (r.isOk()) {
                        okCount.incrementAndGet();
                        winnerUserId.set(uid);
                    }
                } catch (InterruptedException ignored) {
                } finally {
                    done.countDown();
                }
            });
        }
        start.countDown();
        done.await(5, TimeUnit.SECONDS);
        pool.shutdownNow();
        assertEquals("100 个不同账号并发只能有 1 个抢到锁", 1, okCount.get());
        assertNotNull(manager.getCurrent());
        // 持锁者必须就是宣称 ok 的那个线程，不能是别人
        assertEquals("holder 身份必须等于胜出线程的 userId", winnerUserId.get(), manager.getCurrent().getUserId());
    }
}
