package com.agent.management.service.impl;

import com.agent.management.common.exception.BusinessException;
import com.agent.management.config.HermesProperties;
import com.agent.management.config.SkillProperties;
import com.agent.management.engine.NodeStreamSink;
import com.agent.management.engine.WorkflowRunDirManager;
import com.agent.management.engine.hermes.HermesBridgeClient;
import com.agent.management.engine.hermes.HermesSkillSyncService;
import com.agent.management.external.SseEventBus;
import com.agent.management.model.dto.SkillDTO;
import com.agent.management.model.dto.SkillGenerationRequest;
import com.agent.management.model.entity.AiModel;
import com.agent.management.parser.SkillNameNormalizer;
import com.agent.management.service.AiModelService;
import com.agent.management.service.SkillGenerationService;
import com.agent.management.service.SkillService;
import com.agent.management.skillgeneration.SkillGenerationRegistry;
import com.agent.management.skillgeneration.SkillGenerationState;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.stereotype.Service;

import jakarta.annotation.PreDestroy;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

/**
 * 技能生成服务实现。
 *
 * 核心流程：
 * 1. 校验源技能存在且 canGenerateSkill=true
 * 2. 创建临时工作目录（{run-dir-base}/_skill_gen/{runId}）
 * 3. 调用 HermesBridgeClient.run（hermesHome 传 null，回落到固定 HERMES_HOME），
 *    sink 回调通过 SseEventBus 推送给订阅者
 * 4. 扫描工作目录（排除隐藏目录），定位生成器写入的新技能目录
 * 5. 将新技能目录复制到全局 skills 根目录下，并触发写时同步到固定 HERMES_HOME
 * 6. 通过 workflow_complete / workflow_error 终止 SSE 流
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SkillGenerationServiceImpl implements SkillGenerationService {

    private static final String SKILL_MD_FILENAME = "SKILL.md";
    private static final String GEN_PARENT_DIR = "_skill_gen";
    private static final long MAX_EXECUTION_SECONDS = 600L;  // 10 分钟

    private final SkillService skillService;
    private final HermesSkillSyncService hermesSkillSyncService;
    private final SkillProperties skillProperties;
    private final WorkflowRunDirManager runDirManager;
    private final HermesProperties hermesProperties;
    private final AiModelService aiModelService;
    private final SseEventBus sseEventBus;
    private final SkillGenerationRegistry registry;
    private final ObjectProvider<HermesBridgeClient> bridgeClientProvider;

    /** 单独的线程池，避免与工作流执行器争抢资源 */
    private final ExecutorService executor = Executors.newFixedThreadPool(
            Math.max(2, Runtime.getRuntime().availableProcessors()),
            r -> {
                Thread t = new Thread(r, "skill-generation-worker");
                t.setDaemon(true);
                return t;
            });

    @Override
    public String startGeneration(SkillGenerationRequest request) {
        validateRequest(request);

        // 加载并校验源技能
        SkillDTO generatorSkill = skillService.getSkill(request.getGeneratorSkillFolder());
        if (!generatorSkill.isCanGenerateSkill()) {
            throw new BusinessException("所选技能不具备生成其他技能的能力");
        }

        // 生成 runId 并初始化状态
        String runId = generateRunId();
        SkillGenerationState state = new SkillGenerationState();
        state.setRunId(runId);
        state.setGeneratorSkillFolder(request.getGeneratorSkillFolder());
        state.setName(request.getName());
        state.setDescription(request.getDescription());
        state.setRequirement(request.getRequirement());
        state.setModelId(request.getModelId());
        state.setMaxIterations(request.getMaxIterations() > 0
                ? request.getMaxIterations()
                : hermesProperties.getBridge().getDefaultMaxIterations());
        state.setStatus(SkillGenerationState.Status.CREATED);
        state.setCreatedAt(Instant.now());
        registry.save(state);

        // 预注册 SSE 流，让客户端可立即订阅
        sseEventBus.register(runId);

        // 异步执行
        executor.submit(() -> runGeneration(runId, state, generatorSkill));
        return runId;
    }

    @Override
    public SkillGenerationState getState(String runId) {
        return registry.get(runId);
    }

    // ==================== 核心执行 ====================

    private void runGeneration(String runId, SkillGenerationState state, SkillDTO generatorSkill) {
        try {
            registry.markRunning(runId);
            publishStatus(runId, "RUNNING", "开始生成");

            // 1. 创建工作目录
            Path workingDir = createWorkingDir(runId);
            state.setWorkingDir(workingDir);
            log.info("[SkillGen] runId={}, workingDir={}", runId, workingDir);

            // 2. 系统提示词固定为中文指令（不再注入源技能正文）
            String systemPrompt = "使用简体中文回复，使用简体中文创建技能。";

            // 3. 构造用户消息
            String userMessage = buildUserMessage(state);

            // 4. 调用 HermesBridgeClient（通过 sink 实时推送事件）
            HermesBridgeClient client = bridgeClientProvider.getIfAvailable();
            if (client == null) {
                throw new IllegalStateException("Hermes Bridge 未启用（hermes.enabled=false），无法生成技能");
            }

            NodeStreamSink sink = (nodeId, kind, content, toolName) ->
                    sseEventBus.publish(runId, kind, Map.of(
                            "nodeId", nodeId != null ? nodeId : "skill_gen",
                            "kind", kind,
                            "content", content != null ? content : "",
                            "toolName", toolName != null ? toolName : ""
                    ));

            String sessionId = "skillgen_" + runId;
            Map<String, String> modelConfig = resolveModelConfig(state.getModelId());

            int maxIter = state.getMaxIterations();
            publishStatus(runId, "EXECUTING", "调用 Hermes 执行");

            var runResult = client.run(systemPrompt, userMessage, maxIter,
                    null, workingDir.toString(),
                    "skill_gen", sink, sessionId, modelConfig);

            if (runResult.getFinalText() == null || runResult.getFinalText().isBlank()) {
                throw new IllegalStateException("Hermes 返回空结果");
            }

            publishStatus(runId, "EXTRACTING", "扫描生成的技能文件");

            // 6. 扫描工作目录，定位新技能
            Path generatedSkillDir = findGeneratedSkill(workingDir);
            if (generatedSkillDir == null) {
                throw new IllegalStateException("未在工作目录中找到生成的 SKILL.md");
            }

            // 7. 用规范化的 name 作为目标 folder（不信任 AI 生成的目录名）
            // 用户输入的 name 经 normalizeStrict 后必为合法 kebab-case
            String folderName = SkillNameNormalizer.normalizeStrict(state.getName());
            Path globalSkillsRoot = Paths.get(skillProperties.getBasePath()).toAbsolutePath().normalize();
            Path targetDir = globalSkillsRoot.resolve(folderName);
            if (Files.exists(targetDir)) {
                // 同名已存在，附加 runId 后缀避免覆盖（必须保持 kebab-case）
                String altName = folderName + "-" + runId;
                targetDir = globalSkillsRoot.resolve(altName);
                folderName = altName;
            }
            FileUtils.copyDirectory(generatedSkillDir.toFile(), targetDir.toFile());
            log.info("[SkillGen] runId={}, 复制到全局 skills: {}", runId, targetDir);

            // 7.5 同步副本 SKILL.md 的 name 字段，确保 name == folderName
            // 防止 AI 生成的 name 与目录名不一致，导致后续 Hermes 查找失败
            alignTargetSkillMdName(targetDir, folderName);

            // 7.6 新技能入库后触发写时同步，让固定 HERMES_HOME 立即可见
            hermesSkillSyncService.syncAfterWrite();

            // 8. 标记成功，发布终止事件
            registry.markSuccess(runId, folderName);
            sseEventBus.publish(runId, "workflow_complete", Map.of(
                    "runId", runId,
                    "generatedFolderName", folderName,
                    "finalText", runResult.getFinalText()
            ));

        } catch (Exception e) {
            log.error("[SkillGen] runId={} 生成失败: {}", runId, e.getMessage(), e);
            registry.markFailed(runId, e.getMessage());
            sseEventBus.publish(runId, "workflow_error", Map.of(
                    "runId", runId,
                    "message", e.getMessage() != null ? e.getMessage() : "未知错误"
            ));
        }
    }

    // ==================== 私有辅助 ====================

    private void validateRequest(SkillGenerationRequest request) {
        if (request == null) {
            throw new BusinessException("请求不能为空");
        }
        if (isBlank(request.getGeneratorSkillFolder())) {
            throw new BusinessException("必须选择用于生成的源技能");
        }
        if (isBlank(request.getName())) {
            throw new BusinessException("新技能名称不能为空");
        }
        if (isBlank(request.getRequirement())) {
            throw new BusinessException("需求描述不能为空");
        }
    }

    private boolean isBlank(String s) {
        return s == null || s.trim().isEmpty();
    }

    private String generateRunId() {
        return UUID.randomUUID().toString().replace("-", "").substring(0, 12);
    }

    private Path createWorkingDir(String runId) throws IOException {
        Path base = runDirManager.getBasePath().resolve(GEN_PARENT_DIR).resolve(runId);
        Files.createDirectories(base);
        return base;
    }

    /**
     * 构造用户消息，引导生成器按指定格式产出技能。
     */
    private String buildUserMessage(SkillGenerationState state) {
        StringBuilder sb = new StringBuilder();
        sb.append("请使用技能 ").append(state.getGeneratorSkillFolder())
                .append(" 生成一个新的技能（Skill）。\n\n");
        sb.append("技能名称：").append(state.getName()).append("\n");
        if (!isBlank(state.getDescription())) {
            sb.append("技能描述：").append(state.getDescription()).append("\n");
        }
        sb.append("\n具体需求：\n").append(state.getRequirement()).append("\n\n");
        sb.append("要求：\n");
        sb.append("1. 在当前工作目录下创建一个以技能英文名命名的子目录\n");
        sb.append("2. 在该子目录中写入 SKILL.md 文件（包含 frontmatter: name / description / inputs / outputs）\n");
        sb.append("3. 如有必要，可在子目录中创建其他辅助文件\n");
        sb.append("4. 完成后简短说明生成的技能目录结构与设计要点\n");
        return sb.toString();
    }

    /**
     * 定位生成器在工作目录中创建的新技能目录。
     * 跳过隐藏目录（如 .hermes），扫描其余子目录查找 SKILL.md。
     */
    private Path findGeneratedSkill(Path workingDir) throws IOException {
        try (Stream<Path> stream = Files.list(workingDir)) {
            for (Path entry : (Iterable<Path>) stream::iterator) {
                if (!Files.isDirectory(entry)) continue;
                String name = entry.getFileName().toString();
                if (name.startsWith(".")) continue;  // 跳过 .hermes 等隐藏目录
                if (Files.exists(entry.resolve(SKILL_MD_FILENAME))) {
                    return entry;
                }
            }
        }
        // 兜底：递归扫描（最多 3 层）
        return findGeneratedSkillRecursive(workingDir, 0, 3);
    }

    private Path findGeneratedSkillRecursive(Path dir, int depth, int maxDepth) throws IOException {
        if (depth > maxDepth) return null;
        try (Stream<Path> stream = Files.list(dir)) {
            for (Path entry : (Iterable<Path>) stream::iterator) {
                if (!Files.isDirectory(entry)) continue;
                String name = entry.getFileName().toString();
                if (name.startsWith(".")) continue;
                if (Files.exists(entry.resolve(SKILL_MD_FILENAME))) {
                    return entry;
                }
                Path nested = findGeneratedSkillRecursive(entry, depth + 1, maxDepth);
                if (nested != null) return nested;
            }
        }
        return null;
    }

    /**
     * 同步目标 SKILL.md 的 name 字段到规范化目录名。
     * 仅在 frontmatter 内匹配 name: 行，命中则替换；其他内容保持不变。
     * 用于 AI 生成场景：AI 可能在 SKILL.md 中写入与目录不一致的 name，需统一。
     */
    private void alignTargetSkillMdName(Path skillDir, String folderName) {
        Path skillMd = skillDir.resolve(SKILL_MD_FILENAME);
        if (!Files.isRegularFile(skillMd)) return;
        try {
            List<String> lines = new ArrayList<>(Files.readAllLines(skillMd, StandardCharsets.UTF_8));
            boolean inFrontmatter = false;
            boolean updated = false;
            for (int i = 0; i < lines.size(); i++) {
                String trimmed = lines.get(i).trim();
                if (trimmed.equals("---")) {
                    if (!inFrontmatter) {
                        inFrontmatter = true;
                        continue;
                    } else break;
                }
                if (inFrontmatter && trimmed.toLowerCase().startsWith("name:")) {
                    String current = trimmed.substring(5).trim();
                    if (!current.equals(folderName)) {
                        lines.set(i, "name: " + folderName);
                        updated = true;
                    }
                    break;
                }
            }
            if (updated) {
                Files.write(skillMd, lines, StandardCharsets.UTF_8);
                log.debug("[SkillGen] 同步 SKILL.md name 字段: {}", folderName);
            }
        } catch (IOException e) {
            log.warn("[SkillGen] 同步 SKILL.md name 字段失败: {}", skillMd, e);
        }
    }

    private Map<String, String> resolveModelConfig(Long modelId) {
        if (modelId == null || modelId <= 0) return null;
        try {
            AiModel model = aiModelService.getModel(modelId);
            if (model == null || isBlank(model.getBaseUrl())
                    || isBlank(model.getApiKey()) || isBlank(model.getModelName())) {
                log.warn("[SkillGen] 模型 {} 配置不完整，回退到默认模型", modelId);
                return null;
            }
            Map<String, String> cfg = new HashMap<>();
            cfg.put("modelId", String.valueOf(modelId));
            cfg.put("baseUrl", model.getBaseUrl());
            cfg.put("apiKey", model.getApiKey());
            cfg.put("modelName", model.getModelName());
            return cfg;
        } catch (Exception e) {
            log.warn("[SkillGen] 获取模型 {} 失败，回退到默认: {}", modelId, e.getMessage());
            return null;
        }
    }

    private void publishStatus(String runId, String stage, String message) {
        sseEventBus.publish(runId, "status", Map.of(
                "runId", runId,
                "stage", stage,
                "message", message,
                "timestamp", Instant.now().toString()
        ));
    }

    /** 应用关闭时优雅关闭线程池 */
    @PreDestroy
    public void shutdown() {
        executor.shutdown();
        try {
            if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
                executor.shutdownNow();
            }
        } catch (InterruptedException e) {
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
