package com.zioinfo.cms.rag;

import com.zioinfo.cms.ai.AiService;
import com.zioinfo.cms.rag.RagToggleService.RagToggles;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * CMS 대표 AI 기능 RAG 배선 레이어(순증·비파괴, 별개 레이어).
 *
 * <p>기존 {@link AiService}(Ollama + Java 폴백)는 <b>불변</b>. 본 서비스는 중앙 guardia-rag 를
 * 경유하는 별개 레이어로, 두 대표 기능을 전환한다:
 * <ol>
 *   <li><b>contentDraft</b> → {@code /rag/answer}(hybrid 근거+인용) 또는 tool_use 토글 시
 *       {@code /rag/agent}(다단계 콘텐츠 작업). 근거 부족 시 보류 표기.
 *       토글 off/미가용 시 기존 {@link AiService#draft} 로 무손실 폴백(degraded:true).</li>
 *   <li><b>moderate</b> → {@code /rag/structured}(format:json 결정론) 판정. 애매(저신뢰) 시
 *       자동 통과 금지 → REVIEW(hold). 토글 off/미가용 시 기존 {@link AiService#moderate}
 *       (룰 기반) 폴백(degraded:true).</li>
 * </ol>
 *
 * <p>모든 응답에 적용 metadata(retrievalMode·rerank·graphrag·toolUse·structured·degraded)를
 * 표기해 토글 effect 가 관측 가능하게 한다. 보안 불변: 외부 API 0, PII/자격증명/스택트레이스 미노출.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class RagCmsService {

    private final RagClient rag;
    private final RagToggleService toggles;
    private final AiService aiService;   // 기존 Ollama+Java 폴백 재사용(불변)

    /** UGC 모더레이션 결정론 JSON 스키마(중앙 /structured 강제). */
    private static final Map<String, Object> MODERATION_SCHEMA = Map.of(
        "type", "object",
        "properties", Map.of(
            "verdict", Map.of("type", "string", "enum", List.of("APPROVE", "REJECT", "REVIEW")),
            "categories", Map.of("type", "array", "items", Map.of("type", "string")),
            "score", Map.of("type", "number"),
            "reasons", Map.of("type", "array", "items", Map.of("type", "string")),
            "pii_flag", Map.of("type", "boolean")
        ),
        "required", List.of("verdict", "score")
    );

    /** CMS 콘텐츠 도구 — /agent 토글 on 시 중앙 도구 레지스트리에서 매칭. */
    private static final List<String> CMS_TOOLS = List.of(
        "cms_content_search",   // 기존 페이지/포스트/블록 조회
        "cms_category_tree",    // 카테고리 트리 lookup
        "cms_media_lookup",     // 미디어 자산 조회(alt/태그)
        "cms_glossary"          // 용어집(번역 일관성)
    );

    /**
     * AI 콘텐츠 초안 (중앙 /answer hybrid 또는 /agent 경유, 근거+인용, 근거 부족 시 보류).
     * 미가용/off → 기존 AiService.draft 폴백.
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> contentDraft(Map<String, Object> req, String actor) {
        String kind = str(req.get("kind"));
        String topic = str(req.get("topic"));
        String tone = str(req.get("tone"));
        RagToggles t = toggles.current();
        Map<String, Object> out = new LinkedHashMap<>();

        if (!t.ragEnabled || !rag.available()) {
            String legacy = aiService.draft(kind, topic, tone.isBlank() ? null : tone);
            out.put("draft", legacy);
            out.put("degraded", true);
            out.put("degraded_reason", t.ragEnabled ? "rag_unavailable" : "rag_disabled");
            out.put("source", "cms_local_fallback");
            out.putAll(meta(t, "answer", false));
            return out;
        }

        String query = String.format(
            "GUARDiA CMS 콘텐츠 초안을 작성하라. 유형: %s, 주제: %s, 톤: %s. "
                + "기존 사이트 콘텐츠·용어집·브랜드 톤을 근거로 일관되게 한국어 마케팅 초안을 작성하라. "
                + "근거가 부족하면 추측하지 말고 축소하라.",
            kind.isBlank() ? "콘텐츠" : kind, topic.isBlank() ? "신규" : topic,
            tone.isBlank() ? "친근함" : tone);

        Map<String, Object> res;
        boolean usedAgent = false;
        if (t.toolUse) {
            // 에이전틱 다단계(옵트인): CMS 도구 등록 → /agent
            res = rag.agent(query, CMS_TOOLS, t.stepLimit, t.generationModel, toggles.effectiveMode(t));
            usedAgent = true;
        } else {
            res = rag.answer(query, toggles.effectiveMode(t), t.rerank, t.topK, t.generationModel, true);
        }

        boolean degraded = bool(res.get("degraded"));
        Object answer = res.get("answer");
        if (degraded || answer == null) {
            String legacy = aiService.draft(kind, topic, tone.isBlank() ? null : tone);
            out.put("draft", legacy);
            out.put("degraded", true);
            out.put("degraded_reason", str(res.getOrDefault("degraded_reason",
                usedAgent ? "agent_empty" : "answer_empty")));
            out.put("source", "cms_local_fallback");
            out.putAll(meta(t, usedAgent ? "agent" : "answer", false));
            return out;
        }

        // 정상 경유: 초안은 항상 draft 상태(published 직행 금지 — 사람 승인)
        out.put("draft", answer);
        out.put("status", "draft");
        out.put("grounded", res.get("grounded"));
        out.put("faithfulness", res.get("faithfulness"));
        out.put("citations", res.get("citations"));
        out.put("sources", res.get("sources"));
        out.put("guardrail", res.get("guardrail"));
        out.put("abstained", res.getOrDefault("abstained", Boolean.FALSE.equals(res.get("grounded"))));
        if (usedAgent) {
            out.put("steps", res.get("steps"));
            out.put("toolsUsed", res.get("tools_used"));
        }
        out.put("answerId", res.get("answer_id"));   // /feedback 연계
        out.put("engine", usedAgent ? "RAG_AGENT" : "RAG_ANSWER");
        out.put("degraded", false);
        out.putAll(meta(t, usedAgent ? "agent" : "answer", false));
        return out;
    }

    /**
     * UGC 모더레이션 (중앙 /structured 결정론 JSON, 애매 시 hold=REVIEW).
     * 미가용/off → 기존 AiService.moderate(룰 기반) 폴백.
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> moderate(Map<String, Object> req, String actor) {
        String text = str(req.get("text"));
        RagToggles t = toggles.current();
        Map<String, Object> out = new LinkedHashMap<>();

        if (text.isBlank()) {
            out.put("verdict", "APPROVE");
            out.put("score", 0.0);
            out.put("reason", "empty");
            out.put("degraded", false);
            out.putAll(meta(t, "structured", true));
            return out;
        }

        if (!t.ragEnabled || !rag.available()) {
            return moderateFallback(text, t, t.ragEnabled ? "rag_unavailable" : "rag_disabled");
        }

        String prompt =
            "다음 UGC(리뷰/댓글/문의)가 유해/스팸/욕설/금칙어/정책위반인지 판정하라. "
                + "개인정보(전화/이메일/주소/카드)가 섞였으면 pii_flag=true. "
                + "확신이 낮으면 자동 통과시키지 말고 verdict=REVIEW(보류). "
                + "JSON 스키마(verdict, categories, score, reasons, pii_flag)로만 응답:\n" + truncate(text, 800);

        Map<String, Object> sres = rag.structured(prompt, MODERATION_SCHEMA, t.generationModel, t.temperature);
        boolean degraded = bool(sres.get("degraded"));
        Map<String, Object> data = sres.get("data") instanceof Map ? (Map<String, Object>) sres.get("data") : null;

        if (degraded || data == null || data.get("verdict") == null) {
            // 스키마 위반/파싱 실패/미가용 → 룰 기반 폴백 + 안전측(REVIEW 강제 가능)
            return moderateFallback(text, t, str(sres.getOrDefault("degraded_reason", "structured_empty")));
        }

        String verdict = normVerdict(str(data.get("verdict")));
        double score = num(data.get("score"), 0.5);
        // 안전측 게이트: 저신뢰(애매)면 자동 APPROVE/REJECT 금지 → REVIEW
        if ("APPROVE".equals(verdict) && score >= t.faithfulnessThreshold) {
            verdict = "REVIEW";
        }

        out.put("verdict", verdict);
        out.put("categories", data.getOrDefault("categories", List.of()));
        out.put("score", score);
        out.put("reasons", data.getOrDefault("reasons", List.of()));
        out.put("piiFlag", bool(data.get("pii_flag")));
        out.put("engine", "RAG_STRUCTURED");
        out.put("degraded", false);
        out.putAll(meta(t, "structured", true));
        return out;
    }

    /** 모더레이션 폴백 — 기존 룰 기반 AiService.moderate + 안전측 표기. */
    private Map<String, Object> moderateFallback(String text, RagToggles t, String reason) {
        Map<String, Object> legacy = aiService.moderate(text);
        Map<String, Object> out = new LinkedHashMap<>();
        // AiService.moderate → {decision, risk, reason, source}
        String decision = normVerdict(str(legacy.getOrDefault("decision", "REVIEW")));
        out.put("verdict", decision);
        out.put("score", num(legacy.get("risk"), 0.5));
        out.put("reasons", List.of(str(legacy.getOrDefault("reason", "rule_based"))));
        out.put("piiFlag", false);
        out.put("degraded", true);
        out.put("degraded_reason", reason);
        out.put("source", "cms_local_fallback");
        out.putAll(meta(t, "structured", false));
        return out;
    }

    /** 👍/👎 피드백 → 중앙 /rag/feedback (solution=cms 격리). */
    public Map<String, Object> feedback(Map<String, Object> req, String actor) {
        String userRef = "u_" + Integer.toHexString((actor == null ? "anon" : actor).hashCode());
        return rag.feedback(
            str0(req.get("answerId")),
            str0(req.get("query")),
            str0(req.get("answer")),
            str(req.getOrDefault("verdict", "down")),
            str0(req.get("correction")),
            userRef);
    }

    /** 현재 토글 스냅샷(화면용). */
    public Map<String, Object> currentToggles() {
        Map<String, Object> m = toggles.current().toMap();
        m.put("ragAvailable", rag.available());
        return m;
    }

    // ── helpers ─────────────────────────────────────────────────────────────
    private Map<String, Object> meta(RagToggles t, String technique, boolean structured) {
        Map<String, Object> m = new LinkedHashMap<>();
        Map<String, Object> applied = new LinkedHashMap<>();
        applied.put("retrievalMode", toggles.effectiveMode(t));
        applied.put("rerank", t.rerank);
        applied.put("graphrag", t.graphrag);
        applied.put("toolUse", t.toolUse);
        applied.put("structured", structured);
        applied.put("technique", technique);
        m.put("applied", applied);
        return m;
    }

    private String normVerdict(String v) {
        String s = v == null ? "" : v.trim().toUpperCase();
        if (s.contains("REJECT")) return "REJECT";
        if (s.contains("REVIEW")) return "REVIEW";
        if (s.contains("APPROVE")) return "APPROVE";
        return "REVIEW";  // 알 수 없으면 안전측 보류
    }

    private boolean bool(Object o) { return Boolean.TRUE.equals(o) || "true".equalsIgnoreCase(String.valueOf(o)); }
    private double num(Object o, double def) {
        if (o instanceof Number n) return n.doubleValue();
        try { return Double.parseDouble(String.valueOf(o)); } catch (Exception e) { return def; }
    }
    private String str(Object o) { return o == null ? "" : String.valueOf(o); }
    private String str0(Object o) { return o == null ? null : String.valueOf(o); }
    private String truncate(String s, int max) {
        if (s == null) return "";
        return s.length() <= max ? s : s.substring(0, max);
    }
}
