package tech.easyflow.skill.imports;

import com.alicp.jetcache.AutoReleaseLock;
import com.alicp.jetcache.Cache;
import com.mybatisflex.core.query.QueryWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.annotation.Propagation;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.filestorage.FileStorageService;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.skill.entity.SkillImportStage;
import tech.easyflow.skill.mapper.SkillImportStageMapper;

import java.math.BigInteger;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 基于数据库临时索引、JetCache 单次锁与文件存储的 Skill 导入会话仓库。
 */
@Service
public class SkillImportStageStore {

    private static final Logger LOG = LoggerFactory.getLogger(SkillImportStageStore.class);
    private static final Duration SESSION_TTL = Duration.ofMinutes(30);
    private static final Duration PROCESSING_TTL = Duration.ofHours(2);
    private static final String CACHE_PREFIX = "skill:import:";

    private final Cache<String, Object> defaultCache;
    private final SkillImportStageMapper stageMapper;
    private final FileStorageService fileStorageService;

    /**
     * 创建 Skill 导入会话仓库。
     *
     * @param defaultCache 平台默认缓存
     * @param stageMapper 临时包 Mapper
     * @param fileStorageService 文件存储
     */
    public SkillImportStageStore(@Qualifier("defaultCache") Cache<String, Object> defaultCache,
                                 SkillImportStageMapper stageMapper,
                                 @Qualifier("default") FileStorageService fileStorageService) {
        this.defaultCache = defaultCache;
        this.stageMapper = stageMapper;
        this.fileStorageService = fileStorageService;
    }

    /**
     * 登记临时包并返回单次令牌。
     *
     * @param filePath 临时包存储路径
     * @param originalName 原始文件名
     * @return 临时包索引
     */
    @Transactional(rollbackFor = Exception.class)
    public SkillImportStage create(String filePath, String originalName) {
        LoginAccount account = requireAccount();
        Date now = new Date();
        SkillImportStage stage = new SkillImportStage();
        stage.setImportToken(UUID.randomUUID().toString().replace("-", ""));
        stage.setTenantId(account.getTenantId());
        stage.setAccountId(account.getId());
        stage.setFilePath(filePath);
        stage.setOriginalName(originalName);
        stage.setStatus("PENDING");
        stage.setCreated(now);
        stage.setExpiresAt(new Date(now.getTime() + SESSION_TTL.toMillis()));
        if (stageMapper.insert(stage) != 1) {
            throw new BusinessException(500, 500, "创建 Skill 导入会话失败，请稍后重试");
        }
        // 缓存只保存小型索引；完整包始终留在受控文件存储中。
        defaultCache.put(cacheKey(stage.getImportToken()), stage, SESSION_TTL.toMinutes(), TimeUnit.MINUTES);
        return stage;
    }

    /**
     * 原子消费导入令牌。令牌一旦消费，即使业务导入失败也不能重复执行。
     *
     * @param token 导入令牌
     * @return 被消费的临时包索引
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public SkillImportStage consume(String token) {
        validateToken(token);
        LoginAccount account = requireAccount();
        try (AutoReleaseLock lock = defaultCache.tryLock(lockKey(token), 60, TimeUnit.SECONDS)) {
            if (lock == null) {
                throw new BusinessException("Skill 导入正在处理中，请勿重复提交");
            }
            SkillImportStage stage = findOwnedStage(token, account);
            assertOwner(stage, account);
            Date now = new Date();
            Date processingExpiresAt = new Date(now.getTime() + PROCESSING_TTL.toMillis());
            if (stageMapper.consume(token, account.getTenantId(), account.getId(), now, processingExpiresAt) != 1) {
                throw new BusinessException("Skill 导入令牌已过期或已被使用，请重新预览");
            }
            stage.setStatus("PROCESSING");
            stage.setExpiresAt(processingExpiresAt);
            defaultCache.remove(cacheKey(token));
            return stage;
        }
    }

    /**
     * 取消尚未消费的导入会话并释放临时包。
     *
     * @param token 导入令牌
     */
    public void cancel(String token) {
        validateToken(token);
        LoginAccount account = requireAccount();
        try (AutoReleaseLock lock = defaultCache.tryLock(lockKey(token), 30, TimeUnit.SECONDS)) {
            if (lock == null) {
                throw new BusinessException("Skill 导入正在处理中，暂时无法取消");
            }
            SkillImportStage stage = findOwnedStage(token, account);
            assertOwner(stage, account);
            if (!"PENDING".equals(stage.getStatus())) {
                throw new BusinessException("Skill 导入正在处理中，不能取消");
            }
            Date now = new Date();
            if (stageMapper.beginCancel(token, account.getTenantId(), account.getId(), now) != 1) {
                throw new BusinessException("Skill 导入状态已变化，请刷新后重试");
            }
            stage.setStatus("PROCESSING");
            stage.setExpiresAt(now);
            defaultCache.remove(cacheKey(token));
            deleteFile(stage);
            if (stageMapper.finishCancel(token, account.getTenantId(), account.getId()) != 1) {
                throw new BusinessException("Skill 导入状态已变化，请刷新后重试");
            }
        }
    }

