"""Phase T-2: Stage 1A/1B 검증 시스템.

Stage 2 이후 검증(content_verifier.py)과 분리된 독립 모듈.
AI(Kei)의 콘텐츠 분석 결과를 원본과 대조하여 검증.

검증 4계층:
  1. 형식 검증 (Pydantic) — 값 범위, 유효 enum, null 체크
  2. 내용 검증 (코드+대조) — 결과가 원본에 대해 적절한가
  3. 모순 탐지 (결정 테이블) — purpose × relation_type 논리 모순
  4. 피드백 생성 — Self-Refine 패턴: localization + evidence + instruction

도구:
  - kiwipiepy: 한국어 명사/키워드 추출 (T-2 조사: Windows 즉시 동작, Java 불필요)
  - regex: 관계 표현 패턴 (T-2 조사: 7개 relation_type별 15개+ 패턴)
"""
from __future__ import annotations

import re
import logging
from typing import Any

logger = logging.getLogger(__name__)

# kiwipiepy lazy loading (첫 import 시 ~50MB 모델 다운로드)
_kiwi = None

def _get_kiwi():
    global _kiwi
    if _kiwi is None:
        from kiwipiepy import Kiwi
        _kiwi = Kiwi()
    return _kiwi


# ══════════════════════════════════════
# 한국어 키워드 추출 (kiwipiepy)
# ══════════════════════════════════════

def extract_keywords_kiwi(text: str) -> set[str]:
    """kiwipiepy로 명사 + 영문 약어 추출.

    기존 content_verifier.py의 regex extract_keywords()보다 정확:
    - "정립되지" → "정립" 추출 가능 (어미 분리)
    - "혼용되어" → "혼용" 추출 가능
    - 복합 조사 "에서는" 등 정확 분리
    """
    kiwi = _get_kiwi()
    tokens = kiwi.tokenize(text)
    keywords = set()
    for t in tokens:
        # NNG: 일반명사, NNP: 고유명사, SL: 외국어(영문약어)
        if t.tag in ("NNG", "NNP", "SL") and len(t.form) >= 2:
            keywords.add(t.form)
    return keywords


# ══════════════════════════════════════
# 관계 표현 패턴 (T-2 조사 결과)
# ══════════════════════════════════════

RELATION_PATTERNS: dict[str, list[str]] = {
    "comparison": [
        r"[Vv][Ss]\.?", r"에\s*비해", r"반면", r"차이점?", r"비교",
        r"대비", r"와\s*달리", r"과\s*달리", r"한편", r"에\s*반하여",
        r"그에\s*비해", r"상이", r"구분", r"차별화",
    ],
    "sequence": [
        r"→", r"이후", r"다음", r"먼저", r"그\s*후", r"단계",
        r"순서", r"[0-9]+차", r"최종적", r"한\s*뒤", r"우선",
        r"이어서", r"점진적", r"과정", r"를\s*거쳐",
    ],
    "hierarchy": [
        r"상위", r"하위", r"속하", r"의\s*일부", r"범주",
        r"구성요소", r"체계", r"분류", r"계층", r"로\s*나뉜?다",
        r"종속", r"수직적", r"상하\s*관계", r"아우르", r"광의|협의",
    ],
    "inclusion": [
        r"포함", r"융합", r"통합", r"안에", r"속에", r"결합",
        r"합쳐", r"아우르", r"망라", r"수렴", r"내포", r"포괄",
        r"연계", r"접목", r"겹치|중복",
    ],
    "cause_effect": [
        r"때문에", r"따라서", r"결과", r"원인", r"로\s*인해",
        r"하여", r"해서", r"초래", r"야기", r"기인",
        r"영향", r"유발", r"한\s*결과", r"므로",
        r"그래서", r"그러므로", r"에\s*의해",
    ],
    "definition": [
        r"이란", r"정의", r"의미", r"개념",
        r"을\s*말한다", r"을\s*뜻한다", r"로\s*정의", r"가리킨다",
        r"라\s*함은", r"라고\s*한다", r"줄임말|약어|약자",
        r"에\s*해당", r"일컫", r"용어",
    ],
}


