package com.njcn.gather.detection.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import com.njcn.gather.detection.pojo.vo.DetectionLockHolderVO;

/**
 * 检测互斥锁管理器（进程内单例）。
 * 详细设计：docs/superpowers/specs/2026-05-28-单用户检测互斥-design.md
 */
@Slf4j
public final class DetectionLockManager {

    private static final long LOCK_MAX_HOLD_MS = TimeUnit.HOURS.toMillis(4);

    private static final DetectionLockManager INSTANCE = new DetectionLockManager();

    public static DetectionLockManager getInstance() {
        return INSTANCE;
    }

    private final AtomicReference<DetectionLock> current = new AtomicReference<>(null);

    private DetectionLockManager() {}

    /** 抢锁。同账号视为重入（刷新 page/expireAt）。 */
    public AcquireResult tryAcquire(String userId, String userName, String userPageId) {
        for (int attempt = 0; attempt < 2; attempt++) {
            DetectionLock cur = current.get();
            long now = System.currentTimeMillis();
            // 空闲 或 绝对超时已过 → 直接抢
            if (cur == null || now > cur.getExpireAt()) {
                DetectionLock fresh = new DetectionLock(userId, userName, userPageId, now, now + LOCK_MAX_HOLD_MS);
                if (current.compareAndSet(cur, fresh)) {
                    log.info("DetectionLock acquired by userId={}, userName={}, userPageId={}", userId, userName, userPageId);
                    return AcquireResult.ok();
                }
                continue;  // CAS 失败重试
            }
            // 同账号重入 → 刷新
            if (userId.equals(cur.getUserId())) {
                DetectionLock refreshed = new DetectionLock(userId, userName, userPageId, now, now + LOCK_MAX_HOLD_MS);
                if (current.compareAndSet(cur, refreshed)) {
                    log.debug("DetectionLock reentered by userId={}, new userPageId={}", userId, userPageId);
                    return AcquireResult.ok();
                }
                continue;
            }
            // 被他人持有
            return AcquireResult.busy(toHolderVO(cur));
        }
        // 两次 CAS 都失败属于罕见高并发场景：绝不返回 ok()（那样会让调用方误以为持锁）。
        // 返回 busy，data 可能为 null（锁刚被释放）；调用方/前端按"请重试"处理。
        DetectionLock cur = current.get();
        return AcquireResult.busy(cur == null ? null : toHolderVO(cur));
    }

    /** 仅当 holder.userId == userId 才释放（幂等）。
     *  循环终止性：每轮 CAS 失败意味着 current 被其他线程改写；
     *  下一轮 get 后 cur 可能变成 null 或不再匹配 userId，命中前置 return 退出。
     *  唯一可能继续的情况是另一线程把它换成了同 userId 的新 lock，下一轮 CAS 会再次尝试；
     *  最坏情况下 CAS 成功，仍然终止。 */
    public void releaseIfHeldBy(String userId, String reason) {
        while (true) {
            DetectionLock cur = current.get();
            if (cur == null || !cur.getUserId().equals(userId)) return;
            if (current.compareAndSet(cur, null)) {
                log.info("DetectionLock released, reason={}, userId={}", reason, userId);
                return;
            }
        }
    }

    /** 仅当 holder.userPageId == userPageId 才释放（幂等）。终止性同 releaseIfHeldBy。 */
    public void releaseIfMatchPage(String userPageId, String reason) {
        while (true) {
            DetectionLock cur = current.get();
            if (cur == null || !cur.getUserPageId().equals(userPageId)) return;
            if (current.compareAndSet(cur, null)) {
                log.info("DetectionLock released, reason={}, userPageId={}", reason, userPageId);
                return;
            }
        }
    }

    /** 管理员强制释放，不校验 holder。 */
    public void forceRelease(String operatorUserId, String reason) {
        DetectionLock cur = current.getAndSet(null);
        if (cur != null) {
            log.warn("DetectionLock force-released by operator={}, victim userId={}, reason={}",
                    operatorUserId, cur.getUserId(), reason);
        }
    }

    /** 返回当前 holder 快照；返回 null 表示空闲。 */
    public DetectionLock getCurrent() {
        DetectionLock cur = current.get();
        // 顺手做惰性超时回收（spec R5）
        if (cur != null && System.currentTimeMillis() > cur.getExpireAt()) {
            current.compareAndSet(cur, null);
            return null;
        }
        return cur;
    }

    /** 把 DetectionLock 转成给前端的 VO。 */
    public static DetectionLockHolderVO toHolderVO(DetectionLock lock) {
        DetectionLockHolderVO vo = new DetectionLockHolderVO();
        vo.setHolderUserId(lock.getUserId());
        vo.setHolderUserName(lock.getUserName());
        vo.setAcquireTime(new Date(lock.getAcquireTime()));
        vo.setExpireAt(new Date(lock.getExpireAt()));
        return vo;
    }

    /** 抢锁结果。 */
    public static final class AcquireResult {
        private final boolean ok;
        private final DetectionLockHolderVO holder;

        private AcquireResult(boolean ok, DetectionLockHolderVO holder) {
            this.ok = ok;
            this.holder = holder;
        }
        public static AcquireResult ok() { return new AcquireResult(true, null); }
        public static AcquireResult busy(DetectionLockHolderVO holder) { return new AcquireResult(false, holder); }
        public boolean isOk() { return ok; }
        public DetectionLockHolderVO getHolder() { return holder; }
    }
}