    /**
     * 完成导入后释放临时包和索引。
     *
     * @param stage 临时包索引
     */
    public void complete(SkillImportStage stage) {
        if (stage != null) {
            cleanup(stage);
        }
    }

    /**
     * 定时清理过期或已消费但未完成清理的临时包。
     */
    @Scheduled(fixedDelayString = "${easyflow.skill.import-cleanup-delay-ms:300000}")
    public void cleanupExpired() {
        List<SkillImportStage> expired = stageMapper.selectListByQuery(QueryWrapper.create()
                .le(SkillImportStage::getExpiresAt, new Date())
                .orderBy("expires_at asc")
                .limit(100));
        for (SkillImportStage stage : expired) {
            try (AutoReleaseLock lock = defaultCache.tryLock(lockKey(stage.getImportToken()), 30, TimeUnit.SECONDS)) {
                if (lock == null) {
                    continue;
                }
                SkillImportStage current = stageMapper.selectOneById(stage.getImportToken());
                if (current != null && current.getExpiresAt() != null && !current.getExpiresAt().after(new Date())) {
                    cleanup(current);
                }
            } catch (RuntimeException exception) {
                LOG.error("清理过期 Skill 导入临时包失败，token={}", stage.getImportToken(), exception);
            }
        }
    }

    private void cleanup(SkillImportStage stage) {
        deleteFile(stage);
        stageMapper.deleteById(stage.getImportToken());
        defaultCache.remove(cacheKey(stage.getImportToken()));
    }

    private void deleteFile(SkillImportStage stage) {
        try {
            fileStorageService.delete(stage.getFilePath());
        } catch (RuntimeException exception) {
            if (isFileAlreadyAbsent(exception)) {
                return;
            }
            LOG.error("删除 Skill 导入临时包失败，token={}, path={}",
                    stage.getImportToken(), stage.getFilePath(), exception);
            throw new BusinessException(500, 500, "清理 Skill 导入临时包失败，请稍后重试", exception);
        }
    }

    /**
     * 判断存储异常是否表示目标文件已经不存在。
     *
     * @param exception 存储删除异常
     * @return 文件已不存在时为 true
     */
    private boolean isFileAlreadyAbsent(RuntimeException exception) {
        Throwable current = exception;
        while (current != null) {
            if (current instanceof java.io.FileNotFoundException
                    || current instanceof java.nio.file.NoSuchFileException) {
                return true;
            }
            current = current.getCause();
        }
        return false;
    }

    private void assertOwner(SkillImportStage stage, LoginAccount account) {
        if (stage == null) {
            throw new BusinessException(404, 404, "Skill 导入令牌不存在或已过期");
        }
        if (!account.getId().equals(stage.getAccountId()) || !account.getTenantId().equals(stage.getTenantId())) {
            throw new BusinessException(403, 403, "无权限使用该 Skill 导入令牌");
        }
        if (stage.getExpiresAt() == null || !stage.getExpiresAt().after(new Date())) {
            throw new BusinessException("Skill 导入令牌已过期，请重新预览");
        }
    }

    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("Skill 导入令牌格式不正确");
        }
    }

    private SkillImportStage findOwnedStage(String token, LoginAccount account) {
        SkillImportStage stage = stageMapper.selectOneByQuery(QueryWrapper.create()
                .eq(SkillImportStage::getImportToken, token)
                .eq(SkillImportStage::getTenantId, account.getTenantId())
                .eq(SkillImportStage::getAccountId, account.getId()));
        if (stage == null && stageMapper.selectCountByQuery(QueryWrapper.create()
                .eq(SkillImportStage::getImportToken, token)) > 0) {
            throw new BusinessException(403, 403, "无权限使用该 Skill 导入令牌");
        }
        return stage;
    }

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

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