package tech.easyflow.agent.runtime.workspace;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import tech.easyflow.agent.config.AgentWorkspaceProperties;
import tech.easyflow.agent.runtime.AgentRunRegistry;
import tech.easyflow.agent.runtime.lock.AgentRunLock;

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.math.BigInteger;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 清理过期且没有活动运行保护的本机会话工作区。
 */
@Component
public class AgentWorkspaceCleanupService {

    private static final Logger LOG = LoggerFactory.getLogger(AgentWorkspaceCleanupService.class);
    private static final int MAX_DELETIONS_PER_RUN = 50;

    private final AgentWorkspaceResolver resolver;
    private final AgentWorkspaceProperties properties;
    private final AgentRunRegistry runRegistry;
    private final AgentRunLock agentRunLock;

    /**
     * 创建工作区清理服务。
     *
     * @param resolver 工作区解析器
     * @param properties 工作区配置
     * @param runRegistry 活动运行注册表
     * @param agentRunLock 会话级分布式运行锁
     */
    public AgentWorkspaceCleanupService(AgentWorkspaceResolver resolver,
                                        AgentWorkspaceProperties properties,
                                        AgentRunRegistry runRegistry,
                                        AgentRunLock agentRunLock) {
        this.resolver = resolver;
        this.properties = properties;
        this.runRegistry = runRegistry;
        this.agentRunLock = agentRunLock;
    }

    /**
     * 按固定深度扫描会话目录并执行有界清理。
     */
    @Scheduled(fixedDelayString = "${easyflow.agent.workspace.cleanup-interval:30m}")
    public void cleanup() {
        Instant threshold = Instant.now().minus(properties.getRetention());
        AtomicInteger deleted = new AtomicInteger();
        try (var tenants = Files.list(resolver.getRealRoot())) {
            tenants.filter(this::businessDirectory).forEach(tenant -> scanAgents(tenant, threshold, deleted));
        } catch (IOException error) {
            LOG.error("Scan Agent workspace root failed", error);
        }
    }

    private void scanAgents(Path tenant, Instant threshold, AtomicInteger deleted) {
        if (deleted.get() >= MAX_DELETIONS_PER_RUN) {
            return;
        }
        try (var agents = Files.list(tenant)) {
            agents.filter(this::businessDirectory).forEach(agent -> scanSessions(agent, threshold, deleted));
        } catch (IOException error) {
            LOG.error("Scan Agent workspace tenant directory failed", error);
        }
    }

    private void scanSessions(Path agent, Instant threshold, AtomicInteger deleted) {
        if (deleted.get() >= MAX_DELETIONS_PER_RUN) {
            return;
        }
        try (var sessions = Files.list(agent)) {
            sessions.filter(this::businessDirectory).forEach(session -> {
                if (deleted.get() >= MAX_DELETIONS_PER_RUN || runRegistry.hasActiveSession(session.getFileName().toString())) {
                    return;
                }
                AgentRunLock.Handle lockHandle = tryAcquireSessionLock(agent, session);
                if (lockHandle == null) {
                    return;
                }
                try (lockHandle) {
                    if (runRegistry.hasActiveSession(session.getFileName().toString())) {
                        return;
                    }
                    Path activity = resolver.activityFile(session);
                    Instant lastActive = Files.exists(activity)
                            ? Files.getLastModifiedTime(activity).toInstant()
                            : Files.getLastModifiedTime(session).toInstant();
                    if (lastActive.isAfter(threshold)) {
                        return;
                    }
                    if (runRegistry.hasActiveSession(session.getFileName().toString())) {
                        return;
                    }
                    deleteTree(session);
                    Files.deleteIfExists(activity);
                    deleted.incrementAndGet();
                } catch (IOException error) {
                    LOG.error("Clean expired Agent workspace failed", error);
                }
            });
        } catch (IOException error) {
            LOG.error("Scan Agent workspace session directory failed", error);
        }
    }

    private AgentRunLock.Handle tryAcquireSessionLock(Path agent, Path session) {
        try {
            return agentRunLock.tryAcquire(
                    new BigInteger(agent.getFileName().toString()), session.getFileName().toString());
        } catch (RuntimeException error) {
            LOG.error("Acquire Agent workspace cleanup lock failed", error);
            return null;
        }
    }

    private boolean businessDirectory(Path path) {
        return Files.isDirectory(path, java.nio.file.LinkOption.NOFOLLOW_LINKS)
                && !Files.isSymbolicLink(path)
                && !path.equals(resolver.getActivityRoot());
    }

    private void deleteTree(Path root) throws IOException {
        Files.walkFileTree(root, new SimpleFileVisitor<>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Files.delete(file);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path directory, IOException error) throws IOException {
                if (error != null) {
                    throw error;
                }
                Files.delete(directory);
                return FileVisitResult.CONTINUE;
            }
        });
    }
}
