package com.zioinfo.kintex.work.meeting;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zioinfo.kintex.ai.AiTextRouter;
import com.zioinfo.kintex.ai.AiTextRouter.AiResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 전사 텍스트 → 회의록(요약·결정·액션아이템) 구조화 생성.
 *
 * <p>{@link AiTextRouter}(Claude 기본, 실패 시 Ollama 폴백) 경유. 결정론적 구조 출력을 위해 프롬프트에서
 * JSON 만 반환하도록 강제하고, 응답은 코드펜스 제거 후 관대 파싱한다(NlQueryService 패턴).
 * AI 미가용/파싱 실패/전사 비어있음 → degraded 폴백(빈 초안). 근거 없는 내용을 지어내지 않도록 지시한다(환각 방지).
 */
@Service
public class MeetingMinutesGenerator {

    private static final Logger log = LoggerFactory.getLogger(MeetingMinutesGenerator.class);
    private static final ObjectMapper MAPPER = new ObjectMapper();

    private final AiTextRouter router;

    public MeetingMinutesGenerator(AiTextRouter router) {
        this.router = router;
    }

    /**
     * 전사로부터 회의록을 생성한다. 어떤 사유로든 실패하면 degraded 폴백(빈 초안, minutesText=전사 원문).
     * @param title      회의 제목
     * @param transcript 전사 원문(비어 있으면 degraded)
     * @param attendees  참석자(선택) — 프롬프트 맥락으로만 사용
     */
    public GeneratedMinutes generate(String title, String transcript, List<String> attendees) {
        if (transcript == null || transcript.isBlank()) {
            return fallback(title, transcript, "none");
        }
        AiResult ai = router.generate(buildPrompt(title, transcript, attendees), 1024);
        if (!ai.usable()) {
            log.info("Minutes generation degraded (AI unusable) → empty draft");
            return fallback(title, transcript, ai.provider());
        }
        GeneratedMinutes parsed = parse(ai.text(), ai.provider());
        if (parsed == null) {
            log.warn("Minutes JSON parse failed → degraded draft");
            return fallback(title, transcript, ai.provider());
        }
        return parsed;
    }

    private String buildPrompt(String title, String transcript, List<String> attendees) {
        String att = (attendees == null || attendees.isEmpty()) ? "(미지정)" : String.join(", ", attendees);
        return "당신은 회의록 작성 보조자다. 아래 회의 전사를 분석해 JSON 객체 하나로만 응답하라.\n"
                + "회의 제목: " + title + "\n"
                + "참석자: " + att + "\n"
                + "규칙(엄수):\n"
                + "1) 키는 정확히 summary(요약 문자열), decisions(결정사항 문자열 배열), "
                + "actionItems(객체 배열: content 문자열, assignee 문자열 또는 빈문자열, dueDate 'yyyy-MM-dd' 또는 빈문자열) 3개.\n"
                + "2) 전사에 실제로 언급된 내용만 사용하라. 없는 담당자·기한·결정을 지어내지 마라(불명확하면 빈문자열/빈배열).\n"
                + "3) 설명·머리말·마크다운·코드펜스 없이 JSON 객체 텍스트만 출력하라.\n\n"
                + "전사:\n" + transcript;
    }

    /** AI 응답(JSON) → GeneratedMinutes. 코드펜스/전후 텍스트를 관대 처리. 실패 시 null. */
    private GeneratedMinutes parse(String raw, String provider) {
        String json = stripToJson(raw);
        if (json == null) {
            return null;
        }
        try {
            JsonNode root = MAPPER.readTree(json);
            String summary = text(root.get("summary"));
            List<String> decisions = asStringList(root.get("decisions"));
            List<MinutesActionDraft> actions = new ArrayList<>();
            JsonNode ai = root.get("actionItems");
            if (ai != null && ai.isArray()) {
                for (JsonNode n : ai) {
                    String content = text(n.get("content"));
                    if (content == null || content.isBlank()) {
                        continue;
                    }
                    actions.add(new MinutesActionDraft(content.trim(),
                            emptyToNull(text(n.get("assignee"))), emptyToNull(text(n.get("dueDate")))));
                }
            }
            String minutesText = compose(summary, decisions, actions);
            return new GeneratedMinutes(summary == null ? "" : summary.trim(), decisions, actions,
                    minutesText, false, provider);
        } catch (Exception e) {
            return null;
        }
    }

    private GeneratedMinutes fallback(String title, String transcript, String provider) {
        // 폴백: 요약·결정·액션은 비우고, 전사 원문(있으면)을 본문 초안으로 제공해 사용자가 편집하도록 한다.
        String body = (transcript != null && !transcript.isBlank()) ? transcript.trim() : "";
        return new GeneratedMinutes("", new ArrayList<>(), new ArrayList<>(), body, true, provider);
    }

    /** 요약+결정+액션을 회의록 본문 문자열로 합친다(textarea 프리필). */
    private static String compose(String summary, List<String> decisions, List<MinutesActionDraft> actions) {
        StringBuilder sb = new StringBuilder();
        if (summary != null && !summary.isBlank()) {
            sb.append("[요약]\n").append(summary.trim()).append("\n\n");
        }
        if (decisions != null && !decisions.isEmpty()) {
            sb.append("[결정사항]\n");
            for (String d : decisions) {
                sb.append("- ").append(d).append('\n');
            }
            sb.append('\n');
        }
        if (actions != null && !actions.isEmpty()) {
            sb.append("[액션아이템]\n");
            for (MinutesActionDraft a : actions) {
                sb.append("- ").append(a.content());
                boolean hasAssignee = a.assignee() != null && !a.assignee().isBlank();
                boolean hasDue = a.dueDate() != null && !a.dueDate().isBlank();
                if (hasAssignee || hasDue) {
                    sb.append(" (");
                    if (hasAssignee) {
                        sb.append("담당: ").append(a.assignee());
                    }
                    if (hasDue) {
                        sb.append(hasAssignee ? ", " : "").append("기한: ").append(a.dueDate());
                    }
                    sb.append(')');
                }
                sb.append('\n');
            }
        }
        return sb.toString().trim();
    }

    /** 코드펜스/전후 텍스트를 제거하고 첫 '{' ~ 마지막 '}' 구간을 반환. */
    private static String stripToJson(String raw) {
        if (raw == null || raw.isBlank()) {
            return null;
        }
        String s = raw.trim();
        int start = s.indexOf('{');
        int end = s.lastIndexOf('}');
        if (start < 0 || end <= start) {
            return null;
        }
        return s.substring(start, end + 1);
    }

    private static List<String> asStringList(JsonNode node) {
        List<String> out = new ArrayList<>();
        if (node != null && node.isArray()) {
            for (JsonNode n : node) {
                String v = text(n);
                if (v != null && !v.isBlank()) {
                    out.add(v.trim());
                }
            }
        }
        return out;
    }

    private static String text(JsonNode n) {
        return (n == null || n.isNull()) ? null : n.asText();
    }

    private static String emptyToNull(String s) {
        return (s == null || s.isBlank()) ? null : s.trim();
    }
}
