package com.zioinfo.mall.rag;

import com.zioinfo.mall.ai.MallAiService;
import com.zioinfo.mall.analytics.mapper.AnalyticsMapper;
import com.zioinfo.mall.product.MallProduct;
import com.zioinfo.mall.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;
import java.util.regex.Pattern;

/**
 * Mall 대표 AI 기능 RAG 배선 레이어(순증·비파괴).
 *
 * <p>기존 {@link MallAiService}(Ollama 우선 + Java 폴백)는 <b>불변</b>. 본 서비스는 중앙 guardia-rag 를
 * 경유하는 <b>별개 레이어</b>로, 두 대표 기능을 전환한다:
 * <ol>
 *   <li><b>상품 추천·자연어 상품검색</b> → {@code /rag/answer}(retrieval_mode=hybrid 근거) +
 *       {@code /rag/structured}(productId·score·reason 결정론 JSON). 매장 재고 ON/OFF·상태 필터 적용.
 *       토글 off/미가용 시 기존 {@link MallAiService#recommend}/{@link MallAiService#nlSearch} 폴백(degraded:true).</li>
 *   <li><b>수요예측·재고이양 추천</b> → {@code /rag/agent}(tool-use, 사람 승인 게이트, 제안만).
 *       tool_use 토글 off/미가용 시 기존 {@link MallAiService#demandForecast}/{@link MallAiService#transferRecommendations} 통계 폴백.</li>
 * </ol>
 *
 * <p>모든 응답에 적용 metadata(retrieval_mode·rerank·toolUse·structured·degraded·abstained)를 표기해
 * 토글 effect 가 관측 가능하게 한다. 자격증명/카드/회원 PII/내부IP/매장 SSH/스택트레이스는 입력·출력 모두 마스킹.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class MallRagAiService {

    private final RagClient rag;
    private final RagToggleService toggles;
    private final MallAiService mallAi;            // 기존 Ollama/Java 폴백 재사용(불변)
    private final AnalyticsMapper analyticsMapper; // 수요예측 신호(매장별 판매)

    /** 추천/검색 결정론 결과 JSON 스키마(중앙 /structured 강제). */
    private static final Map<String, Object> RECOMMEND_SCHEMA = Map.of(
        "type", "object",
        "properties", Map.of(
            "items", Map.of(
                "type", "array",
                "items", Map.of(
                    "type", "object",
                    "properties", Map.of(
                        "productId", Map.of("type", "integer"),
                        "name", Map.of("type", "string"),
                        "score", Map.of("type", "number"),
                        "reason", Map.of("type", "string")
                    ),
                    "required", List.of("productId")
                )
            ),
            "summary", Map.of("type", "string"),
            "noResults", Map.of("type", "boolean")
        ),
        "required", List.of("items")
    );

    /** 재고이양/수요 결정 JSON 스키마(중앙 /structured·/agent 최종 정리 강제). */
    private static final Map<String, Object> DECISION_SCHEMA = Map.of(
        "type", "object",
        "properties", Map.of(
            "decisions", Map.of(
                "type", "array",
                "items", Map.of(
                    "type", "object",
                    "properties", Map.of(
                        "action", Map.of("type", "string"),       // forecast|transfer|clearance
                        "storeFrom", Map.of("type", "integer"),
                        "storeTo", Map.of("type", "integer"),
                        "productId", Map.of("type", "integer"),
                        "qty", Map.of("type", "integer"),
                        "expectedImpact", Map.of("type", "string"),
                        "confidence", Map.of("type", "number")
                    ),
                    "required", List.of("action")
                )
            ),
            "rationale", Map.of("type", "string")
        ),
        "required", List.of("decisions")
    );

    /** 입력/근거 내 민감정보 마스킹 패턴(내부IP·자격증명·토큰·카드번호). */
    private static final Pattern SENSITIVE = Pattern.compile(
        "(?i)(pass(word)?\\s*[:=]\\s*\\S+|token\\s*[:=]\\s*\\S+|api[-_]?key\\s*[:=]\\s*\\S+"
            + "|\\b(?:10|172|192)\\.(?:\\d{1,3})\\.(?:\\d{1,3})\\.(?:\\d{1,3})\\b"
            + "|\\b(?:\\d[ -]?){13,16}\\b)");

    // ── ① 상품 추천·자연어 검색 (중앙 /answer hybrid + /structured) ────────────
    @SuppressWarnings("unchecked")
    public Map<String, Object> recommend(Map<String, Object> req, String actor) {
        String occasion = str(req.get("occasion"));
        String keyword = str(req.get("keyword"));
        if (keyword.isBlank()) keyword = str(req.get("query"));   // nl-search 진입 호환
        int limit = clamp(intOf(req.get("limit"), 6), 1, 20);
        RagToggles t = toggles.current();
        Map<String, Object> out = new LinkedHashMap<>();

        // RAG 미가용 → 기존 추천(판매순/평점 폴백)
        if (!t.ragEnabled || !rag.available()) {
            return fallbackRecommend(req, actor, t, occasion, keyword, limit,
                t.ragEnabled ? "rag_unavailable" : "rag_disabled");
        }

        // hybrid(BM25+벡터) 검색 근거 — 매칭 사유 동반. graphrag 토글 on 이면 graph.
        String mode = toggles.effectiveMode(t);
        String query = "GUARDiA Mall 꽃집 카탈로그에서 다음 조건에 맞는 부케/상품을 추천하라. "
            + "occasion='" + (occasion.isBlank() ? "any" : occasion) + "' "
            + "요청='" + keyword + "'. 근거(매칭 사유)를 동반하고, 조건에 맞는 상품이 없으면 추천을 만들지 말라.";
        Map<String, Object> ares = rag.answer(query, mode, t.rerank, t.topK, t.generationModel, true);
        boolean degraded = bool(ares.get("degraded"));

        if (degraded || ares.get("answer") == null) {
            return fallbackRecommend(req, actor, t, occasion, keyword, limit,
                str(ares.getOrDefault("degraded_reason", "answer_empty")));
        }

        // /structured 결정론 결과(productId·score·reason 고정) — 토글 on 시
        List<Map<String, Object>> items = new ArrayList<>();
        boolean structuredApplied = false;
        if (t.structured) {
            String sp = "검색 근거에서 추천 상품 목록을 추출하라. 각 항목은 productId·name·score·reason. "
                + "카탈로그 밖 상품을 지어내지 말 것.\n근거:\n" + mask(str(ares.get("answer")));
            Map<String, Object> sres = rag.structured(sp, RECOMMEND_SCHEMA, t.generationModel, t.temperature);
            if (!bool(sres.get("degraded")) && sres.get("data") instanceof Map) {
                Map<String, Object> data = (Map<String, Object>) sres.get("data");
                Object raw = data.get("items");
                if (raw instanceof List) {
                    for (Object o : (List<Object>) raw) if (o instanceof Map) items.add((Map<String, Object>) o);
                }
                structuredApplied = !items.isEmpty();
            }
        }

        out.put("items", items);                        // 결정론 추천(있으면)
        out.put("summary", mask(str(ares.get("answer"))));
        out.put("grounded", ares.get("grounded"));
        out.put("faithfulness", ares.get("faithfulness"));
        out.put("citations", ares.get("citations"));
        out.put("sources", ares.get("sources"));
        out.put("guardrail", ares.get("guardrail"));    // 근거 부족 시 보류(환각 차단)
        out.put("abstained", ares.getOrDefault("abstained", Boolean.FALSE.equals(ares.get("grounded"))));
        out.put("answerId", ares.get("answer_id"));     // /feedback 연계
        out.put("engine", structuredApplied ? "RAG_ANSWER+STRUCTURED" : "RAG_ANSWER");
        out.put("degraded", false);
        out.putAll(meta(t, structuredApplied ? "structured" : "answer", structuredApplied));
        return out;
    }

    private Map<String, Object> fallbackRecommend(Map<String, Object> req, String actor, RagToggles t,
                                                  String occasion, String keyword, int limit, String reason) {
        Map<String, Object> out = new LinkedHashMap<>();
        List<MallProduct> legacy = mallAi.recommend(occasion.isBlank() ? null : occasion,
            keyword.isBlank() ? null : keyword, limit);
        out.put("items", legacy);                       // 기존 추천(판매순/평점)
        out.put("summary", legacy.isEmpty() ? "조건에 맞는 상품이 없습니다." : "인기/평점 기반 추천(폴백).");
        out.put("degraded", true);
        out.put("degraded_reason", reason);
        out.put("source", "mall_local_fallback");
        out.put("engine", "MALL_LOCAL");
        out.putAll(meta(t, "answer", false));
        return out;
    }

    // ── ② 수요예측·재고이양 추천 (중앙 /agent tool-use, 제안만) ────────────────
    @SuppressWarnings("unchecked")
    public Map<String, Object> demandPlan(Map<String, Object> req, String actor) {
        String season = str(req.getOrDefault("season", "valentine"));
        int days = clamp(intOf(req.get("days"), 14), 1, 90);
        List<Long> storeIds = longList(req.get("storeIds"));
        RagToggles t = toggles.current();
        Map<String, Object> out = new LinkedHashMap<>();

        if (!t.ragEnabled || !rag.available()) {
            return fallbackDemand(t, season, days, storeIds, t.ragEnabled ? "rag_unavailable" : "rag_disabled");
        }

        String query = "GUARDiA Mall 운영 의사결정: season='" + season + "' 기간 " + days + "일 기준으로 "
            + "매장별 수요예측과 매장간 재고이양/당일소진을 제안하라(자동실행 아닌 권고). "
            + "꽃의 단명성·이벤트(밸런타인/어머니날)·ZIP 권역 인접을 고려하고, "
            + "데이터가 부족하면 단정하지 말고 저신뢰로 표시하라.";

        String mode = toggles.effectiveMode(t);
        Map<String, Object> ares;
        boolean usedAgent = false;

        if (t.toolUse) {
            // 에이전틱 tool-use: Mall read-only 도구 등록(값 노출 없는 메타 조회)
            ares = rag.agent(query, demandTools(), t.agentMaxSteps, t.generationModel, true);
            usedAgent = !bool(ares.get("degraded"));
            if (bool(ares.get("degraded"))) {
                // /agent 불가 → /answer 강등
                ares = rag.answer(query, mode, t.rerank, t.topK, t.generationModel, true);
            }
        } else {
            ares = rag.answer(query, mode, t.rerank, t.topK, t.generationModel, true);
        }

        boolean degraded = bool(ares.get("degraded"));
        if (degraded || ares.get("answer") == null) {
            return fallbackDemand(t, season, days, storeIds,
                str(ares.getOrDefault("degraded_reason", "answer_empty")));
        }

        // /structured 로 최종 결정 정리(action·storeFrom·storeTo·qty·confidence) — 토글 on 시
        List<Map<String, Object>> decisions = new ArrayList<>();
        boolean structuredApplied = false;
        if (t.structured) {
            String sp = "다음 운영 분석에서 결정 목록을 추출하라. 각 항목 action·storeFrom·storeTo·productId·qty·"
                + "expectedImpact·confidence. 추측 수치를 지어내지 말 것.\n분석:\n" + mask(str(ares.get("answer")));
            Map<String, Object> sres = rag.structured(sp, DECISION_SCHEMA, t.generationModel, t.temperature);
            if (!bool(sres.get("degraded")) && sres.get("data") instanceof Map) {
                Map<String, Object> data = (Map<String, Object>) sres.get("data");
                Object raw = data.get("decisions");
                if (raw instanceof List) {
                    for (Object o : (List<Object>) raw) if (o instanceof Map) decisions.add((Map<String, Object>) o);
                }
                structuredApplied = !decisions.isEmpty();
            }
        }

        out.put("decisions", decisions);                 // 결정론 제안(있으면) — 실행은 승인 게이트
        out.put("rationale", mask(str(ares.get("answer"))));
        out.put("approvalRequired", true);               // 사람 승인 게이트(AI 임의 라이브 변경 금지)
        out.put("grounded", ares.get("grounded"));
        out.put("faithfulness", ares.get("faithfulness"));
        out.put("citations", ares.get("citations"));
        out.put("sources", ares.get("sources"));
        out.put("steps", ares.get("steps"));             // /agent 진단 단계(있으면)
        out.put("guardrail", ares.get("guardrail"));
        out.put("abstained", ares.getOrDefault("abstained", Boolean.FALSE.equals(ares.get("grounded"))));
        out.put("answerId", ares.get("answer_id"));
        out.put("engine", usedAgent ? "RAG_AGENT" : "RAG_ANSWER");
        out.put("degraded", false);
        out.putAll(meta(t, usedAgent ? "agent" : "answer", structuredApplied));
        return out;
    }

    private Map<String, Object> fallbackDemand(RagToggles t, String season, int days,
                                               List<Long> storeIds, String reason) {
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("forecast", mallAi.demandForecast(season, analyticsMapper.salesByStore(days)));
        if (!storeIds.isEmpty()) out.put("transfers", mallAi.transferRecommendations(storeIds));
        out.put("approvalRequired", true);
        out.put("degraded", true);
        out.put("degraded_reason", reason);
        out.put("source", "mall_local_fallback");        // 통계(이동평균·계절지수) 폴백
        out.put("engine", "MALL_LOCAL");
        out.putAll(meta(t, "agent", false));
        return out;
    }

    /** 👍/👎 피드백 → 중앙 /rag/feedback (solution=mall 격리). */
    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;
    }

    // ── Mall 고유 read-only 도구 레지스트리(/agent 용) ────────────────────────
    // 자격증명·매장 SSH·카드/회원 PII 는 도구 응답에서 제외 — 집계 메타/판매·재고 수량만.
    private List<Map<String, Object>> demandTools() {
        return List.of(
            tool("mall_store_inventory", "매장별 상품 재고 수량/신선도/판매가능(ON·OFF) 메타를 조회",
                Map.of("storeId", Map.of("type", "integer"))),
            tool("mall_sales_history", "기간별 매장·상품 판매량 집계를 조회",
                Map.of("days", Map.of("type", "integer"))),
            tool("mall_zone_adjacency", "매장 ZIP 배송권역 인접/배송가능 매장 메타를 조회",
                Map.of("storeId", Map.of("type", "integer"))),
            tool("mall_season_signal", "이벤트/시즌(밸런타인·어머니날) 수요 배수 신호를 조회",
                Map.of("season", Map.of("type", "string")))
        );
    }

    private Map<String, Object> tool(String name, String desc, Map<String, Object> params) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("name", name);
        m.put("description", desc);
        m.put("parameters", Map.of("type", "object", "properties", params));
        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("stream", t.stream);
        applied.put("technique", technique);
        m.put("applied", applied);
        return m;
    }

    @SuppressWarnings("unchecked")
    private List<Long> longList(Object o) {
        List<Long> ids = new ArrayList<>();
        if (o instanceof List) {
            for (Object x : (List<Object>) o) {
                try { ids.add(Long.valueOf(String.valueOf(x))); } catch (Exception ignored) { }
            }
        }
        return ids;
    }

    private int intOf(Object o, int def) {
        if (o == null) return def;
        try { return Integer.parseInt(String.valueOf(o).trim()); } catch (Exception e) { return def; }
    }

    private int clamp(int v, int min, int max) { return Math.max(min, Math.min(max, v)); }

    private String mask(String s) {
        if (s == null || s.isEmpty()) return s;
        return SENSITIVE.matcher(s).replaceAll("***");
    }

    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); }
}
