package tech.easyflow.skill.gitimport;

import org.eclipse.jgit.lib.Repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.skill.imports.SkillImportPreview;
import tech.easyflow.skill.imports.SkillImportService;

import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 编排 Git 仓库扫描、固定提交复核与标准 Skill 导入预览。
 */
@Service
public class SkillGitImportService {

    private static final Logger LOG = LoggerFactory.getLogger(SkillGitImportService.class);
    private static final int MAX_PREPARE_COUNT = SkillImportService.MAX_BATCH_SKILL_COUNT;

    private final GitRepositoryAccessPolicy accessPolicy;
    private final SkillGitRepositoryReader repositoryReader;
    private final SkillGitCandidateScanner candidateScanner;
    private final SkillGitScanStore scanStore;
    private final SkillGitScanRateLimiter scanRateLimiter;
    private final SkillImportService skillImportService;

    /**
     * 创建 Git Skill 导入编排服务。
     *
     * @param accessPolicy 仓库地址访问策略
     * @param repositoryReader Git 仓库读取器
     * @param candidateScanner Skill 候选扫描器
     * @param scanStore 短期扫描会话仓库
     * @param scanRateLimiter 仓库扫描频控器
     * @param skillImportService 标准 Skill ZIP 导入服务
     */
    public SkillGitImportService(GitRepositoryAccessPolicy accessPolicy,
                                 SkillGitRepositoryReader repositoryReader,
                                 SkillGitCandidateScanner candidateScanner,
                                 SkillGitScanStore scanStore,
                                 SkillGitScanRateLimiter scanRateLimiter,
                                 SkillImportService skillImportService) {
        this.accessPolicy = accessPolicy;
        this.repositoryReader = repositoryReader;
        this.candidateScanner = candidateScanner;
        this.scanStore = scanStore;
        this.scanRateLimiter = scanRateLimiter;
        this.skillImportService = skillImportService;
    }

    /**
     * 扫描任意受支持的 HTTPS Git 仓库并登记短期固定提交结果。
     *
     * @param repositoryUrl Git 仓库地址，可省略 .git 后缀
     * @return 候选扫描结果
     */
    public SkillGitScanResult scan(String repositoryUrl) {
        scanRateLimiter.check();
        String normalizedUrl = accessPolicy.normalizeRepositoryUrl(repositoryUrl);
        try (SkillGitRepositoryReader.Snapshot snapshot = repositoryReader.openDefault(normalizedUrl)) {
            SkillGitRepositoryScan repositoryScan = candidateScanner.scan(snapshot);
            SkillGitScanResult.RepositoryInfo repository = new SkillGitScanResult.RepositoryInfo(
                    repositoryName(normalizedUrl), normalizedUrl,
                    Repository.shortenRefName(snapshot.branchRef()), snapshot.commitSha());
            return scanStore.create(repository, snapshot.branchRef(), repositoryScan.candidates());
        }
    }

    /**
     * 复核扫描时的固定提交，并把选中候选转换为既有标准包导入预览。
     *
     * @param scanToken 扫描令牌
     * @param candidateIds 选中的候选 ID
     * @return 与现有 ZIP 导入一致的预览列表
     */
    public List<SkillImportPreview> prepare(String scanToken, List<String> candidateIds) {
        validateCandidateIds(candidateIds);
        return scanStore.withLockedSession(scanToken,
                session -> prepareLocked(scanToken, session, List.copyOf(candidateIds)));
    }

    private List<SkillImportPreview> prepareLocked(String scanToken,
                                                   SkillGitScanStore.Session session,
                                                   List<String> candidateIds) {
        SkillGitScanResult original = session.result();
        Set<String> allowedIds = new HashSet<>();
        original.candidates().stream().filter(SkillGitScanResult.Candidate::importable)
                .map(SkillGitScanResult.Candidate::candidateId).forEach(allowedIds::add);
        if (!allowedIds.containsAll(candidateIds)) {
            throw new BusinessException("所选 Git Skill 不存在或当前不可导入，请重新扫描");
        }

        List<SkillImportPreview> previews = new ArrayList<>(candidateIds.size());
        try (SkillGitRepositoryReader.Snapshot snapshot = repositoryReader.openCommit(
                original.repository().url(), session.branchRef(), original.repository().commitSha())) {
            SkillGitRepositoryScan current = candidateScanner.scan(snapshot);
            Set<String> currentImportableIds = new HashSet<>();
            current.candidates().stream().filter(SkillGitScanResult.Candidate::importable)
                    .map(SkillGitScanResult.Candidate::candidateId).forEach(currentImportableIds::add);
            if (!currentImportableIds.containsAll(candidateIds)) {
                throw new BusinessException("Git 仓库扫描结果已变化，请重新扫描");
            }
            for (String candidateId : candidateIds) {
                SkillGitScanResult.Candidate candidate = current.candidates().stream()
                        .filter(item -> item.candidateId().equals(candidateId))
                        .findFirst().orElseThrow(() -> new BusinessException("Git Skill 候选不存在，请重新扫描"));
                Path archive = candidateScanner.createStandardZip(snapshot, current, candidateId);
                try {
                    previews.add(skillImportService.preview(candidate.name() + ".zip", archive.toFile()));
                } finally {
                    candidateScanner.deleteArchive(archive);
                }
            }
            scanStore.remove(scanToken);
            return List.copyOf(previews);
        } catch (RuntimeException exception) {
            cancelPreparedPreviews(previews);
            throw exception;
        }
    }

    private void cancelPreparedPreviews(List<SkillImportPreview> previews) {
        for (SkillImportPreview preview : previews) {
            if (preview == null || preview.getImportToken() == null || preview.getImportToken().isBlank()) {
                continue;
            }
            try {
                skillImportService.cancel(preview.getImportToken());
            } catch (RuntimeException cleanupFailure) {
                LOG.error("回滚 Git Skill 导入预览失败，token={}", preview.getImportToken(), cleanupFailure);
            }
        }
    }

    private void validateCandidateIds(List<String> candidateIds) {
        if (candidateIds == null || candidateIds.isEmpty()) {
            throw new BusinessException("请选择要导入的 Git Skill");
        }
        if (candidateIds.size() > MAX_PREPARE_COUNT) {
            throw new BusinessException("单次最多导入 " + MAX_PREPARE_COUNT + " 个 Git Skill");
        }
        if (candidateIds.stream().anyMatch(id -> id == null || !id.matches("^[a-fA-F0-9]{64}$"))) {
            throw new BusinessException("Git Skill 候选参数格式不正确");
        }
        if (new HashSet<>(candidateIds).size() != candidateIds.size()) {
            throw new BusinessException("不能重复选择同一个 Git Skill");
        }
    }

    private String repositoryName(String repositoryUrl) {
        try {
            String path = new URI(repositoryUrl).getPath();
            String name = path.substring(path.lastIndexOf('/') + 1);
            return name.toLowerCase(java.util.Locale.ROOT).endsWith(".git")
                    ? name.substring(0, name.length() - 4) : name;
        } catch (URISyntaxException | RuntimeException exception) {
            throw new BusinessException("Git 仓库地址格式不正确");
        }
    }
}
