"""Phase 3.5 B1 (fix1+fix3): unit-aware fabricated_number + bound semantics.

기준:
- 단위 일치 시에만 exact/range/tolerance clear (fix1: Codex unit-mismatch regression 방지)
- 약/대략/거의/얼추 만 approx prefix strip; 최대/최소 는 bound operator 로 보존 (fix3)
- tolerance 는 양적 단위(_TOLERANCE_UNITS) + 4자리+ 만; 식별자성(_EXACT_ONLY_UNITS) 은 strict
"""

from __future__ import annotations

import os
import sys

# tests/ → 프로젝트 루트 → app/
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "app"))

import pytest

from services.search.evidence_service import EvidenceItem
from services.search.grounding_check import check


def _ev(text: str, n: int = 1) -> EvidenceItem:
    return EvidenceItem(
        n=n,
        chunk_id=None,
        doc_id=100 + n,
        title=f"doc{n}",
        section_title=None,
        span_text=text,
        relevance=0.9,
        rerank_score=0.85,
        full_snippet=text,
        source="llm",
    )


def _has_fabricated(result, sub: str | None = None) -> bool:
    for f in result.strong_flags:
        if not f.startswith("fabricated_number:"):
            continue
        if sub is None or sub in f:
            return True
    return False


# ─── 콤마/prefix/range/단위 동의어/citation (기존 17 케이스) ──────


def test_comma_thousand_match():
    r = check("질문", "총 1,000명 [1]", [_ev("총원은 1000명입니다.")])
    assert not _has_fabricated(r, "1000")


def test_comma_thousand_reverse():
    r = check("질문", "총 1000명 [1]", [_ev("총원은 1,000명입니다.")])
    assert not _has_fabricated(r)


def test_approx_prefix_in_answer():
    r = check("질문", "약 100명이 참여 [1]", [_ev("100명이 참여")])
    assert not _has_fabricated(r)


def test_approx_prefix_in_evidence():
    r = check("질문", "100명이 참여 [1]", [_ev("약 100명이 참여")])
    assert not _has_fabricated(r)


def test_range_inner_value_passes():
    r = check("질문", "약 150명 [1]", [_ev("100~200명 사이 추정")])
    assert not _has_fabricated(r, "150")


def test_range_outer_value_flagged():
    r = check("질문", "300명 [1]", [_ev("100~200명 사이 추정")])
    assert _has_fabricated(r, "300")


def test_unit_synonym_in_to_myeong():
    r = check("질문", "총 50인이 모임 [1]", [_ev("총 50명이 모임.")])
    assert not _has_fabricated(r)


def test_unit_synonym_percent_to_pct():
    r = check("질문", "비율 30퍼센트 [1]", [_ev("비율 30%이다.")])
    assert not _has_fabricated(r)


def test_citation_marker_both_sides():
    """bug fix: evidence 측 [n] 미제거로 디지트 합쳐지던 케이스."""
    r = check("질문", "가격 [1] 5,000원", [_ev("[2] 5,000원이 정확")])
    assert not _has_fabricated(r)


def test_genuine_fabricated_number():
    r = check("질문", "결과 777명 [1]", [_ev("500명, 300명을 받음.")])
    assert _has_fabricated(r, "777")


def test_amount_4digit_tolerance_passes():
    r = check("질문", "9,990원 [1]", [_ev("10,000원입니다.")])
    assert not _has_fabricated(r)


def test_year_no_tolerance_flagged():
    r = check("질문", "2024년 [1]", [_ev("2026년에 발효")])
    assert _has_fabricated(r, "2024")


def test_article_no_tolerance_flagged():
    r = check("질문", "제5조에 명시 [1]", [_ev("제6조에 따라")])
    assert _has_fabricated(r)


def test_count_no_tolerance_flagged():
    r = check("질문", "총 3회 위반 [1]", [_ev("총 4회 적발")])
    assert _has_fabricated(r)


def test_three_digit_strict():
    r = check("질문", "총 15개 [1]", [_ev("총 10개")])
    assert _has_fabricated(r, "15")


def test_single_digit_ignored():
    """1자리 + 양적 단위 → 무시 (오탐 방지)."""
    r = check("질문", "총 3개 발생 [1]", [_ev("관련 통계 별도")])
    assert not _has_fabricated(r, "3개")


def test_range_korean_butter_separator():
    r = check("질문", "약 150명 [1]", [_ev("100부터 200명까지 대상.")])
    assert not _has_fabricated(r, "150")


# ─── fix1: unit-mismatch (Codex no-ship) ──────────────────


def test_won_vs_myeong_range_flagged():
    """answer '150원' vs evidence '100~200명' → 단위 불일치, flag 유지."""
    r = check("질문", "약 150원이 든다 [1]", [_ev("대상은 100~200명")])
    assert _has_fabricated(r, "150")


def test_won_vs_myeong_tolerance_flagged():
    """answer '9,990원' vs evidence '10,000명' → tolerance pool 단위 다름, flag 유지."""
    r = check("질문", "9,990원 [1]", [_ev("10,000명입니다.")])
    assert _has_fabricated(r, "9990")


def test_pct_vs_myeong_range_flagged():
    """answer '15%' vs evidence '10~20명' → 단위 불일치, flag 유지."""
    r = check("질문", "약 15% [1]", [_ev("대상 10~20명")])
    assert _has_fabricated(r, "15")


# ─── fix3: 최대/최소 bound semantics ───────────────────────


def test_choedae_exact_boundary_flagged():
    """evidence '최대 100명' + answer '100명' → 경계값 자체는 cleared 아님."""
    r = check("질문", "100명이다 [1]", [_ev("최대 100명까지 가능")])
    assert _has_fabricated(r, "100")


def test_choeso_exact_boundary_flagged():
    """evidence '최소 100명' + answer '100명' → 경계값 자체는 cleared 아님."""
    r = check("질문", "100명이다 [1]", [_ev("최소 100명 이상 필요")])
    assert _has_fabricated(r, "100")


def test_choedae_inner_value_passes():
    """evidence '최대 100명' + answer '50명' → bound 안, cleared."""
    r = check("질문", "50명이다 [1]", [_ev("최대 100명까지 가능")])
    assert not _has_fabricated(r, "50")


def test_choeso_above_value_passes():
    """evidence '최소 100명' + answer '150명' → bound 안, cleared."""
    r = check("질문", "150명이다 [1]", [_ev("최소 100명 이상 필요")])
    assert not _has_fabricated(r, "150")


def test_choedae_outer_value_flagged():
    """evidence '최대 100명' + answer '200명' → bound 밖, flag."""
    r = check("질문", "200명이다 [1]", [_ev("최대 100명까지 가능")])
    assert _has_fabricated(r, "200")
