package com.zioinfo.mes.rag;

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

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

/**
 * MES 대표 AI 기능 RAG 배선 레이어(순증·비파괴).
 *
 * <p>기존 {@link AiService}(8개 제조 AI, Ollama + 결정론 Java 폴백)는 <b>불변</b>. 본 서비스는
 * 중앙 guardia-rag 를 경유하는 <b>별개 레이어</b>로, 두 대표 기능을 고급 기법으로 전환한다:
 * <ol>
 *   <li><b>품질 분석(defectAnalysis)</b> — 불량 원인분석 + SPC 이상감지.
 *       SPC 수치(평균·관리한계 이탈·런 규칙)는 {@link AiService#spcAnomaly} 결정론 로직으로 산출하고,
 *       <b>원인 서술·분류·우선순위</b>만 중앙 {@code /rag/agent}(tool-use) + {@code /rag/structured}(JSON 강제)로 받는다.
 *       토글 off/미가용 시 기존 {@link AiService#defectRootCause}/{@code spcAnomaly} 로 무손실 폴백(degraded:true).</li>
 *   <li><b>예측 분석(predictAnalysis)</b> — 설비 예지보전 + 수요/생산 예측.
 *       예측 수치(이동평균·추세·위험점수)는 {@link AiService#forecast}/{@code predictiveMaintenance} 결정론 베이스라인으로 산출하고,
 *       <b>해석·권고 서술</b>만 {@code /rag/agent} 로 받는다. 토글 off/미가용 시 결정론 경로로 폴백(degraded:true).</li>
 * </ol>
 *
 * <p><b>결정론 불변</b>: SPC/예측 수치는 절대 AI 가 만들지 않는다(재현 가능). AI 는 서술·분류·우선순위만.
 * <p>모든 응답에 적용 metadata(retrievalMode·rerank·toolUse·structured·degraded)를 표기해 토글 effect 가 관측 가능하게 한다.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class RagMesService {

    private final RagClient rag;
    private final RagToggleService toggles;
    private final AiService ai;   // 기존 결정론/Ollama 폴백 재사용(불변)

    /** 불량 RCA 결정론 결과 스키마(중앙 /structured/agent 강제). */
    private static final Map<String, Object> DEFECT_SCHEMA = Map.of(
        "type", "object",
        "properties", Map.of(
            "rootCauses", Map.of("type", "array", "items", Map.of(
                "type", "object", "properties", Map.of(
                    "cause", Map.of("type", "string"),
                    "evidenceRef", Map.of("type", "string"),
                    "contribution", Map.of("type", "number")
                ))),
            "correctiveActions", Map.of("type", "array", "items", Map.of("type", "string")),
            "priority", Map.of("type", "string"),
            "confidence", Map.of("type", "number")
        ),
        "required", List.of("rootCauses")
    );

    /** 예측 해석 결과 스키마(중앙 /agent 구조화). */
    private static final Map<String, Object> PREDICT_SCHEMA = Map.of(
        "type", "object",
        "properties", Map.of(
            "interpretation", Map.of("type", "string"),
            "drivers", Map.of("type", "array", "items", Map.of("type", "string")),
            "recommendation", Map.of("type", "string"),
            "confidence", Map.of("type", "number")
        ),
        "required", List.of("interpretation")
    );

    // 읽기전용 조회 도구 정의(이름·설명만 노출 — 실제 조회는 중앙에서 컬렉션 검색으로 수행. 쓰기/SSH 금지)
    private static final List<Map<String, Object>> DEFECT_TOOLS = List.of(
        tool("search_ncr", "부적합(NCR) 이력 검색 — 불량코드/LOT 기준 근거 수집(읽기전용)"),
        tool("search_capa", "시정조치(CAPA) 이력 검색 — 유사 불량의 과거 조치(읽기전용)"),
        tool("search_inspection", "검사 결과 검색 — 측정값·합부 판정 근거(읽기전용)")
    );
    private static final List<Map<String, Object>> PREDICT_TOOLS = List.of(
        tool("search_equipment", "설비 가동/비가동·MTBF 이력 검색(읽기전용)"),
        tool("search_production", "생산실적·작업지시 시계열 검색(읽기전용)")
    );

    // ── 1) 품질 분석: 불량 RCA + SPC 이상감지 ──────────────────────────────────
    /**
     * 불량 원인분석 + SPC 이상감지. SPC 수치는 결정론, 원인 서술은 /rag/agent(+structured).
     * @param req {defectCode, context[], values[], ucl, lcl, cl}
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> defectAnalysis(Map<String, Object> req, String actor) {
        String defectCode = str(req.get("defectCode"));
        List<Map<String, Object>> context = req.get("context") instanceof List
            ? (List<Map<String, Object>>) req.get("context") : List.of();

        // (A) SPC 수치 — 항상 결정론(재현 가능). AI 가 만들지 않는다.
        Map<String, Object> spc = null;
        if (req.get("values") instanceof List) {
            spc = ai.spcAnomaly(toDoubles(req.get("values")),
                dbl(req.get("ucl")), dbl(req.get("lcl")), dbl(req.get("cl")));
        }

        RagToggles t = toggles.current();
        Map<String, Object> out = new LinkedHashMap<>();
        if (spc != null) out.put("spc", spc);   // 결정론 수치 결과

        // (B) 원인 서술 — 토글 off/미가용 시 기존 결정론 RCA 폴백
        if (!t.ragEnabled || !rag.available()) {
            Map<String, Object> legacy = ai.defectRootCause(defectCode, context);
            out.put("defect", legacy);
            out.put("degraded", true);
            out.put("degraded_reason", t.ragEnabled ? "rag_unavailable" : "rag_disabled");
            out.put("source", "mes_local_fallback");
            out.putAll(meta(t, t.toolUse ? "agent" : "answer"));
            return out;
        }

        String mode = toggles.effectiveMode(t);
        String task =
            "당신은 제조 품질 엔지니어입니다. 불량코드 '" + defectCode + "' 의 근본원인을 4M·5Why·파레토 관점으로 "
            + "추정하고 시정조치를 제시하라. 제공된 NCR/CAPA/검사 근거에 기반해야 한다. "
            + "수치 판정(SPC 관리한계·Cp/Cpk)은 이미 결정론으로 계산되었으니 재계산하지 말고 해석만 하라. "
            + "근거: " + truncate(context.toString(), 1200)
            + (spc != null ? "\nSPC결과: " + truncate(spc.toString(), 400) : "");

        Map<String, Object> res;
        String technique;
        if (t.toolUse) {
            // 에이전틱 tool-use: 조회 매퍼 도구로 멀티스텝 + 구조화(structured on 시 스키마 결합)
            res = rag.agent(task, DEFECT_TOOLS, mode, t.maxSteps, t.generationModel,
                t.structured ? DEFECT_SCHEMA : null);
            technique = "agent";
        } else if (t.structured) {
            // tool-use off + structured on: 결정론 JSON
            res = rag.structured(task, DEFECT_SCHEMA, t.generationModel, t.temperature);
            technique = "structured";
        } else {
            // 둘 다 off: 검색 근거 답변
            res = rag.answer(task, mode, t.rerank, t.topK, t.generationModel, true);
            technique = "answer";
        }

        boolean degraded = bool(res.get("degraded"));
        if (degraded) {
            Map<String, Object> legacy = ai.defectRootCause(defectCode, context);
            out.put("defect", legacy);
            out.put("degraded", true);
            out.put("degraded_reason", str(res.getOrDefault("degraded_reason", "rag_empty")));
            out.put("source", "mes_local_fallback");
            out.putAll(meta(t, technique));
            return out;
        }

        // 정상 경유 — 구조화 데이터/근거/인용/answerId 표면화
        Map<String, Object> defect = new LinkedHashMap<>();
        if (res.get("data") instanceof Map) defect.putAll((Map<String, Object>) res.get("data"));
        if (res.get("answer") != null) defect.put("narrative", res.get("answer"));
        defect.put("citations", res.get("citations"));
        defect.put("sources", res.get("sources"));
        defect.put("steps", res.get("steps"));               // 에이전트 추론 스텝(tool-use 시)
        defect.put("answerId", res.get("answer_id"));
        out.put("defect", defect);
        out.put("engine", "RAG_" + technique.toUpperCase());
        out.put("degraded", false);
        out.putAll(meta(t, technique));
        return out;
    }

    // ── 2) 예측 분석: 설비 예지보전 + 수요/생산 예측 ───────────────────────────
    /**
     * 설비 예지보전 + 수요/생산 예측. 예측 수치는 결정론 베이스라인, 해석/권고는 /rag/agent.
     * @param req {mode:"pdm"|"forecast", equipmentCode, availability, downtimeCount, mtbfHours, series[], horizon}
     */
    @SuppressWarnings("unchecked")
    public Map<String, Object> predictAnalysis(Map<String, Object> req, String actor) {
        String kind = str(req.getOrDefault("kind", "forecast"));
        RagToggles t = toggles.current();
        Map<String, Object> out = new LinkedHashMap<>();

        // (A) 수치 베이스라인 — 항상 결정론(재현 가능). AI 가 만들지 않는다.
        Map<String, Object> baseline;
        if ("pdm".equalsIgnoreCase(kind)) {
            baseline = ai.predictiveMaintenance(str(req.get("equipmentCode")),
                dbl(req.get("availability")), (int) Math.round(dbl(req.get("downtimeCount"))),
                dbl(req.get("mtbfHours")));
        } else {
            baseline = ai.forecast(toDoubles(req.get("series")),
                (int) Math.round(dbl(req.get("horizon"))));
        }
        out.put("baseline", baseline);

        // (B) 해석/권고 — 토글 off/미가용 시 베이스라인만(degraded)
        if (!t.ragEnabled || !rag.available()) {
            out.put("interpretation", null);
            out.put("degraded", true);
            out.put("degraded_reason", t.ragEnabled ? "rag_unavailable" : "rag_disabled");
            out.put("source", "mes_local_baseline");
            out.putAll(meta(t, t.toolUse ? "agent" : "answer"));
            return out;
        }

        String mode = toggles.effectiveMode(t);
        String task =
            "당신은 제조 운영 분석가입니다. 아래 결정론 통계 결과를 해석하고 우선순위·권고를 제시하라. "
            + "수치는 이미 계산되었으니 재계산하지 말고 의미·이상신호·조치만 서술하라. "
            + ("pdm".equalsIgnoreCase(kind) ? "예지보전 결과" : "수요/생산 예측 결과") + ": "
            + truncate(baseline.toString(), 800);

        Map<String, Object> res;
        String technique;
        if (t.toolUse) {
            res = rag.agent(task, PREDICT_TOOLS, mode, t.maxSteps, t.generationModel,
                t.structured ? PREDICT_SCHEMA : null);
            technique = "agent";
        } else if (t.structured) {
            res = rag.structured(task, PREDICT_SCHEMA, t.generationModel, t.temperature);
            technique = "structured";
        } else {
            res = rag.answer(task, mode, t.rerank, t.topK, t.generationModel, true);
            technique = "answer";
        }

        boolean degraded = bool(res.get("degraded"));
        if (degraded) {
            out.put("interpretation", null);
            out.put("degraded", true);
            out.put("degraded_reason", str(res.getOrDefault("degraded_reason", "rag_empty")));
            out.put("source", "mes_local_baseline");
            out.putAll(meta(t, technique));
            return out;
        }

        Map<String, Object> interp = new LinkedHashMap<>();
        if (res.get("data") instanceof Map) interp.putAll((Map<String, Object>) res.get("data"));
        if (res.get("answer") != null) interp.put("narrative", res.get("answer"));
        interp.put("citations", res.get("citations"));
        interp.put("steps", res.get("steps"));
        interp.put("answerId", res.get("answer_id"));
        out.put("interpretation", interp);
        out.put("engine", "RAG_" + technique.toUpperCase());
        out.put("degraded", false);
        out.putAll(meta(t, technique));
        return out;
    }

    // ── 3) 피드백 → 중앙 /rag/feedback (solution=mes 격리) ─────────────────────
    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 static Map<String, Object> tool(String name, String desc) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("name", name);
        m.put("description", desc);
        m.put("readonly", true);
        return m;
    }

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

    private List<Double> toDoubles(Object o) {
        List<Double> out = new ArrayList<>();
        if (o instanceof List<?> list) {
            for (Object v : list) {
                try { out.add(Double.parseDouble(String.valueOf(v))); } catch (Exception ignore) {}
            }
        }
        return out;
    }

    private double dbl(Object o) {
        try { return o == null ? 0.0 : Double.parseDouble(String.valueOf(o)); } catch (Exception e) { return 0.0; }
    }

    private String truncate(String s, int max) {
        if (s == null) return "";
        return s.length() > max ? s.substring(0, max) : s;
    }

    private boolean bool(Object o) { return Boolean.TRUE.equals(o) || "true".equalsIgnoreCase(String.valueOf(o)); }
    private String str(Object o) { return o == null ? "" : String.valueOf(o); }
    private String str0(Object o) { return o == null ? null : String.valueOf(o); }
}
