package com.erp.ai.service;

import com.erp.ai.client.LlmClient;
import com.erp.ai.client.LlmClientFactory;
import com.erp.ai.dto.GroupExecutionResult;
import com.erp.ai.exception.AiAgentException;
import com.erp.ai.mapper.AiAgentMapper;
import com.erp.ai.model.AiAgent;
import com.erp.ai.model.AiAgentConversation;
import com.erp.ai.model.AiAgentGroup;
import com.erp.ai.model.AiAgentGroupMember;
import com.erp.ai.model.AiAgentUsageLog;
import com.erp.ai.model.AiAnalysisLog;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;

/**
 * 멀티 에이전트 실행 엔진 (★ 핵심).
 * vexplor multiAgentExecutionEngine.ts 1:1 포팅.
 *
 * - sequential: 1→2→3 순차, 이전 결과를 다음에 전달
 * - parallel: 전체 동시 실행
 * - mixed: execution_order 같으면 병렬, 다르면 순차
 *
 * architecture §7.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MultiAgentExecutionEngine {

    private final AiAgentGroupService groupService;
    private final AiAgentMapper agentMapper;
    private final AiAgentConversationService conversationService;
    private final AiAgentUsageService usageService;
    private final AiAnalysisLogService analysisLogService;
    private final LlmClientFactory llmClientFactory;

    @Qualifier("aiObjectMapper")
    private final ObjectMapper objectMapper;

    @Qualifier("aiAgentExecutor")
    private final ExecutorService aiAgentExecutor;

    /**
     * 그룹 실행 entry point.
     */
    public GroupExecutionResult execute(long groupId, String userMessage, String userId, Long apiKeyId) {
        AiAgentGroup group = groupService.getEntityById(groupId);
        if (group == null) throw new AiAgentException("멀티 에이전트 그룹을 찾을 수 없습니다.");

        List<AiAgentGroupMember> members = groupService.listMembers(groupId);
        if (members.isEmpty()) throw new AiAgentException("그룹에 에이전트가 없습니다.");

        String executionMode = group.getExecution_mode() != null ? group.getExecution_mode() : "mixed";
        long startTime = System.currentTimeMillis();

        log.info("멀티 에이전트 실행 시작: {} ({}) - \"{}\"", group.getName(), executionMode,
                userMessage.length() > 50 ? userMessage.substring(0, 50) + "..." : userMessage);

        // 과거 분석 이력 컨텍스트 (try/catch — 이력 없으면 무시)
        String historyContext = "";
        try {
            List<AiAnalysisLog> recentLogs = analysisLogService.getRecentLogs(groupId, 30, 5);
            if (!recentLogs.isEmpty()) {
                StringBuilder sb = new StringBuilder("\n[과거 분석 이력 (최근 5건)]:\n");
                for (AiAnalysisLog log : recentLogs) {
                    String date = log.getCreated_at() != null
                            ? log.getCreated_at().format(DateTimeFormatter.ISO_LOCAL_DATE)
                            : "";
                    String preview = log.getAnalysis_result() != null && log.getAnalysis_result().length() > 200
                            ? log.getAnalysis_result().substring(0, 200) + "..."
                            : log.getAnalysis_result();
                    sb.append("- ").append(date).append(": ").append(preview).append("\n");
                }
                historyContext = sb.toString();
            }
            BigDecimal accuracy = analysisLogService.getAverageAccuracy(groupId);
            if (accuracy != null && accuracy.compareTo(BigDecimal.ZERO) > 0) {
                historyContext += "\n평균 예측 정확도: " + accuracy.setScale(1, java.math.RoundingMode.HALF_UP) + "%";
            }
        } catch (Exception ignored) {
            // 이력 조회 실패는 무시
        }

        String enrichedMessage = historyContext.isEmpty() ? userMessage : userMessage + "\n\n" + historyContext;

        List<ExecutionStepResult> stepResults;
        switch (executionMode) {
            case "parallel" -> stepResults = executeParallel(members, enrichedMessage, "");
            case "sequential" -> stepResults = executeSequential(members, enrichedMessage);
            default -> stepResults = executeMixed(members, enrichedMessage);
        }

        String finalSummary = buildFinalSummary(stepResults, userMessage);
        long totalTokens = stepResults.stream().mapToLong(ExecutionStepResult::tokensUsed).sum();
        long totalDuration = System.currentTimeMillis() - startTime;

        // 사이드 이펙트 — 별도 트랜잭션 (REQUIRES_NEW)
        try {
            persistResults(group, executionMode, userMessage, stepResults,
                    finalSummary, totalTokens, totalDuration, userId, apiKeyId);
        } catch (Exception e) {
            log.warn("멀티 에이전트 결과 적재 실패: {}", e.getMessage());
        }

        log.info("멀티 에이전트 실행 완료: {} - {} tokens, {}ms", group.getName(), totalTokens, totalDuration);

        // GroupExecutionResult 매핑
        List<Map<String, Object>> stepsOut = new ArrayList<>();
        for (ExecutionStepResult r : stepResults) {
            Map<String, Object> m = new HashMap<>();
            m.put("order", r.executionOrder());
            m.put("role", r.roleName());
            m.put("agent", r.agentName());
            m.put("model", r.modelName());
            m.put("response", r.response());
            m.put("tokens", r.tokensUsed());
            m.put("duration_ms", r.durationMs());
            m.put("memberId", r.memberId());
            m.put("connectorResults", r.connectorResults());
            stepsOut.add(m);
        }
        return GroupExecutionResult.builder()
                .groupId(group.getId())
                .groupName(group.getName())
                .executionMode(executionMode)
                .steps(stepsOut)
                .finalSummary(finalSummary)
                .totalTokens(totalTokens)
                .totalDurationMs(totalDuration)
                .build();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, timeout = 30)
    public void persistResults(AiAgentGroup group, String executionMode, String userMessage,
                               List<ExecutionStepResult> stepResults, String finalSummary,
                               long totalTokens, long totalDuration, String userId, Long apiKeyId) {
        // 대화 저장
        try {
            AiAgentConversation conv = conversationService.createConversation(null, userId, apiKeyId);
            String title = "[" + group.getName() + "] "
                    + (userMessage.length() > 100 ? userMessage.substring(0, 100) : userMessage);
            Map<String, Object> meta = Map.of(
                    "group_id", group.getId(),
                    "group_name", group.getName(),
                    "execution_mode", executionMode);
            conversationService.updateMeta(conv.getId(), title, toJson(meta));
            conversationService.addMessage(conv.getId(), "user", userMessage, 0, null);
            for (ExecutionStepResult step : stepResults) {
                Map<String, Object> stepMeta = Map.of(
                        "role_name", step.roleName(),
                        "agent_name", step.agentName(),
                        "model_name", step.modelName(),
                        "execution_order", step.executionOrder(),
                        "duration_ms", step.durationMs());
                conversationService.addMessageWithMetadata(conv.getId(), "assistant",
                        "[" + step.roleName() + " - " + step.agentName() + "]\n" + step.response(),
                        (int) step.tokensUsed(), toJson(stepMeta));
            }
        } catch (Exception e) {
            log.warn("멀티 에이전트 대화 저장 실패: {}", e.getMessage());
        }

        // 분석 이력 저장
        try {
            AiAnalysisLog analysis = new AiAnalysisLog();
            analysis.setGroup_id(group.getId());
            analysis.setExecution_type(apiKeyId != null ? "api" : "manual");
            analysis.setInput_message(userMessage);
            analysis.setAnalysis_result(finalSummary);
            analysis.setTokens_used((int) totalTokens);
            analysis.setDuration_ms((int) totalDuration);
            analysis.setCompany_code(group.getCompany_code());
            analysisLogService.save(analysis);
        } catch (Exception e) {
            log.warn("분석 이력 저장 실패: {}", e.getMessage());
        }

        // 사용량 로깅
        try {
            AiAgentUsageLog usage = new AiAgentUsageLog();
            usage.setUser_id(userId);
            usage.setApi_key_id(apiKeyId);
            usage.setTotal_tokens((int) totalTokens);
            usage.setResponse_time_ms((int) totalDuration);
            usage.setSuccess(true);
            usage.setRequest_path("/groups/" + group.getId());
            usage.setCompany_code(group.getCompany_code());
            usageService.log(usage);
        } catch (Exception e) {
            log.warn("사용량 로그 실패: {}", e.getMessage());
        }
    }

    private List<ExecutionStepResult> executeSequential(List<AiAgentGroupMember> members, String userMessage) {
        List<AiAgentGroupMember> sorted = new ArrayList<>(members);
        sorted.sort((a, b) -> Integer.compare(a.getExecution_order(), b.getExecution_order()));
        List<ExecutionStepResult> results = new ArrayList<>();
        StringBuilder previousContext = new StringBuilder();
        for (AiAgentGroupMember m : sorted) {
            ExecutionStepResult r = executeSingleAgent(m, userMessage, previousContext.toString());
            results.add(r);
            previousContext.append("\n[").append(m.getRole_name()).append(" 결과]:\n")
                    .append(r.response()).append("\n");
        }
        return results;
    }

    private List<ExecutionStepResult> executeParallel(List<AiAgentGroupMember> members,
                                                      String userMessage, String previousContext) {
        List<CompletableFuture<ExecutionStepResult>> futures = new ArrayList<>();
        for (AiAgentGroupMember m : members) {
            futures.add(CompletableFuture.supplyAsync(
                    () -> executeSingleAgent(m, userMessage, previousContext), aiAgentExecutor));
        }
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
        List<ExecutionStepResult> out = new ArrayList<>();
        for (CompletableFuture<ExecutionStepResult> f : futures) {
            out.add(f.join());
        }
        return out;
    }

    private List<ExecutionStepResult> executeMixed(List<AiAgentGroupMember> members, String userMessage) {
        TreeMap<Integer, List<AiAgentGroupMember>> orderGroups = new TreeMap<>();
        for (AiAgentGroupMember m : members) {
            orderGroups.computeIfAbsent(m.getExecution_order(), k -> new ArrayList<>()).add(m);
        }
        List<ExecutionStepResult> all = new ArrayList<>();
        StringBuilder previousContext = new StringBuilder();
        for (Map.Entry<Integer, List<AiAgentGroupMember>> entry : orderGroups.entrySet()) {
            List<AiAgentGroupMember> stage = entry.getValue();
            List<ExecutionStepResult> stageResults;
            if (stage.size() == 1) {
                stageResults = List.of(executeSingleAgent(stage.get(0), userMessage, previousContext.toString()));
            } else {
                stageResults = executeParallel(stage, userMessage, previousContext.toString());
            }
            all.addAll(stageResults);
            for (ExecutionStepResult r : stageResults) {
                previousContext.append("\n[").append(r.roleName()).append(" 결과]:\n")
                        .append(r.response()).append("\n");
            }
        }
        return all;
    }

    @SuppressWarnings("unchecked")
    private ExecutionStepResult executeSingleAgent(AiAgentGroupMember member,
                                                   String userMessage, String previousContext) {
        long startTime = System.currentTimeMillis();
        AiAgent agent = agentMapper.getById(member.getAgent_id());
        if (agent == null) {
            return new ExecutionStepResult(member.getId(), member.getRole_name(),
                    "알 수 없음", "unknown", member.getExecution_order(),
                    "에이전트를 찾을 수 없습니다.", 0L, System.currentTimeMillis() - startTime, List.of());
        }

        // 커넥터 데이터 수집 (invyone에는 외부 커넥터 테이블 부재 — info 만 반환)
        List<Map<String, Object>> connectorResults = new ArrayList<>();
        StringBuilder connectorContext = new StringBuilder();
        List<Map<String, Object>> connectors = parseConnectors(member.getConnectors());
        for (Map<String, Object> c : connectors) {
            Map<String, Object> data = executeConnectorPlaceholder(c);
            Map<String, Object> entry = new HashMap<>();
            entry.put("connector", c.get("name"));
            entry.put("type", c.get("type"));
            entry.put("data", data);
            connectorResults.add(entry);
            connectorContext.append("\n[데이터 소스: ").append(c.get("name"))
                    .append(" (").append(c.get("type")).append(")]:\n")
                    .append(safeJson(data)).append("\n");
        }

        // 지식 파일 컨텍스트 (config.knowledge_files)
        String knowledgeContext = buildKnowledgeContext(agent);

        String systemPrompt = (agent.getSystem_prompt() != null
                ? agent.getSystem_prompt() : "당신은 도움이 되는 AI 어시스턴트입니다.")
                + "\n당신의 역할: " + member.getRole_name()
                + knowledgeContext
                + (connectorContext.length() > 0 ? "\n사용 가능한 데이터:\n" + connectorContext : "")
                + (previousContext != null && !previousContext.isEmpty()
                        ? "\n이전 에이전트들의 분석 결과:\n" + previousContext : "");

        // LLM 호출
        try {
            Map<String, Object> agentConfig = parseConfig(agent.getConfig());
            Map<String, Object> llmRequest = new HashMap<>();
            llmRequest.put("model", agent.getModel());
            llmRequest.put("messages", List.of(
                    Map.of("role", "system", "content", systemPrompt),
                    Map.of("role", "user", "content", userMessage)
            ));
            llmRequest.put("max_tokens", agentConfig.getOrDefault("max_tokens", 2000));
            llmRequest.put("temperature", agentConfig.getOrDefault("temperature", 0.7));

            LlmClient client = llmClientFactory.pick(agent.getModel());
            Map<String, Object> result = client.chat(llmRequest);

            String text = "응답 없음";
            long tokens = 0L;
            List<Map<String, Object>> choices = (List<Map<String, Object>>) result.getOrDefault("choices", List.of());
            if (!choices.isEmpty()) {
                Map<String, Object> message = (Map<String, Object>) choices.get(0).get("message");
                if (message != null) {
                    Object content = message.get("content");
                    if (content != null) text = String.valueOf(content);
                }
            }
            Map<String, Object> usage = (Map<String, Object>) result.get("usage");
            if (usage != null && usage.get("total_tokens") instanceof Number n) {
                tokens = n.longValue();
            }

            return new ExecutionStepResult(member.getId(), member.getRole_name(),
                    agent.getName(), agent.getModel(), member.getExecution_order(),
                    text, tokens, System.currentTimeMillis() - startTime, connectorResults);
        } catch (Exception e) {
            log.warn("에이전트 실행 실패 ({}): {}", member.getRole_name(), e.getMessage());
            return new ExecutionStepResult(member.getId(), member.getRole_name(),
                    agent.getName(), agent.getModel(), member.getExecution_order(),
                    "[실행 실패] " + e.getMessage(), 0L,
                    System.currentTimeMillis() - startTime, connectorResults);
        }
    }

    @SuppressWarnings("unchecked")
    private String buildKnowledgeContext(AiAgent agent) {
        try {
            Map<String, Object> config = parseConfig(agent.getConfig());
            Object kfRaw = config.get("knowledge_files");
            if (!(kfRaw instanceof List<?> rawList) || rawList.isEmpty()) return "";

            StringBuilder out = new StringBuilder("\n[참고 지식 문서]:\n");
            for (Object item : rawList) {
                if (!(item instanceof Map<?, ?> rawMap)) continue;
                Map<String, Object> mapItem = (Map<String, Object>) rawMap;
                Object nameObj = mapItem.get("name");
                String name = nameObj != null ? String.valueOf(nameObj) : "";
                Object content = mapItem.get("content");
                if (content != null) {
                    String c = String.valueOf(content);
                    out.append("--- ").append(name).append(" ---\n")
                            .append(c.length() > 10000 ? c.substring(0, 10000) : c).append("\n\n");
                }
                // library_id resolution은 KnowledgeService 의존성 회피 위해 생략 (향후 통합)
            }
            return out.toString();
        } catch (Exception e) {
            return "";
        }
    }

    private Map<String, Object> executeConnectorPlaceholder(Map<String, Object> connector) {
        String type = String.valueOf(connector.getOrDefault("type", ""));
        String name = String.valueOf(connector.getOrDefault("name", ""));
        return Map.of("type", type, "name", name, "info", "커넥터 연결 준비됨");
    }

    private List<Map<String, Object>> parseConnectors(String json) {
        if (json == null || json.isBlank()) return List.of();
        try {
            return objectMapper.readValue(json, new TypeReference<>() {});
        } catch (JsonProcessingException e) {
            return List.of();
        }
    }

    private Map<String, Object> parseConfig(String json) {
        if (json == null || json.isBlank()) return new HashMap<>();
        try {
            return objectMapper.readValue(json, new TypeReference<>() {});
        } catch (JsonProcessingException e) {
            return new HashMap<>();
        }
    }

    private String safeJson(Object value) {
        try {
            String s = objectMapper.writeValueAsString(value);
            return s.length() > 2000 ? s.substring(0, 2000) : s;
        } catch (JsonProcessingException e) {
            return String.valueOf(value);
        }
    }

    private String toJson(Object value) {
        try {
            return objectMapper.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            return "{}";
        }
    }

    private String buildFinalSummary(List<ExecutionStepResult> results, String originalQuestion) {
        StringBuilder sb = new StringBuilder("질문: ").append(originalQuestion).append("\n\n");
        for (int i = 0; i < results.size(); i++) {
            ExecutionStepResult r = results.get(i);
            sb.append("[").append(r.roleName()).append(" (").append(r.agentName()).append(")]:\n")
                    .append(r.response());
            if (i < results.size() - 1) sb.append("\n\n---\n\n");
        }
        return sb.toString();
    }

    /**
     * 단일 에이전트 실행 결과.
     */
    public record ExecutionStepResult(
            long memberId,
            String roleName,
            String agentName,
            String modelName,
            int executionOrder,
            String response,
            long tokensUsed,
            long durationMs,
            List<Map<String, Object>> connectorResults
    ) {}
}
