package com.zioinfo.esn.rag;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

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

/**
 * ESN RAG 기법 토글 서비스 — <b>테넌트 격리</b>.
 *
 * <p>전용 {@code esn_rag_setting}(tenant_code, key, value) 테이블에서 테넌트별 토글을 읽는다.
 * 한 테넌트가 다른 테넌트 설정을 읽거나 바꿀 수 없다. 키 네임스페이스 {@code rag.*}.
 *
 * <p>무거운 기법(graphrag·rerank·stream·tool_use)은 서버 RAM 제약상 <b>기본 off</b>,
 * retrieval_mode 기본 vector, 소형모델 기본.
 */
@Service
@RequiredArgsConstructor
public class RagToggleService {

    private final RagSettingMapper mapper;

    public static final String K_ENABLED      = "rag.enabled";
    public static final String K_MODE         = "rag.retrieval_mode";   // vector|hybrid|graph
    public static final String K_RERANK       = "rag.rerank";           // 무거움→기본 off
    public static final String K_GRAPHRAG     = "rag.graphrag";         // 무거움→기본 off
    public static final String K_TOOL_USE     = "rag.tool_use";         // 에이전틱→기본 off
    public static final String K_STRUCTURED   = "rag.structured";       // 결정론 JSON→기본 on
    public static final String K_STREAM       = "rag.stream";           // SSE→기본 off
    public static final String K_TOP_K        = "rag.top_k";
    public static final String K_AGENT_STEPS  = "rag.agent_max_steps";
    public static final String K_TEMPERATURE  = "rag.temperature";
    public static final String K_FAITHFULNESS = "rag.faithfulness_threshold";
    public static final String K_GEN_MODEL    = "rag.generation_model";

    /** 변경 허용 키 화이트리스트 — rag.* 외 임의 키 주입 차단. */
    private static final java.util.Set<String> ALLOWED = java.util.Set.of(
        K_ENABLED, K_MODE, K_RERANK, K_GRAPHRAG, K_TOOL_USE, K_STRUCTURED, K_STREAM,
        K_TOP_K, K_AGENT_STEPS, K_TEMPERATURE, K_FAITHFULNESS, K_GEN_MODEL);

    /** 테넌트 토글 전부 조회(DB 누락 키는 안전 기본값 머지). */
    public RagToggles current(String tenantCode) {
        String tc = normTenant(tenantCode);
        Map<String, String> m = new LinkedHashMap<>();
        List<Map<String, Object>> rows = mapper.findByTenant(tc);
        if (rows != null) {
            for (Map<String, Object> r : rows) {
                Object k = r.get("settingKey");
                Object v = r.get("settingValue");
                if (k != null) m.put(String.valueOf(k), v == null ? null : String.valueOf(v));
            }
        }

        RagToggles t = new RagToggles();
        t.tenantCode = tc;
        t.ragEnabled = bool(m.get(K_ENABLED), true);
        t.retrievalMode = mode(m.get(K_MODE));
        t.rerank = bool(m.get(K_RERANK), false);
        t.graphrag = bool(m.get(K_GRAPHRAG), false);
        t.toolUse = bool(m.get(K_TOOL_USE), false);
        t.structured = bool(m.get(K_STRUCTURED), true);
        t.stream = bool(m.get(K_STREAM), false);
        t.topK = clampInt(m.get(K_TOP_K), 5, 1, 20);
        t.agentMaxSteps = clampInt(m.get(K_AGENT_STEPS), 4, 1, 8);
        t.temperature = clampDouble(m.get(K_TEMPERATURE), 0.1, 0.0, 1.0);
        t.faithfulnessThreshold = clampDouble(m.get(K_FAITHFULNESS), 0.5, 0.0, 1.0);
        t.generationModel = blankTo(m.get(K_GEN_MODEL), "llama3.2:1b");
        return t;
    }

    /** 토글 변경(테넌트 격리, 화이트리스트 키만). 반환=변경 후 전체 스냅샷. */
    public RagToggles update(String tenantCode, String key, String value) {
        if (key == null || !ALLOWED.contains(key)) {
            throw new IllegalArgumentException("ERR-RAG-400: 허용되지 않은 토글 키");
        }
        String tc = normTenant(tenantCode);
        mapper.upsert(tc, key, value == null ? "" : value);
        return current(tc);
    }

    /** retrieval_mode 결정: graphrag 토글 on 이면 graph 우선, 아니면 설정 mode. */
    public String effectiveMode(RagToggles t) {
        if (t.graphrag) return "graph";
        return t.retrievalMode;
    }

    // ── 정규화/클램프 (상한 강제 — 폭주 차단) ───────────────────────────────
    private String normTenant(String tc) {
        if (tc == null || tc.isBlank()) return "ZIOINFO";   // 기본 테넌트
        return tc.trim().toUpperCase();
    }

    private boolean bool(String v, boolean def) {
        if (v == null) return def;
        String s = v.trim();
        return "true".equalsIgnoreCase(s) || "1".equals(s) || "on".equalsIgnoreCase(s);
    }

    private String mode(String v) {
        if (v == null) return "vector";
        String s = v.trim().toLowerCase();
        return (s.equals("hybrid") || s.equals("graph") || s.equals("vector")) ? s : "vector";
    }

    private int clampInt(String v, int def, int min, int max) {
        try {
            return Math.max(min, Math.min(max, Integer.parseInt(v.trim())));
        } catch (Exception e) {
            return def;
        }
    }

    private double clampDouble(String v, double def, double min, double max) {
        try {
            return Math.max(min, Math.min(max, Double.parseDouble(v.trim())));
        } catch (Exception e) {
            return def;
        }
    }

    private String blankTo(String v, String def) {
        return (v == null || v.isBlank()) ? def : v.trim();
    }

    /** 토글 스냅샷 DTO(응답 metadata·화면 공용). */
    public static class RagToggles {
        public String tenantCode = "ZIOINFO";
        public boolean ragEnabled = true;
        public String retrievalMode = "vector";
        public boolean rerank = false;
        public boolean graphrag = false;
        public boolean toolUse = false;
        public boolean structured = true;
        public boolean stream = false;
        public int topK = 5;
        public int agentMaxSteps = 4;
        public double temperature = 0.1;
        public double faithfulnessThreshold = 0.5;
        public String generationModel = "llama3.2:1b";

        public Map<String, Object> toMap() {
            Map<String, Object> m = new LinkedHashMap<>();
            m.put("tenantCode", tenantCode);
            m.put("ragEnabled", ragEnabled);
            m.put("retrievalMode", retrievalMode);
            m.put("rerank", rerank);
            m.put("graphrag", graphrag);
            m.put("toolUse", toolUse);
            m.put("structured", structured);
            m.put("stream", stream);
            m.put("topK", topK);
            m.put("agentMaxSteps", agentMaxSteps);
            m.put("temperature", temperature);
            m.put("faithfulnessThreshold", faithfulnessThreshold);
            m.put("generationModel", generationModel);
            return m;
        }
    }
}
