package tech.easyflow.agent.runtime.lock;

import org.springframework.stereotype.Component;
import tech.easyflow.agent.config.AgentRuntimeProperties;
import tech.easyflow.common.cache.RedisLockExecutor;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.math.BigInteger;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 基于 Redis 的 Agent 会话级运行锁。
 */
@Component
public class RedisAgentRunLock implements AgentRunLock {

    private static final String LOCK_PREFIX = "easyflow:agent:run:";
    private static final ScheduledExecutorService RENEW_EXECUTOR = Executors.newSingleThreadScheduledExecutor(task -> {
        Thread thread = new Thread(task, "agent-run-lock-renew");
        thread.setDaemon(true);
        return thread;
    });

    private final RedisLockExecutor redisLockExecutor;
    private final AgentRuntimeProperties properties;

    /**
     * 创建 Redis Agent 运行锁。
     *
     * @param redisLockExecutor Redis 锁执行器
     * @param properties Agent 运行态配置
     */
    public RedisAgentRunLock(RedisLockExecutor redisLockExecutor, AgentRuntimeProperties properties) {
        this.redisLockExecutor = redisLockExecutor;
        this.properties = properties;
    }

    @Override
    public Handle acquire(BigInteger agentId, String sessionId) {
        try {
            RedisLockExecutor.LockHandle handle = redisLockExecutor.acquire(
                    lockKey(agentId, sessionId),
                    properties.getLockWaitTimeout(),
                    properties.getLockLeaseTimeout());
            return new RedisHandle(handle, scheduleRenew(handle));
        } catch (IllegalStateException e) {
            throw new BusinessException("当前 Agent 会话正在运行，请稍后再试");
        }
    }

    private ScheduledFuture<?> scheduleRenew(RedisLockExecutor.LockHandle handle) {
        long intervalMillis = Math.max(1000L, properties.getLockRenewInterval().toMillis());
        return RENEW_EXECUTOR.scheduleAtFixedRate(handle::renew, intervalMillis, intervalMillis, TimeUnit.MILLISECONDS);
    }

    private String lockKey(BigInteger agentId, String sessionId) {
        return LOCK_PREFIX + "agent:" + (agentId == null ? "unknown" : agentId)
                + ":session:" + (sessionId == null ? "unknown" : sessionId);
    }

    private static final class RedisHandle implements Handle {

        private final RedisLockExecutor.LockHandle delegate;
        private final ScheduledFuture<?> renewTask;
        private final AtomicBoolean released = new AtomicBoolean(false);

        private RedisHandle(RedisLockExecutor.LockHandle delegate, ScheduledFuture<?> renewTask) {
            this.delegate = delegate;
            this.renewTask = renewTask;
        }

        @Override
        public boolean renew() {
            return delegate.renew();
        }

        @Override
        public void release() {
            if (!released.compareAndSet(false, true)) {
                return;
            }
            if (renewTask != null) {
                renewTask.cancel(false);
            }
            delegate.release();
        }
    }
}
