"""Phase B Verifier MVP — 단위 테스트 (최소 10케이스)."""

from __future__ import annotations
import json, pytest
from verifier import validators


# ── 헬퍼 ──

def _noop_conn():
    return None  # _load_tag_set 내부에서 호출되지 않도록 monkeypatch로 대체


# ═══════════════════════════════════════════════════════════════════
# R1 — tag-existence
# ═══════════════════════════════════════════════════════════════════

def test_R1_valid_tag_format_pass(monkeypatch):
    """PASS: ficq-6113.pv → 올바른 형식 + DB 존재."""
    monkeypatch.setattr(validators, "_load_tag_set", lambda gc: {"ficq-6113", "p-6102"})
    assert validators.validate_tag("ficq-6113.pv", _noop_conn) is None

def test_R1_valid_tag_no_suffix_pass(monkeypatch):
    """PASS: p-6102 → suffix 없어도 OK."""
    monkeypatch.setattr(validators, "_load_tag_set", lambda gc: {"ficq-6113", "p-6102"})
    assert validators.validate_tag("p-6102", _noop_conn) is None

def test_R1_tags_dot_suffix_pass(monkeypatch):
    """PASS: vp-6117a.pv → 문자 포함 태그 번호."""
    monkeypatch.setattr(validators, "_load_tag_set", lambda gc: {"vp-6117a", "vp-6117b"})
    assert validators.validate_tag("vp-6117a.pv", _noop_conn) is None

def test_R1_invalid_tag_format_fail():
    """FAIL: raw_material_input → 형식 위반 (대문자 + 밑줄)."""
    err = validators.validate_tag("raw_material_input", _noop_conn)
    assert err is not None
    assert err.code == "invalid_tag_format"
    assert "R1" in err.rule

def test_R1_unknown_tag_fail(monkeypatch):
    """FAIL: RM-6101 → 형식은 맞지만 DB에 없음."""
    monkeypatch.setattr(validators, "_load_tag_set", lambda gc: {"ficq-6113", "p-6102"})
    err = validators.validate_tag("RM-6101", _noop_conn)
    assert err is not None
    assert err.code == "tag_not_found"
    assert "suggested" in err.extra

def test_R1_none_or_empty_pass():
    """PASS: None/빈 문자열 → 검증 스킵."""
    assert validators.validate_tag(None, _noop_conn) is None
    assert validators.validate_tag("", _noop_conn) is None


# ═══════════════════════════════════════════════════════════════════
# R2 — area-format
# ═══════════════════════════════════════════════════════════════════

def test_R2_valid_area_pass():
    """PASS: P6, P6-1, P10, UTIL."""
    for a in ("P6", "P6-1", "P10", "UTIL", "PACKING"):
        assert validators.validate_area(a) is None, f"area={a} expected PASS"

def test_R2_invalid_area_format_fail():
    """FAIL: 6-1 → P prefix 없음."""
    err = validators.validate_area("6-1")
    assert err is not None
    assert err.code == "invalid_area_format"

def test_R2_unknown_area_fail():
    """FAIL: P7 → 존재하지 않는 area."""
    err = validators.validate_area("P7")
    assert err is not None
    assert err.code == "unknown_area"

def test_R2_empty_or_none_pass():
    """PASS: None/빈 문자열 → 선택 필터."""
    assert validators.validate_area(None) is None
    assert validators.validate_area("") is None

def test_R2_sub_area_pass():
    """PASS: P6-2 → 형식+base 모두 유효."""
    assert validators.validate_area("P6-2") is None


# ═══════════════════════════════════════════════════════════════════
# R4 — trace_connections 보강
# ═══════════════════════════════════════════════════════════════════

def test_R4_valid_direction_pass():
    """PASS: upstream, downstream."""
    assert validators.validate_direction("upstream") is None
    assert validators.validate_direction("downstream") is None
    assert validators.validate_direction(None) is None

def test_R4_invalid_direction_fail():
    """FAIL: sideways → 허용되지 않은 방향."""
    err = validators.validate_direction("sideways")
    assert err is not None
    assert err.code == "invalid_direction"

def test_R4_valid_max_depth_pass():
    """PASS: 1, 50, 20."""
    for n in (1, 50, 20, None):
        assert validators.validate_max_depth(n) is None, f"max_depth={n} expected PASS"

def test_R4_invalid_max_depth_out_of_range():
    """FAIL: 0, 51 → 범위 초과."""
    err0 = validators.validate_max_depth(0)
    assert err0 is not None and err0.code == "max_depth_out_of_range"
    err51 = validators.validate_max_depth(51)
    assert err51 is not None and err51.code == "max_depth_out_of_range"

def test_R4_invalid_max_depth_not_int():
    """FAIL: 'abc' → 정수 아님."""
    err = validators.validate_max_depth("abc")
    assert err is not None and err.code == "invalid_max_depth"


# ═══════════════════════════════════════════════════════════════════
# R3 / R5 stub
# ═══════════════════════════════════════════════════════════════════

def test_R3_stub_returns_none():
    """Phase B.2 전까지는 항상 None."""
    assert validators.validate_response_text("anything") is None


# ═══════════════════════════════════════════════════════════════════
# VerifierError 직렬화
# ═══════════════════════════════════════════════════════════════════

def test_verifier_error_to_dict():
    err = validators.VerifierError("R1", "tag_not_found", "hint msg", suggested=["a", "b"])
    d = err.to_dict()
    assert d["verifier_error"] == "R1.tag_not_found"
    assert d["hint"] == "hint msg"
    assert d["suggested"] == ["a", "b"]


# ═══════════════════════════════════════════════════════════════════
# log_rejection — 파일 적재
# ═══════════════════════════════════════════════════════════════════

def test_log_rejection_writes_jsonl(tmp_path, monkeypatch):
    monkeypatch.setattr(validators, "_LOG_DIR", tmp_path)
    err = validators.VerifierError("R2", "invalid_area_format", "bad area", valid_areas=["P6"])
    validators.log_rejection("find_tags", {"area": "6-1"}, err)
    files = list(tmp_path.iterdir())
    assert len(files) == 1
    content = files[0].read_text(encoding="utf-8")
    rec = json.loads(content)
    assert rec["tool"] == "find_tags"
    assert rec["params"] == {"area": "6-1"}
    assert rec["verifier_error"]["verifier_error"] == "R2.invalid_area_format"