def detect_relation_evidence(text: str) -> dict[str, int]:
    """원본 텍스트에서 각 relation_type의 증거 수를 카운트."""
    evidence = {}
    for rel_type, patterns in RELATION_PATTERNS.items():
        count = sum(1 for p in patterns if re.search(p, text))
        evidence[rel_type] = count
    return evidence


# ══════════════════════════════════════
# 모순 결정 테이블 (데이터로 정의)
# ══════════════════════════════════════

# purpose × relation_type 하드 모순 (이 조합은 논리적으로 불가능)
CONTRADICTIONS: dict[str, list[str]] = {
    "결론강조": ["comparison", "sequence"],       # 결론은 비교나 순서가 아님
    "문제제기": ["sequence", "definition"],        # 문제제기는 순서 나열이나 정의가 아님
    "용어정의": ["hierarchy", "cause_effect"],     # 정의 나열은 상하위나 인과가 아님
    "구조시각화": ["none"],                        # 시각화할 관계가 없으면 구조시각화가 아님
}

# 소프트 경고 (의심 수준)
SOFT_WARNINGS: dict[str, list[str]] = {
    "핵심전달": ["definition"],     # 핵심전달에 definition은 약간 의심
}


# ══════════════════════════════════════
# Stage 1A 검증
# ══════════════════════════════════════

VALID_PURPOSES = {"문제제기", "근거사례", "핵심전달", "용어정의", "결론강조", "구조시각화"}
VALID_ROLES = {"flow", "reference"}
VALID_LAYERS = {"intro", "core", "supporting", "conclusion"}


