package com.zioinfo.mes.rag;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;

import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 얇은 중앙 guardia-rag REST 클라이언트 (MES 전용).
 *
 * <p><b>핵심 설계</b>: MES 는 LangChain·검색·에이전틱 로직을 Java 에 재구현하지 않는다. 중앙 Python
 * guardia-rag 의 검색·에이전틱·구조화·검증을 REST 로 호출하기만 한다. 미가용/타임아웃/RAM 부족 시
 * 절대 예외를 전파하지 않고 {@code degraded:true} 가 표시된 빈/폴백 결과를 돌려준다(서비스 계층이
 * MES 결정론 로컬 폴백 수행).
 *
 * <p><b>보안 불변</b>: 온프레미스 전용. 응답에서 자격증명/거래처·인사 PII/스택트레이스 미노출. 오류는 요약만 로깅.
 *
 * <p>호출 계약(중앙, 8020):
 * <ul>
 *   <li>POST /rag/answer     — 검색(retrieval_mode)+근거생성+인용+guardrail</li>
 *   <li>POST /rag/agent      — 에이전틱 tool-use(멀티스텝 추론, step 상한)</li>
 *   <li>POST /rag/structured — format:json 결정론 JSON(스키마 강제)</li>
 *   <li>POST /rag/feedback   — 👍/👎 피드백(solution=mes 격리)</li>
 *   <li>GET  /rag/trust/settings — 가용성 프로브</li>
 * </ul>
 */
@Slf4j
@Component
public class RagClient {

    private final WebClient.Builder builder;
    private final RagProperties props;

    public RagClient(WebClient.Builder builder, RagProperties props) {
        this.builder = builder;
        this.props = props;
    }

