package com.zioinfo.kintex.ai.nlquery;

import com.zioinfo.kintex.ai.AiTextRouter;
import com.zioinfo.kintex.ai.AiTextRouter.AiResult;
import com.zioinfo.kintex.ai.nlquery.NlQuerySqlGuard.GuardedSql;
import com.zioinfo.kintex.ai.nlquery.dto.NlQueryDtos.NlQueryResult;
import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

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

/**
 * F092 자연어 조회(Text-to-SQL) — 자연어 질문을 안전한 read-only SELECT 로 변환·실행한다.
 *
 * <p><b>파이프라인</b>: 질문 + 화이트리스트 스키마 프롬프트 → {@link AiTextRouter}(Claude→Ollama) → SQL 추출 →
 * {@link NlQuerySqlGuard} 결정론 검증(단일 SELECT·화이트리스트·PII 차단·LIMIT 200) →
 * read-only 트랜잭션 실행 → 결과 + <b>생성 SQL 근거</b> 반환(투명성).
 *
 * <p><b>보안</b>: 프롬프트 스키마에서 PII 원문 컬럼(name/phone/email)을 아예 제외한다. 가드가 재차 차단한다(이중 방어).
 * AI 미가용/가드 위반은 예외를 던져 명확한 4xx(AI_UNAVAILABLE/VALIDATION)로 응답한다(환각·오작동 방지).
 */
@Service
public class NlQueryService {

    private static final Logger log = LoggerFactory.getLogger(NlQueryService.class);

    private final AiTextRouter router;
    private final NlQuerySqlGuard guard;
    private final NlQueryReadMapper readMapper;

    public NlQueryService(AiTextRouter router, NlQuerySqlGuard guard, NlQueryReadMapper readMapper) {
        this.router = router;
        this.guard = guard;
        this.readMapper = readMapper;
    }

    /** 화이트리스트 테이블 스키마(PII 원문 컬럼 제외) — 프롬프트에 주입되는 유일한 스키마 근거. */
    private static final String SCHEMA = """
            -- PostgreSQL. 아래 테이블/컬럼만 조회 가능(그 외 테이블·컬럼 금지). 개인정보(name/phone/email)는 존재하지 않는 것으로 간주하라.
            event(id, name, start_date, end_date, status)
            hall(id, exhibition_center, label, width_m, depth_m, height_m, area_m2, floor_load_t_per_m2, floor_type, booth_capacity, has_gas)
            booth(id, layout_id, booth_no, booth_type, size_w_m, size_d_m, height_m, floor_load_t_per_m2, assigned_company_name, premium)
            visitor_registration(id, event_id, company, visitor_type, agree_privacy, agree_marketing, badge_issued, checkin_state, registered_at)
            lead(id, event_id, booth_id, role, company, product, interest, score, ai_generated, collected_at)
            invoice(id, event_id, exhibitor_name, invoice_no, subtotal, vat, total, paid_amount, status, due_date, issued_at, paid_at)
            auction(id, event_id, title, category, auction_type, award_criteria, weight_price, weight_reputation, weight_delivery, round, deadline, materials, created_at)
            """;

    private String buildPrompt(String question) {
        return "당신은 킨텍스 전시운영 데이터베이스의 SQL 생성기다. 아래 스키마만 근거로 사용자의 질문에 답할 PostgreSQL SELECT 문 하나만 생성하라.\n\n"
                + SCHEMA + "\n"
                + "규칙(엄수):\n"
                + "1) 반드시 단일 SELECT 문. INSERT/UPDATE/DELETE/DDL/세미콜론/주석 금지.\n"
                + "2) 위 스키마의 테이블/컬럼만 사용. name/phone/email 등 개인정보 컬럼은 절대 사용하지 마라.\n"
                + "3) SELECT * 대신 필요한 컬럼만 명시하고, booth.geom 같은 공간 컬럼은 선택하지 마라.\n"
                + "4) 결과는 LIMIT 200 이하로 제한하라.\n"
                + "5) 스키마에 없는 정보는 지어내지 말고, 답할 수 없으면 가장 근접한 조회를 생성하라.\n"
                + "6) 출력은 SQL 문 하나만. 설명·마크다운·코드펜스 없이 SQL 텍스트만 반환하라.\n\n"
                + "질문: " + question;
    }

    /**
     * 자연어 질문 → 검증된 SELECT 실행. read-only 트랜잭션(쓰기 원천 차단).
     * @throws ApiException AI 미가용(AI_UNAVAILABLE·503) 또는 가드 위반(VALIDATION·400)
     */
    @Transactional(readOnly = true)
    public NlQueryResult ask(String question) {
        if (question == null || question.isBlank()) {
            throw new ApiException(ErrorCode.VALIDATION, "질문을 입력해 주세요.");
        }
        AiResult ai = router.generate(buildPrompt(question), 512);
        if (!ai.usable()) {
            throw new ApiException(ErrorCode.AI_UNAVAILABLE);
        }
        // 가드: 위반 시 VALIDATION(400) — 실행 전 차단.
        GuardedSql g = guard.validate(ai.text());

        List<Map<String, Object>> rows;
        try {
            rows = readMapper.runReadOnly(g.sql());
        } catch (org.springframework.dao.DataAccessException e) {
            // 실행 오류(존재하지 않는 컬럼·문법 등)는 스택트레이스 미노출, 요약만.
            log.warn("NL query execution failed: {}", e.getClass().getSimpleName());
            throw new ApiException(ErrorCode.VALIDATION, "생성된 조회를 실행할 수 없습니다. 질문을 더 구체적으로 입력해 주세요.");
        }
        if (rows == null) {
            rows = new ArrayList<>();
        }
        List<String> columns = rows.isEmpty() ? List.of() : new ArrayList<>(rows.get(0).keySet());
        return new NlQueryResult(question, g.sql(), g.tables(), columns, rows, rows.size(), ai.provider());
    }
}