def validate_stage_1a(
    analysis: dict[str, Any],
    clean_text: str,
) -> list[dict]:
    """Stage 1A(Kei 꼭지 추출) 결과 검증.

    Args:
        analysis: Kei API 반환 dict
        clean_text: Stage 0에서 정규화된 텍스트

    Returns:
        에러 리스트 (빈 리스트 = 통과)
    """
    errors = []
    topics = analysis.get("topics", [])
    page_struct = analysis.get("page_structure", {})

    # ── 형식 검증 ──

    if not topics:
        errors.append({
            "severity": "FATAL",
            "field": "topics",
            "localization": "topics가 비어있음",
            "instruction": "콘텐츠에서 최소 1개 꼭지를 추출하라",
        })
        return errors

    # weight 합 검증 (0.9~1.1)
    total_weight = sum(
        info.get("weight", 0) for info in page_struct.values()
        if isinstance(info, dict)
    )
    if total_weight < 0.9 or total_weight > 1.1:
        errors.append({
            "severity": "RETRYABLE",
            "field": "page_structure.weight",
            "localization": f"weight 합 {total_weight:.2f} (범위: 0.9~1.1)",
            "instruction": f"weight 합이 1.0에 가깝도록 조정하라. 현재 합: {total_weight:.2f}",
        })

    # 유형에 따른 구조 검증
    layout_template = analysis.get("layout_template", "A")
    if layout_template == "A":
        # 유형 A: 본심 필수
        core_info = page_struct.get("본심", {})
        if not core_info or not isinstance(core_info, dict):
            errors.append({
                "severity": "RETRYABLE",
                "field": "page_structure.본심",
                "localization": "본심 역할이 page_structure에 없음",
                "instruction": "page_structure에 본심 역할을 추가하라. 본심은 슬라이드의 핵심 콘텐츠이다.",
            })
        elif core_info.get("weight", 0) < 0.3:
            errors.append({
                "severity": "RETRYABLE",
                "field": "page_structure.본심.weight",
                "localization": f"본심 weight {core_info['weight']:.2f} < 0.3",
                "instruction": "본심은 슬라이드의 핵심. weight 0.3 이상 필요.",
            })
    elif layout_template == "B":
        # 유형 B: 결론(footer) 필수, 나머지 자유
        has_footer = any(
            isinstance(info, dict) and info.get("zone") == "footer"
            for info in page_struct.values()
        )
        if not has_footer and "결론" not in page_struct:
            errors.append({
                "severity": "RETRYABLE",
                "field": "page_structure.footer",
                "localization": "결론(footer) 역할이 없음",
                "instruction": "유형 B에서도 결론 역할(zone: footer)은 필수이다.",
            })

    # 필수 필드 검증
    for t in topics:
        tid = t.get("id", "?")
        if not t.get("title"):
            errors.append({
                "severity": "RETRYABLE",
                "field": f"topics[{tid}].title",
                "localization": f"topic {tid}에 title 없음",
                "instruction": "각 topic에 title을 부여하라",
            })
        if t.get("purpose") and t["purpose"] not in VALID_PURPOSES:
            errors.append({
                "severity": "RETRYABLE",
                "field": f"topics[{tid}].purpose",
                "localization": f"topic {tid} purpose '{t['purpose']}' 유효하지 않음",
                "current_value": t["purpose"],
                "instruction": f"유효한 purpose: {VALID_PURPOSES}",
            })

    # page_structure의 topic_ids가 실제 topics에 존재하는지
    all_topic_ids = {t.get("id") for t in topics}
    for role, info in page_struct.items():
        if not isinstance(info, dict):
            continue
        for tid in info.get("topic_ids", []):
            if tid not in all_topic_ids:
                errors.append({
                    "severity": "RETRYABLE",
                    "field": f"page_structure.{role}.topic_ids",
                    "localization": f"{role}에 존재하지 않는 topic_id {tid}",
                    "instruction": f"topic_ids는 topics[].id에 존재하는 값만 사용하라. 현재 topics: {sorted(all_topic_ids)}",
                })

    # ── 내용 검증 (원본 대조) ──

    if clean_text:
        # 원본 ## 섹션 수 vs topic 수 비교
        original_sections = re.findall(r"^## .+$", clean_text, re.MULTILINE)
        # 유형 B에서는 하나의 섹션을 여러 꼭지로 나눌 수 있으므로 허용 폭 확대
        max_diff = 4 if layout_template == "B" else 2
        if len(original_sections) > 0 and abs(len(topics) - len(original_sections)) > max_diff:
            errors.append({
                "severity": "RETRYABLE",
                "field": "topics",
                "localization": f"원본 ## 섹션 {len(original_sections)}개, topic {len(topics)}개 (차이 {abs(len(topics) - len(original_sections))})",
                "evidence": f"원본 섹션: {[s[3:].strip()[:30] for s in original_sections]}",
                "instruction": "원본의 주요 섹션이 topic에 매핑되었는지 확인하라",
            })

        # topic summary 키워드가 원본에 존재하는지 (kiwipiepy)
        try:
            orig_keywords = extract_keywords_kiwi(clean_text)
            for t in topics:
                summary = t.get("summary", "")
                if not summary:
                    continue
                summary_kw = extract_keywords_kiwi(summary)
                if not summary_kw:
                    continue
                overlap = summary_kw & orig_keywords
                rate = len(overlap) / len(summary_kw) if summary_kw else 1.0
                if rate < 0.5:
                    missing = summary_kw - orig_keywords
                    errors.append({
                        "severity": "RETRYABLE",
                        "field": f"topics[{t.get('id', '?')}].summary",
                        "localization": f"summary 키워드 보존율 {rate:.0%}",
                        "evidence": f"원본에 없는 키워드: {missing}",
                        "instruction": f"summary에 원본에 없는 표현을 추가하지 마라. 원본 키워드로 수정하라.",
                    })
        except Exception as e:
            logger.warning(f"[T-2] kiwipiepy 키워드 검증 실패: {e}")

    return errors


# ══════════════════════════════════════
# Stage 1B 검증
# ══════════════════════════════════════

VALID_RELATION_TYPES = {"hierarchy", "cause_effect", "comparison", "sequence", "definition", "inclusion", "none"}