    /** 중앙 RAG 사용 가능 여부(설정 enabled + trust/settings 도달). */
    public boolean available() {
        if (!props.isEnabled()) return false;
        try {
            builder.baseUrl(props.getBaseUrl()).build()
                .get().uri("/rag/trust/settings?solution=" + props.getSolution())
                .retrieve().bodyToMono(String.class)
                .timeout(Duration.ofSeconds(3)).block();
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * POST /rag/structured — 스키마 강제 결정론 JSON.
     *
     * @return {@code {data:{...}, valid, fallback_used, degraded}} 형태. 미가용 시 {@code degraded:true}.
     */
    public Map<String, Object> structured(String prompt, Map<String, Object> schema,
                                          String model, double temperature) {
        if (!props.isEnabled()) return degraded("rag_disabled");
        try {
            Map<String, Object> body = new LinkedHashMap<>();
            body.put("solution", props.getSolution());
            body.put("prompt", prompt);
            if (schema != null) body.put("schema", schema);
            if (model != null) body.put("model", model);
            body.put("temperature", temperature);
            body.put("max_retries", 1);
            Map<String, Object> res = post("/rag/structured", body);
            return res != null ? res : degraded("rag_no_response");
        } catch (Exception e) {
            log.warn("RAG /structured 일시 불가 — MES 폴백: {}", summarize(e));
            return degraded("rag_unavailable");
        }
    }

    /**
     * POST /rag/answer — 검색(retrieval_mode)+생성+검증+guardrail.
     *
     * @return {@code {answer, grounded, faithfulness, citations[], guardrail{...}, sources[], answer_id, degraded}}.
     *         미가용 시 {@code degraded:true}.
     */
    public Map<String, Object> answer(String query, String retrievalMode, boolean rerank,
                                      int topK, String model, boolean verify) {
        if (!props.isEnabled()) return degraded("rag_disabled");
        try {
            Map<String, Object> options = new LinkedHashMap<>();
            options.put("rerank", rerank);
            options.put("stream", false);
            options.put("verify", verify);
            if (model != null) options.put("model", model);

            Map<String, Object> body = new LinkedHashMap<>();
            body.put("solution", props.getSolution());
            body.put("query", query);
            body.put("retrieval_mode", retrievalMode == null ? "vector" : retrievalMode);
            body.put("top_k", topK <= 0 ? 5 : topK);
            body.put("options", options);
            Map<String, Object> res = post("/rag/answer", body);
            return res != null ? res : degraded("rag_no_response");
        } catch (Exception e) {
            log.warn("RAG /answer 일시 불가 — MES 폴백: {}", summarize(e));
            return degraded("rag_unavailable");
        }
    }

    /**
     * POST /rag/agent — 에이전틱 tool-use(멀티스텝 추론). MES 도메인 조회 매퍼를 도구로 노출해
     * 조회→집계→해석을 수행한다. {@code maxSteps} 상한으로 폭주/RAM 위협 차단.
     *
     * @param tools 읽기전용 조회 도구 정의 목록(이름·설명·파라미터 스키마). 쓰기/SSH 도구 금지.
     * @return {@code {answer, steps[], tool_calls[], structured?, answer_id, degraded}}. 미가용 시 {@code degraded:true}.
     */
    public Map<String, Object> agent(String task, Object tools, String retrievalMode,
                                     int maxSteps, String model, Map<String, Object> structuredSchema) {
        if (!props.isEnabled()) return degraded("rag_disabled");
        try {
            Map<String, Object> options = new LinkedHashMap<>();
            options.put("max_steps", maxSteps <= 0 ? 4 : Math.min(maxSteps, 8));   // 상한 강제
            options.put("stream", false);
            if (model != null) options.put("model", model);
            if (retrievalMode != null) options.put("retrieval_mode", retrievalMode);
            if (structuredSchema != null) options.put("schema", structuredSchema);  // tool-use + 구조화 결합

            Map<String, Object> body = new LinkedHashMap<>();
            body.put("solution", props.getSolution());
            body.put("task", task);
            if (tools != null) body.put("tools", tools);
            body.put("options", options);
            Map<String, Object> res = post("/rag/agent", body);
            return res != null ? res : degraded("rag_no_response");
        } catch (Exception e) {
            log.warn("RAG /agent 일시 불가 — MES 폴백: {}", summarize(e));
            return degraded("rag_unavailable");
        }
    }

    /**
     * POST /rag/feedback — 👍/👎 피드백(solution=mes 격리).
     *
     * @return {@code {feedback_id, stored}} 또는 미가용 시 {@code {stored:false, degraded:true}}.
     */
    public Map<String, Object> feedback(String answerId, String query, String answer,
                                        String verdict, String correction, String userRef) {
        if (!props.isEnabled()) return Map.of("stored", false, "degraded", true);
        try {
            Map<String, Object> body = new LinkedHashMap<>();
            body.put("solution", props.getSolution());
            if (answerId != null) body.put("answer_id", answerId);
            if (query != null) body.put("query", query);
            if (answer != null) body.put("answer", answer);
            body.put("verdict", verdict);
            if (correction != null) body.put("correction", correction);
            if (userRef != null) body.put("user_ref", userRef);
            Map<String, Object> res = post("/rag/feedback", body);
            return res != null ? res : Map.of("stored", false, "degraded", true);
        } catch (Exception e) {
            log.warn("RAG /feedback 일시 불가: {}", summarize(e));
            return Map.of("stored", false, "degraded", true);
        }
    }

    // ── helpers ───────────────────────────────────────────────────────────
    @SuppressWarnings("unchecked")
    private Map<String, Object> post(String path, Map<String, Object> body) {
        return builder.baseUrl(props.getBaseUrl()).build()
            .post().uri(path)
            .header("X-Solution-Key", props.getSolution())
            .bodyValue(body)
            .retrieve()
            .bodyToMono(Map.class)
            .timeout(Duration.ofMillis(props.getTimeoutMs()))
            .map(m -> (Map<String, Object>) m)
            .block();
    }

    private Map<String, Object> degraded(String reason) {
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("degraded", true);
        out.put("degraded_reason", reason);
        return out;
    }

    /** 스택트레이스 미노출: 메시지 1줄만 요약. */
    private String summarize(Exception e) {
        String m = e.getMessage();
        if (m == null) return e.getClass().getSimpleName();
        return m.length() > 160 ? m.substring(0, 160) : m;
    }
}
