<?php

namespace XZD\AI\V5\Core;

/**
 * 分布式锁 — 基于 WordPress Transient
 *
 * 用于防止并发操作冲突（批量标签生成、流水线执行等）
 */
class Lock
{
    private string $key;
    private string $token;
    private int $ttl;

    private function __construct(string $key, int $ttl = 30)
    {
        $this->key = 'xzd_lock_' . md5($key);
        $this->token = wp_generate_password(32, false);
        $this->ttl = max(5, min($ttl, 900)); // 5-900秒，匹配AS最长超时
    }

    /**
     * 创建锁实例
     */
    public static function for(string $key, int $ttl = 30): self
    {
        return new self($key, $ttl);
    }

    /**
     * 尝试获取锁
     *
     * @param int $forceAfter 锁持有超过此秒数时强制释放（0=不强制）
     * @return bool 是否成功获取
     */
    public function acquire(int $forceAfter = 0): bool
    {
        $locked = get_transient($this->key);
        if ($locked !== false) {
            // 锁过期保护 — 超过 forceAfter 秒强制释放，防止 PHP 崩溃后锁永久残留
            if ($forceAfter > 0) {
                $timeout = get_option('_transient_timeout_' . $this->key);
                if ($timeout) {
                    $heldSeconds = ($this->ttl - max(0, (int)$timeout - time()));
                    if ($heldSeconds >= $forceAfter) {
                        delete_transient($this->key);
                        // TOCTOU 竞态保护：set 后二次 get 验证，若被并发覆盖则返回 false
                        $acquired = set_transient($this->key, $this->token, $this->ttl);
                        if (!$acquired) {
                            return false;
                        }
                        $actual = get_transient($this->key);
                        if ($actual !== $this->token) {
                            // 被并发 forceAfter 覆盖 → 对方持锁
                            return false;
                        }
                        return true;
                    }
                }
            }
            return false;
        }
        // check-then-set 竞态保护：set 后二次 get 验证 token，若被并发覆盖则返回 false
        $acquired = set_transient($this->key, $this->token, $this->ttl);
        if (!$acquired) {
            return false;
        }
        $actual = get_transient($this->key);
        if ($actual !== $this->token) {
            // 被并发进程覆盖 → 对方持锁，竞争失败
            return false;
        }
        return true;
    }

    /**
     * 强制释放锁（无论是否当前持有者）
     */
    public function forceRelease(): bool
    {
        return delete_transient($this->key);
    }

    /**
     * 释放锁（仅当自己是持有者时释放，防止误删并发锁）
     *
     * @return bool 是否成功释放
     */
    public function release(): bool
    {
        $current = get_transient($this->key);
        if ($current !== $this->token) {
            return false; // 锁不属于当前持有者
        }
        // TOCTOU 保护：先标记释放意图，再二次验证，防止删掉并发重获取的锁
        $releaseMarker = 'release_' . $this->token;
        $set = set_transient($this->key, $releaseMarker, 2);
        if (!$set) {
            return false;
        }
        $verify = get_transient($this->key);
        if ($verify !== $releaseMarker) {
            // 释放期间锁被并发重获取，放弃删除
            return false;
        }
        return delete_transient($this->key);
    }

    /**
     * 检查锁是否存在
     */
    public function isLocked(): bool
    {
        return get_transient($this->key) !== false;
    }

    /**
     * 获取锁的剩余时间（秒）
     */
    public function remainingTime(): int
    {
        // Transient 不直接提供 TTL，估算
        $timeout = get_option('_transient_timeout_' . $this->key);
        if (!$timeout) {
            return 0;
        }
        return max(0, (int) $timeout - time());
    }

}