def validate_stage_1b(
    topics: list[dict[str, Any]],
    clean_text: str,
    raw_content: str = "",
    layout_template: str = "A",
) -> list[dict]:
    """Stage 1B(컨셉 구체화) 결과 검증.

    Args:
        topics: Stage 1B 후 업데이트된 topics 리스트
        clean_text: Stage 0에서 정규화된 텍스트
        raw_content: 원본 MDX 전체 (popups/details 포함). 대조 범위 확장용.

    Returns:
        에러 리스트 (빈 리스트 = 통과)
    """
    # 대조 범위: clean_text + raw_content (popups/details 내용 포함)
    full_text = clean_text
    if raw_content:
        full_text = clean_text + "\n" + raw_content
    errors = []

    for t in topics:
        tid = t.get("id", "?")
        purpose = t.get("purpose", "")
        relation_type = t.get("relation_type", "")
        expression_hint = t.get("expression_hint", "")
        source_data = t.get("source_data", "")

        # ── 형식 검증 ──

        if relation_type not in VALID_RELATION_TYPES:
            errors.append({
                "severity": "RETRYABLE",
                "field": f"topics[{tid}].relation_type",
                "localization": f"topic {tid}: 유효하지 않은 relation_type '{relation_type}'",
                "current_value": relation_type,
                "instruction": f"유효한 relation_type: {sorted(VALID_RELATION_TYPES)}",
            })

        if not expression_hint:
            errors.append({
                "severity": "RETRYABLE",
                "field": f"topics[{tid}].expression_hint",
                "localization": f"topic {tid}: expression_hint 비어있음",
                "instruction": "expression_hint를 작성하라. 형식: 관계 선언 + 콘텐츠 설명 + 시각 지침",
            })

        # ── 모순 탐지 (결정 테이블) ──

        if purpose in CONTRADICTIONS:
            if relation_type in CONTRADICTIONS[purpose]:
                errors.append({
                    "severity": "RETRYABLE",
                    "field": f"topics[{tid}].relation_type",
                    "localization": f"topic {tid}: purpose '{purpose}' × relation_type '{relation_type}' 모순",
                    "current_value": f"purpose={purpose}, relation_type={relation_type}",
                    "evidence": f"'{purpose}'는 '{relation_type}'와 논리적으로 양립 불가",
                    "instruction": f"relation_type을 재판단하라. '{purpose}'에 적합한 관계는 "
                                   f"{[r for r in VALID_RELATION_TYPES if r not in CONTRADICTIONS.get(purpose, [])]}",
                })

        if purpose in SOFT_WARNINGS:
            if relation_type in SOFT_WARNINGS[purpose]:
                logger.warning(
                    f"[T-2 경고] topic {tid}: purpose '{purpose}' × relation_type '{relation_type}' 의심"
                )

        # ── 원본 대조: source_data 할루시네이션 감지 ──
        # full_text 사용 (popups/details 내용 포함)

        if source_data and full_text:
            try:
                source_kw = extract_keywords_kiwi(source_data)
                orig_kw = extract_keywords_kiwi(full_text)
                if source_kw:
                    overlap = source_kw & orig_kw
                    rate = len(overlap) / len(source_kw)
                    if rate < 0.4:
                        missing = source_kw - orig_kw
                        errors.append({
                            "severity": "RETRYABLE",
                            "field": f"topics[{tid}].source_data",
                            "localization": f"topic {tid}: source_data 키워드 보존율 {rate:.0%}",
                            "evidence": f"원본에 없는 키워드: {missing}",
                            "instruction": "source_data는 원본에 실제 존재하는 텍스트만 사용하라. 없는 출처를 만들어내지 마라.",
                        })
            except Exception as e:
                logger.warning(f"[T-2] source_data 검증 실패: {e}")

        # ── 원본 대조: relation_type과 원본 언어 패턴 ──
        # full_text 사용 (popups/details 내용 포함)

        if relation_type and relation_type != "none" and full_text:
            evidence = detect_relation_evidence(full_text)
            claimed_count = evidence.get(relation_type, 0)

            if claimed_count == 0:
                if layout_template == "B":
                    # 유형 B: relation_type 증거 부족은 warning만 (역할 구조가 자유)
                    logger.warning(f"[Stage 1B] topic {tid}: '{relation_type}' 증거 0개 — 유형 B warning")
                else:
                    alternatives = [(k, v) for k, v in evidence.items() if v >= 2]
                    alt_str = ", ".join(f"{k}({v}개)" for k, v in alternatives[:3])
                    errors.append({
                        "severity": "RETRYABLE",
                        "field": f"topics[{tid}].relation_type",
                        "localization": f"topic {tid}: '{relation_type}' 증거 0개",
                        "evidence": f"원본에서 '{relation_type}' 패턴 없음. 대안: {alt_str}" if alt_str else f"원본에서 '{relation_type}' 패턴 없음",
                        "instruction": f"원본 텍스트에 '{relation_type}' 관계를 나타내는 표현이 없음. 재판단하라.",
                    })

    return errors
