package tech.easyflow.skill.imports;

import com.easyagents.skill.codec.SkillPackageWriteOptions;
import com.easyagents.skill.codec.ZipSkillPackageCodec;
import com.easyagents.skill.exception.SkillPackageException;
import com.easyagents.skill.model.SkillPackage;
import com.easyagents.skill.model.SkillPackageLayout;
import com.easyagents.skill.validation.SkillValidationIssue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.skill.entity.Skill;
import tech.easyflow.skill.service.SkillService;
import tech.easyflow.skill.store.DBSkillContentStore;
import tech.easyflow.skill.support.SkillModelConverter;

import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 标准 Skill ZIP 导出服务。
 */
@Service
public class SkillExportServiceImpl implements SkillExportService {

    private static final Logger LOG = LoggerFactory.getLogger(SkillExportServiceImpl.class);

    private final SkillService skillService;
    private final DBSkillContentStore contentStore;

    /**
     * 创建标准 Skill ZIP 导出服务。
     *
     * @param skillService Skill 服务
     * @param contentStore 二进制内容仓库
     */
    public SkillExportServiceImpl(SkillService skillService, DBSkillContentStore contentStore) {
        this.skillService = skillService;
        this.contentStore = contentStore;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SkillExportArtifact prepare(Collection<BigInteger> skillIds) {
        if (skillIds == null || skillIds.isEmpty()) {
            throw new BusinessException("请选择要导出的 Skill");
        }
        List<Skill> skills = loadAuthorizedSkills(skillIds);
        Path target = null;
        try {
            target = Files.createTempFile("easyflow-skill-standard-", ".zip");
            SkillPackage skillPackage = new SkillPackage(
                    skills.size() == 1 ? SkillPackageLayout.SINGLE_DIRECTORY : SkillPackageLayout.MULTI_DIRECTORY,
                    skills.stream().map(SkillModelConverter::toAgentSkill).toList());
            try (OutputStream output = Files.newOutputStream(target, StandardOpenOption.TRUNCATE_EXISTING)) {
                new ZipSkillPackageCodec(contentStore)
                        .encode(skillPackage, output, SkillPackageWriteOptions.defaults());
            }
            String fileStem = skills.size() == 1 ? safeFileStem(skills.get(0).getName()) : "skills";
            return new SkillExportArtifact(target, fileStem + ".zip", "application/zip");
        } catch (SkillPackageException exception) {
            deleteQuietly(target);
            throw mapPackageException(exception, skillIds);
        } catch (Exception exception) {
            deleteQuietly(target);
            LOG.error("导出标准 Skill ZIP 失败，skillIds={}", skillIds, exception);
            throw new BusinessException(500, 500, "导出 Skill 包失败，请稍后重试", exception);
        }
    }

    private List<Skill> loadAuthorizedSkills(Collection<BigInteger> skillIds) {
        List<Skill> skills = new ArrayList<>();
        Set<BigInteger> uniqueIds = new HashSet<>();
        for (BigInteger skillId : skillIds) {
            if (skillId != null && uniqueIds.add(skillId)) {
                skills.add(skillService.getPackageDetail(skillId));
            }
        }
        if (skills.isEmpty()) {
            throw new BusinessException("请选择有效的 Skill");
        }
        return skills;
    }

    private BusinessException mapPackageException(SkillPackageException exception,
                                                   Collection<BigInteger> skillIds) {
        List<String> codes = new ArrayList<>();
        codes.add(exception.getCode() == null ? "SKILL_PACKAGE_FAILED" : exception.getCode());
        if (exception.getReport() != null) {
            exception.getReport().getIssues().stream().map(SkillValidationIssue::getCode).forEach(codes::add);
        }
        if (codes.stream().anyMatch(code -> Set.of(
                        "ZIP_IO_ERROR", "CONTENT_STORE_ERROR", "CONTENT_NOT_FOUND", "SKILL_CONTENT_STORE_ERROR",
                        "SKILL_CONTENT_ROLLBACK_ERROR", "CONTENT_REF_MISMATCH", "RESOURCE_SIZE_MISMATCH",
                        "RESOURCE_HASH_MISMATCH", "CRC_MISMATCH").contains(code))) {
            LOG.error("导出标准 Skill ZIP 内部失败，skillIds={}, code={}, path={}",
                    skillIds, exception.getCode(), exception.getPath(), exception);
            return new BusinessException(500, 500, "导出 Skill 包失败，请稍后重试", exception);
        }
        if (codes.stream().anyMatch(code -> code != null
                && (code.endsWith("_LIMIT") || code.contains("SIZE_LIMIT")))) {
            return new BusinessException(413, 4131, "Skill 包超过导出限制：" + firstMessage(exception), exception);
        }
        return new BusinessException(400, 4001,
                "Skill 包不符合导出规范：" + firstMessage(exception), exception);
    }

    private String firstMessage(SkillPackageException exception) {
        if (exception.getReport() != null) {
            return exception.getReport().getIssues().stream().map(SkillValidationIssue::getMessage)
                    .filter(message -> message != null && !message.isBlank()).findFirst()
                    .orElse("Skill 包校验失败");
        }
        return exception.getMessage() == null || exception.getMessage().isBlank()
                ? "Skill 包校验失败" : exception.getMessage();
    }

    private String safeFileStem(String value) {
        if (value == null || value.isBlank()) {
            return "skill";
        }
        String normalized = java.text.Normalizer.normalize(value, java.text.Normalizer.Form.NFKC)
                .toLowerCase(java.util.Locale.ROOT).replaceAll("[^a-z0-9-]+", "-")
                .replaceAll("^-+|-+$", "");
        return normalized.isBlank() ? "skill" : normalized.substring(0, Math.min(normalized.length(), 80));
    }

    private void deleteQuietly(Path path) {
        if (path == null) {
            return;
        }
        try {
            Files.deleteIfExists(path);
        } catch (IOException exception) {
            LOG.warn("清理 Skill 导出临时文件失败，path={}", path, exception);
        }
    }
}
