package com.zioinfo.hrm.ai.learning;

import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.Statement;

/**
 * 로컬 임베디드 DuckDB 학습 저장소. [GUARDiA-HRM]
 *
 * <p><b>목적</b>: AI 피드백(👍/👎+교정)·추론 로그를 솔루션 로컬 파일에 축적해 오프라인 AI 분석/학습 데이터셋
 * 근거로 삼는다(중앙 guardia-rag 전달과 병행). 파일 격리: {@code /opt/guardia-hrm/data/hrm_learning.duckdb}
 * (env {@code guardia.learning.duckdb-path} 로 override).
 *
 * <p><b>스키마(멱등)</b>:
 * <ul>
 *   <li>{@code ai_feedback(id, ts, solution, feature, question, answer, verdict, correction, user_masked)}</li>
 *   <li>{@code ai_infer_log(id, ts, provider, model, latency_ms, degraded)}</li>
 * </ul>
 *
 * <p><b>안전성(무회귀)</b>: 어떤 사유로든(드라이버 미탑재·경로 생성 실패·잠금) 초기화 실패 시 저장소를
 * <b>비활성</b>으로 두고 예외를 던지지 않는다 — 기록은 no-op 가 되며 AI 본 기능은 그대로 동작한다.
 * PII 는 {@link PiiMasker} 로 적재 전 마스킹한다(인사 데이터 특성상 필수).
 *
 * <p>DuckDB 는 파일당 단일 read-write 커넥션만 허용하므로 한 커넥션을 보유하고 {@code synchronized} 로
 * 접근을 직렬화한다(피드백/추론 로그는 저빈도).
 *
 * <p><b>레퍼런스</b>: guardia-bi {@code ai.learning.LearningStore} 미러(SOLUTION·경로 치환).
 */
@Slf4j
@Component
public class LearningStore {

    public static final String SOLUTION = "hrm";

    @Value("${guardia.learning.duckdb-path:/opt/guardia-hrm/data/hrm_learning.duckdb}")
    private String duckdbPath;

    private volatile boolean enabled = false;
    private Connection conn;

    @PostConstruct
    synchronized void init() {
        try {
            Path p = Paths.get(duckdbPath);
            Path parent = p.getParent();
            if (parent != null) {
                Files.createDirectories(parent);
            }
            // 드라이버 로드(신규 의존성 org.duckdb:duckdb_jdbc). 미탑재/환경 문제 시 비활성 폴백.
            Class.forName("org.duckdb.DuckDBDriver");
            conn = DriverManager.getConnection("jdbc:duckdb:" + p.toAbsolutePath());
            try (Statement st = conn.createStatement()) {
                st.execute("CREATE SEQUENCE IF NOT EXISTS ai_feedback_seq START 1");
                st.execute("CREATE SEQUENCE IF NOT EXISTS ai_infer_seq START 1");
                st.execute("CREATE TABLE IF NOT EXISTS ai_feedback ("
                        + "id BIGINT DEFAULT nextval('ai_feedback_seq') PRIMARY KEY,"
                        + "ts TIMESTAMP DEFAULT now(),"
                        + "solution VARCHAR, feature VARCHAR, question VARCHAR, answer VARCHAR,"
                        + "verdict VARCHAR, correction VARCHAR, user_masked VARCHAR)");
                st.execute("CREATE TABLE IF NOT EXISTS ai_infer_log ("
                        + "id BIGINT DEFAULT nextval('ai_infer_seq') PRIMARY KEY,"
                        + "ts TIMESTAMP DEFAULT now(),"
                        + "provider VARCHAR, model VARCHAR, latency_ms BIGINT, degraded BOOLEAN)");
            }
            enabled = true;
            log.info("DuckDB 학습 저장소 활성: {}", p.toAbsolutePath());
        } catch (Throwable t) {   // ClassNotFound·IO·SQL·잠금 등 — 조용히 비활성(무회귀)
            enabled = false;
            log.warn("DuckDB 학습 저장소 비활성(무회귀 폴백): {}", t.getClass().getSimpleName());
        }
    }

    public boolean isEnabled() {
        return enabled;
    }

    /** 피드백 1건 적재(PII 마스킹). 실패 시 조용히 무시(false 반환). */
    public synchronized boolean recordFeedback(String feature, String question, String answer,
                                               String verdict, String correction, String user) {
        if (!enabled || conn == null) {
            return false;
        }
        String sql = "INSERT INTO ai_feedback (solution, feature, question, answer, verdict, correction, user_masked)"
                + " VALUES (?,?,?,?,?,?,?)";
        try (PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, SOLUTION);
            ps.setString(2, trunc(feature, 100));
            ps.setString(3, PiiMasker.mask(trunc(question, 4000)));
            ps.setString(4, PiiMasker.mask(trunc(answer, 8000)));
            ps.setString(5, trunc(verdict, 10));
            ps.setString(6, PiiMasker.mask(trunc(correction, 8000)));
            ps.setString(7, PiiMasker.maskUser(user));
            ps.executeUpdate();
            return true;
        } catch (Exception e) {
            log.warn("DuckDB feedback 적재 실패(무시): {}", e.getClass().getSimpleName());
            return false;
        }
    }

    /** 추론 로그 1건 적재. 실패 시 조용히 무시. */
    public synchronized void recordInfer(String provider, String model, long latencyMs, boolean degraded) {
        if (!enabled || conn == null) {
            return;
        }
        String sql = "INSERT INTO ai_infer_log (provider, model, latency_ms, degraded) VALUES (?,?,?,?)";
        try (PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, trunc(provider, 40));
            ps.setString(2, trunc(model, 60));
            ps.setLong(3, latencyMs);
            ps.setBoolean(4, degraded);
            ps.executeUpdate();
        } catch (Exception e) {
            log.warn("DuckDB infer 적재 실패(무시): {}", e.getClass().getSimpleName());
        }
    }

    private static String trunc(String s, int max) {
        if (s == null) {
            return null;
        }
        return s.length() <= max ? s : s.substring(0, max);
    }
}
