package tech.easyflow.skill.gitimport;

import com.alicp.jetcache.AutoReleaseLock;
import com.alicp.jetcache.Cache;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

/**
 * 保存短期 Git 扫描会话并约束租户、账号与并发使用。
 */
@Service
public class SkillGitScanStore {

    private static final String CACHE_PREFIX = "skill:git-scan:";

    private final Cache<String, Object> defaultCache;
    private final SkillGitImportProperties properties;

    /**
     * 创建 Git 扫描会话仓库。
     *
     * @param defaultCache 平台默认缓存
     * @param properties Git 导入配置
     */
    public SkillGitScanStore(@Qualifier("defaultCache") Cache<String, Object> defaultCache,
                             SkillGitImportProperties properties) {
        this.defaultCache = defaultCache;
        this.properties = properties;
    }

    /**
     * 创建绑定当前账号的扫描会话。
     *
     * @param repository 仓库固定提交信息
     * @param branchRef 默认分支完整引用
     * @param candidates 候选列表
     * @return 可返回给前端的扫描结果
     */
    public SkillGitScanResult create(SkillGitScanResult.RepositoryInfo repository,
                                     String branchRef,
                                     List<SkillGitScanResult.Candidate> candidates) {
        LoginAccount account = requireAccount();
        String token = UUID.randomUUID().toString().replace("-", "");
        Date expiresAt = new Date(System.currentTimeMillis() + properties.getScanTtl().toMillis());
        int importable = (int) candidates.stream().filter(SkillGitScanResult.Candidate::importable).count();
        SkillGitScanResult result = new SkillGitScanResult(token, repository,
                new SkillGitScanResult.Summary(candidates.size(), importable, candidates.size() - importable),
                candidates, expiresAt);
        Session session = new Session(account.getTenantId(), account.getId(), branchRef, result);
        long ttlSeconds = Math.max(1L, properties.getScanTtl().toSeconds());
        defaultCache.put(cacheKey(token), session, ttlSeconds, TimeUnit.SECONDS);
        return result;
    }

    /**
     * 在扫描会话单次锁内执行业务动作。
     *
     * @param token 扫描令牌
     * @param action 受锁保护的动作
     * @param <T> 返回值类型
     * @return 动作结果
     */
    public <T> T withLockedSession(String token, Function<Session, T> action) {
        validateToken(token);
        LoginAccount account = requireAccount();
        try (AutoReleaseLock lock = defaultCache.tryLock(lockKey(token), 5, TimeUnit.MINUTES)) {
            if (lock == null) {
                throw new BusinessException("Git 仓库导入正在处理中，请勿重复提交");
            }
            return action.apply(findOwned(token, account));
        }
    }

    /**
     * 删除已成功转换为标准导入预览的扫描会话。
     *
     * @param token 扫描令牌
     */
    public void remove(String token) {
        validateToken(token);
        defaultCache.remove(cacheKey(token));
    }

    private Session findOwned(String token, LoginAccount account) {
        Object cached = defaultCache.get(cacheKey(token));
        if (!(cached instanceof Session session)) {
            throw new BusinessException(404, 404, "Git 仓库扫描结果不存在或已过期，请重新扫描");
        }
        if (!account.getTenantId().equals(session.tenantId())
                || !account.getId().equals(session.accountId())) {
            throw new BusinessException(403, 403, "无权限使用该 Git 仓库扫描结果");
        }
        Date expiresAt = session.result().expiresAt();
        if (expiresAt == null || !expiresAt.after(new Date())) {
            defaultCache.remove(cacheKey(token));
            throw new BusinessException("Git 仓库扫描结果已过期，请重新扫描");
        }
        return session;
    }

    private LoginAccount requireAccount() {
        LoginAccount account = SaTokenUtil.getLoginAccount();
        if (account == null || account.getId() == null || account.getTenantId() == null) {
            throw new BusinessException(401, 401, "未登录或登录态无效");
        }
        return account;
    }

    private void validateToken(String token) {
        if (token == null || !token.matches("^[a-fA-F0-9]{32}$")) {
            throw new BusinessException("Git 仓库扫描令牌格式不正确");
        }
    }

    private String cacheKey(String token) {
        return CACHE_PREFIX + token;
    }

    private String lockKey(String token) {
        return CACHE_PREFIX + "lock:" + token;
    }

    /**
     * 可序列化的短期扫描会话。
     *
     * @param tenantId 租户 ID
     * @param accountId 账号 ID
     * @param branchRef 默认分支完整引用
     * @param result 对外扫描结果
     */
    public record Session(java.math.BigInteger tenantId,
                          java.math.BigInteger accountId,
                          String branchRef,
                          SkillGitScanResult result) implements Serializable {

        @Serial
        private static final long serialVersionUID = 1L;
    }
}
