"""Phase 3 evidence gate regression tests — T1~T16 + TU1~TU3.

모든 외부 GitHub API 호출은 mock_gh_api fixture로 stub.
실제 gh api / GEMINI_API_KEY / network 호출 0건.
"""
from __future__ import annotations

import sys
from datetime import datetime, timezone, timedelta

import pytest

# conftest.py에서 가져오는 helper (pytest 런타임에 sys.path 확보됨)
from tests.phase3_evidence_gate.conftest import (  # type: ignore[reportMissingImports]
    _iso,
    make_review,
    make_issue_comment,
    SCRIPT_DIR,
)


# ─────────────────────────────────────────────────────────────────────────────
# T1: review 1건 + SHA 일치 + severity 없음 → PASS
# ─────────────────────────────────────────────────────────────────────────────

def test_t1_valid_review_no_severity(evidence_module, mock_gh_api):
    """T1: review 1건 + SHA 일치 + severity 없음 → PASS."""
    head_sha = "abc123"
    now = datetime.now(timezone.utc)
    mock_gh_api(
        reviews=[make_review("LGTM, looks good!", head_sha)],
        head_sha_date=_iso(now - timedelta(seconds=10)),
    )
    result = evidence_module.evaluate_gate(1, head_sha, "owner/repo")
    assert result["state"] == "pass", result
    assert "valid evidence" in result["reason"]


# ─────────────────────────────────────────────────────────────────────────────
# T2: review 0건 + 마지막 push 후 1분 경과 → HOLD
# ─────────────────────────────────────────────────────────────────────────────

def test_t2_no_evidence_within_timeout(evidence_module, mock_gh_api):
    """T2: review 0건 + 마지막 push 후 1분 경과 → HOLD."""
    now = datetime.now(timezone.utc)
    mock_gh_api(head_sha_date=_iso(now - timedelta(seconds=60)))
    result = evidence_module.evaluate_gate(1, "abc123", "owner/repo")
    assert result["state"] == "hold"
    assert result["elapsed_seconds"] < 300


# ─────────────────────────────────────────────────────────────────────────────
# T3: review 0건 + 마지막 push 후 6분 경과 → BLOCK
# ─────────────────────────────────────────────────────────────────────────────

def test_t3_no_evidence_timeout_exceeded(evidence_module, mock_gh_api):
    """T3: review 0건 + 마지막 push 후 6분 경과 → BLOCK."""
    now = datetime.now(timezone.utc)
    mock_gh_api(head_sha_date=_iso(now - timedelta(seconds=360)))
    result = evidence_module.evaluate_gate(1, "abc123", "owner/repo")
    assert result["state"] == "block"
    assert "timeout" in result["reason"].lower()


# ─────────────────────────────────────────────────────────────────────────────
# T4: review 1건 + SHA 불일치 (force-push 후 stale) → BLOCK (all_stale)
# ─────────────────────────────────────────────────────────────────────────────

def test_t4_stale_review_all_stale_block(evidence_module, mock_gh_api):
    """T4: review 1건 + SHA 불일치 (force-push 후 stale) → BLOCK (all_stale)."""
    old_sha = "oldsha000"
    new_sha = "newsha999"
    now = datetime.now(timezone.utc)
    # 새 SHA 기준 push 직후 (elapsed < timeout), 하지만 review는 구 SHA
    mock_gh_api(
        reviews=[make_review("Looks fine.", old_sha)],
        head_sha_date=_iso(now - timedelta(seconds=10)),
    )
    result = evidence_module.evaluate_gate(1, new_sha, "owner/repo")
    assert result["state"] == "block", result
    assert "stale" in result["reason"].lower()
    # primary evidence가 모두 stale 판정
    primary = result["evidence"]["primary"]
    assert len(primary) == 1
    assert primary[0]["stale"] is True


# ─────────────────────────────────────────────────────────────────────────────
# T5: review 1건 + body에 🔴 이모지 → BLOCK
# ─────────────────────────────────────────────────────────────────────────────

def test_t5_high_severity_emoji_red(evidence_module, mock_gh_api):
    """T5: review 1건 + body에 🔴 이모지 → BLOCK."""
    head_sha = "abc123"
    now = datetime.now(timezone.utc)
    mock_gh_api(
        reviews=[make_review("🔴 SQL injection in line 42", head_sha)],
        head_sha_date=_iso(now - timedelta(seconds=10)),
    )
    result = evidence_module.evaluate_gate(1, head_sha, "owner/repo")
    assert result["state"] == "block"
    assert any("emoji" in h for h in result["evidence"]["high_severity_hits"])


# ─────────────────────────────────────────────────────────────────────────────
# T6: review 1건 + body에 `severity: high` → BLOCK
# ─────────────────────────────────────────────────────────────────────────────

def test_t6_high_severity_keyword_severity_high(evidence_module, mock_gh_api):
    """T6: review 1건 + body에 'severity: high' → BLOCK."""
    head_sha = "abc123"
    now = datetime.now(timezone.utc)
    mock_gh_api(
        reviews=[make_review("severity: high — this PR introduces a critical flaw.", head_sha)],
        head_sha_date=_iso(now - timedelta(seconds=10)),
    )
    result = evidence_module.evaluate_gate(1, head_sha, "owner/repo")
    assert result["state"] == "block"
    hits = result["evidence"]["high_severity_hits"]
    assert any("severity" in h for h in hits), f"expected severity hit, got: {hits}"


# ─────────────────────────────────────────────────────────────────────────────
# T7: review 1건 + body에 `BLOCKING` (대문자) → BLOCK
# ─────────────────────────────────────────────────────────────────────────────

def test_t7_high_severity_keyword_blocking_uppercase(evidence_module, mock_gh_api):
    """T7: review 1건 + body에 'BLOCKING' (대문자) → BLOCK."""
    head_sha = "abc123"
    now = datetime.now(timezone.utc)
    mock_gh_api(
        reviews=[make_review("BLOCKING: auth bypass vulnerability found.", head_sha)],
        head_sha_date=_iso(now - timedelta(seconds=10)),
    )
    result = evidence_module.evaluate_gate(1, head_sha, "owner/repo")
    assert result["state"] == "block"
    hits = result["evidence"]["high_severity_hits"]
    assert any("BLOCKING" in h or "keyword" in h for h in hits), f"expected keyword hit, got: {hits}"


# ─────────────────────────────────────────────────────────────────────────────
# T8: review 1건 + body에 `## High` h2 헤더 → BLOCK
# ─────────────────────────────────────────────────────────────────────────────

def test_t8_high_severity_header_h2_high(evidence_module, mock_gh_api):
    """T8: review 1건 + body에 '## High' h2 헤더 → BLOCK."""
    head_sha = "abc123"
    now = datetime.now(timezone.utc)
    body = "Code review results:\n\n## High\n- SQL injection risk at line 10"
    mock_gh_api(
        reviews=[make_review(body, head_sha)],
        head_sha_date=_iso(now - timedelta(seconds=10)),
    )
    result = evidence_module.evaluate_gate(1, head_sha, "owner/repo")
    assert result["state"] == "block"
    hits = result["evidence"]["high_severity_hits"]
    assert any("header" in h for h in hits), f"expected header hit, got: {hits}"


# ─────────────────────────────────────────────────────────────────────────────
# T9: review 1건 + body에 `security` 단독 (보조 신호만) → PASS (audit 기록)
# ─────────────────────────────────────────────────────────────────────────────

def test_t9_supplementary_security_no_block(evidence_module, mock_gh_api):
    """T9: review + 'security' 단독 보조 신호만 → PASS, audit 기록."""
    head_sha = "abc123"
    now = datetime.now(timezone.utc)
    mock_gh_api(
        reviews=[make_review("This change has security implications.", head_sha)],
        head_sha_date=_iso(now - timedelta(seconds=10)),
    )
    result = evidence_module.evaluate_gate(1, head_sha, "owner/repo")
    assert result["state"] == "pass"
    assert "security" in result["evidence"]["supplementary_signals"]
    assert result["evidence"]["high_severity_hits"] == []


# ─────────────────────────────────────────────────────────────────────────────
# T10: review 1건 + code suggestion 블록 안에 `BLOCKING` → PASS (code block 매칭 제외)
# ─────────────────────────────────────────────────────────────────────────────

def test_t10_code_block_excluded(evidence_module, mock_gh_api):
    """T10: code block 안 BLOCKING은 차단 패턴에서 제외."""
    head_sha = "abc123"
    now = datetime.now(timezone.utc)
    body = "Looks good!\n```python\n# BLOCKING comment in code\nprint('hello')\n```"
    mock_gh_api(
        reviews=[make_review(body, head_sha)],
        head_sha_date=_iso(now - timedelta(seconds=10)),
    )
    result = evidence_module.evaluate_gate(1, head_sha, "owner/repo")
    assert result["state"] == "pass"
    assert result["evidence"]["high_severity_hits"] == []


# ─────────────────────────────────────────────────────────────────────────────
# T11: comment만 1건 (review 0건) + severity 없음 → PASS
# ─────────────────────────────────────────────────────────────────────────────

def test_t11_issue_comment_only_no_severity_pass(evidence_module, mock_gh_api):
    """T11: issue_comment만 1건 (review 0건) + severity 없음 → PASS.

    issue_comment는 created_at >= head_pushed_at 일 때만 valid primary evidence.
    """
    now = datetime.now(timezone.utc)
    head_pushed_at = now - timedelta(seconds=30)
    comment_created_at = now - timedelta(seconds=5)  # head 이후 생성
    mock_gh_api(
        reviews=[],
        issue_comments=[make_issue_comment(
            "Gemini review complete. No issues found.",
            created_at=_iso(comment_created_at),
        )],
        head_sha_date=_iso(head_pushed_at),
    )
    result = evidence_module.evaluate_gate(1, "abc123", "owner/repo")
    assert result["state"] == "pass", result
    assert "valid evidence" in result["reason"]
    primary = result["evidence"]["primary"]
    assert len(primary) == 1
    assert primary[0]["type"] == "issue_comment"
    assert primary[0]["stale"] is False


def test_t11b_issue_comment_before_head_push_is_stale(evidence_module, mock_gh_api):
    """T11b: issue_comment의 created_at이 head push 이전이면 stale → BLOCK (all_stale).

    force-push 시나리오: 새 SHA가 push된 후 옛 issue comment만 남아있는 상황.
    """
    now = datetime.now(timezone.utc)
    head_pushed_at = now - timedelta(seconds=30)  # 최근 force-push
    old_comment_created_at = now - timedelta(seconds=600)  # 10분 전 (head 이전)
    mock_gh_api(
        reviews=[],
        issue_comments=[make_issue_comment(
            "LGTM",
            created_at=_iso(old_comment_created_at),
        )],
        head_sha_date=_iso(head_pushed_at),
    )
    result = evidence_module.evaluate_gate(1, "newsha456", "owner/repo")
    assert result["state"] == "block", result
    assert "stale" in result["reason"].lower()
    assert result["evidence"]["primary"][0]["stale"] is True


def test_t11c_fetch_check_runs_strict_app_slug(evidence_module, monkeypatch):
    """T11c: _fetch_check_runs는 app.slug == 'gemini-code-assist' 정확 매칭만 인정.

    이름이 'gemini-something'으로 시작해도 app.slug 다르면 필터링됨.
    """
    fake_response = {
        "check_runs": [
            {"id": 1, "name": "gemini-foo", "app": {"slug": "other-bot"}},  # 차단
            {"id": 2, "name": "gemini-code-assist", "app": {"slug": "gemini-code-assist"}},  # 인정
            {"id": 3, "name": "Gemini Manual Check", "app": {"slug": "manual-runner"}},  # 차단
        ]
    }
    # _gh_api stub
    monkeypatch.setattr(evidence_module, "_gh_api", lambda _endpoint, **_k: (0, fake_response))
    result = evidence_module._fetch_check_runs("owner/repo", "abc123")
    assert len(result) == 1, f"Only gemini-code-assist app should pass: got {result}"
    assert result[0]["id"] == 2


# ─────────────────────────────────────────────────────────────────────────────
# T12: GEMINI_API_KEY 환경변수 누락 상태에서도 정상 동작
# ─────────────────────────────────────────────────────────────────────────────

def test_t12_no_gemini_api_key_required(evidence_module, mock_gh_api, monkeypatch):
    """T12: GEMINI_API_KEY 누락 상태에서도 정상 동작 (의존 0)."""
    monkeypatch.delenv("GEMINI_API_KEY", raising=False)
    head_sha = "abc123"
    now = datetime.now(timezone.utc)
    mock_gh_api(
        reviews=[make_review("LGTM", head_sha)],
        head_sha_date=_iso(now - timedelta(seconds=10)),
    )
    result = evidence_module.evaluate_gate(1, head_sha, "owner/repo")
    assert result["state"] == "pass"


# ─────────────────────────────────────────────────────────────────────────────
# T13: force-push 후 새 SHA로 5분 timer 리셋 검증
# ─────────────────────────────────────────────────────────────────────────────

def test_t13_force_push_resets_timer(evidence_module, mock_gh_api):
    """T13: force-push 후 새 SHA — head_pushed_at 새로 갱신되면 elapsed=0."""
    new_sha = "newforce123"
    now = datetime.now(timezone.utc)
    # 새 SHA의 push 시각이 방금 (force-push 직후)
    mock_gh_api(head_sha_date=_iso(now - timedelta(seconds=5)))
    result = evidence_module.evaluate_gate(1, new_sha, "owner/repo")
    assert result["state"] == "hold"
    assert result["elapsed_seconds"] < 60


# ─────────────────────────────────────────────────────────────────────────────
# T14: gemini-review-gate + phase3-merge-gate 결과 동일 (정합성)
# ─────────────────────────────────────────────────────────────────────────────

def test_t14_two_check_names_same_result(gate_module, evidence_module, mock_gh_api):
    """T14: 두 check name이 같은 evaluate_gate를 호출 — 결과 동일."""
    head_sha = "abc123"
    now = datetime.now(timezone.utc)
    mock_gh_api(
        reviews=[make_review("LGTM", head_sha)],
        head_sha_date=_iso(now - timedelta(seconds=10)),
    )
    r1 = evidence_module.evaluate_gate(1, head_sha, "owner/repo")
    r2 = evidence_module.evaluate_gate(1, head_sha, "owner/repo")
    assert r1["state"] == r2["state"] == "pass"
    assert r1["reason"] == r2["reason"]


def test_t14b_cli_two_check_names_same_publish_payload(gate_module, evidence_module, mock_gh_api, monkeypatch):
    """T14b: gemini_review_gate.py CLI를 두 check name(--check-name)으로 실행했을 때
    publish_check_run에 전달되는 payload가 동일해야 함 (state/conclusion/summary).

    실제 wrapper가 동일 evaluate_gate를 호출하는지 CLI 레벨로 보장.
    """
    head_sha = "abc123"
    now = datetime.now(timezone.utc)
    mock_gh_api(
        reviews=[make_review("LGTM", head_sha)],
        head_sha_date=_iso(now - timedelta(seconds=10)),
    )

    captured: list[dict] = []

    def fake_publish(repo, sha, name, conclusion, summary, details=""):
        captured.append({
            "repo": repo, "sha": sha, "name": name,
            "conclusion": conclusion, "summary": summary,
        })
        return {"rc": 0, "stdout": "{}", "stderr": ""}

    monkeypatch.setattr(gate_module, "publish_check_run", fake_publish)

    for check_name in ("gemini-review-gate", "phase3-merge-gate"):
        monkeypatch.setattr(sys, "argv", [
            "gemini_review_gate.py",
            "--pr-number", "1", "--commit-sha", head_sha,
            "--repo", "owner/repo", "--check-name", check_name,
            "--publish-check"
        ])
        rc = gate_module.main()
        assert rc == 0, f"PASS state should return 0 for {check_name}"

    assert len(captured) == 2
    # name만 다르고 conclusion/summary는 동일해야 함
    assert captured[0]["name"] == "gemini-review-gate"
    assert captured[1]["name"] == "phase3-merge-gate"
    assert captured[0]["conclusion"] == captured[1]["conclusion"] == "success"
    assert captured[0]["summary"] == captured[1]["summary"]
    assert captured[0]["repo"] == captured[1]["repo"] == "owner/repo"
    assert captured[0]["sha"] == captured[1]["sha"] == head_sha


# ─────────────────────────────────────────────────────────────────────────────
# T15: Gemini API endpoint 호출 0건 정적 검증
# ─────────────────────────────────────────────────────────────────────────────

def test_t15_no_gemini_api_endpoint_in_source():
    """T15: 소스 코드에 generativelanguage.googleapis.com 등 Gemini API endpoint 0건."""
    import re as _re
    import ast as _ast

    forbidden_patterns = [
        r"generativelanguage\.googleapis\.com",
        r"googleapis\.com.*models",
        r"google\.generativeai",
        r"genai\.GenerativeModel",
    ]
    # GEMINI_API_KEY는 env var 참조 패턴만 금지 (os.environ/os.getenv 접근)
    env_access_pattern = r'(?:os\.environ|os\.getenv)\s*[\[\(]\s*["\']GEMINI_API_KEY["\']'

    src_files = [
        SCRIPT_DIR / "gemini_evidence_verify.py",
        SCRIPT_DIR / "gemini_review_gate.py",
    ]

    for src in src_files:
        content = src.read_text(encoding="utf-8")

        # AST 기반 파싱으로 string literal / docstring 제외
        try:
            tree = _ast.parse(content, filename=str(src))
        except SyntaxError as e:
            pytest.fail(f"{src.name}: SyntaxError during parse: {e}")

        # string literal 범위 수집 (docstring, 일반 string)
        string_line_ranges: set[int] = set()
        for node in _ast.walk(tree):
            if isinstance(node, _ast.Constant) and isinstance(node.value, str):
                start = node.lineno
                end = getattr(node, "end_lineno", start)
                string_line_ranges.update(range(start, end + 1))

        lines = content.splitlines()
        for ln_no, line in enumerate(lines, 1):
            stripped = line.strip()
            # 주석 라인 스킵
            if stripped.startswith("#"):
                continue
            # AST에서 string literal로 판단된 라인 스킵
            if ln_no in string_line_ranges:
                continue

            # forbidden 패턴 검사
            for pattern in forbidden_patterns:
                if _re.search(pattern, line):
                    pytest.fail(
                        f"{src.name}:{ln_no} forbidden pattern '{pattern}': {line!r}"
                    )

            # GEMINI_API_KEY 환경변수 접근 패턴 검사
            if _re.search(env_access_pattern, line):
                pytest.fail(
                    f"{src.name}:{ln_no} forbidden GEMINI_API_KEY env access: {line!r}"
                )


# ─────────────────────────────────────────────────────────────────────────────
# T16: HOLD 상태에서 gate.py가 conclusion="failure"로 매핑하는지
# ─────────────────────────────────────────────────────────────────────────────

def test_t16_hold_maps_to_failure_not_neutral(gate_module, evidence_module, mock_gh_api, monkeypatch, capsys):
    """T16: HOLD state → GitHub check conclusion='failure' (neutral 절대 금지)."""
    head_sha = "abc123"
    now = datetime.now(timezone.utc)
    mock_gh_api(head_sha_date=_iso(now - timedelta(seconds=60)))  # HOLD

    # publish_check_run을 캡처
    published = {}

    def fake_publish(repo, sha, name, conclusion, summary, details=""):
        published.update({"conclusion": conclusion, "summary": summary, "name": name})
        return {"rc": 0, "stdout": "{}", "stderr": ""}

    monkeypatch.setattr(gate_module, "publish_check_run", fake_publish)

    monkeypatch.setattr(sys, "argv", [
        "gemini_review_gate.py",
        "--pr-number", "1", "--commit-sha", head_sha,
        "--repo", "owner/repo", "--check-name", "gemini-review-gate",
        "--publish-check"
    ])
    rc = gate_module.main()
    assert rc == 1, "HOLD should return non-zero exit code"
    assert published["conclusion"] == "failure", (
        f"HOLD must map to 'failure', not 'neutral'. got: {published}"
    )
    assert "HOLD" in published["summary"]


# ─────────────────────────────────────────────────────────────────────────────
# TU1: strip_code_blocks — fenced + inline code block 제거 단위 테스트
# ─────────────────────────────────────────────────────────────────────────────

def test_tu1_strip_code_blocks_fenced(evidence_module):
    """TU1-a: fenced code block (``` ... ```) 내용이 제거되는지 확인."""
    body = "Normal text\n```python\nBLOCKING code here\n```\nAfter block"
    stripped = evidence_module.strip_code_blocks(body)
    assert "BLOCKING" not in stripped
    assert "Normal text" in stripped
    assert "After block" in stripped


def test_tu1_strip_code_blocks_inline(evidence_module):
    """TU1-b: inline code (` ... `) 내용이 제거되는지 확인."""
    body = "Use `BLOCKING` method carefully."
    stripped = evidence_module.strip_code_blocks(body)
    assert "BLOCKING" not in stripped
    assert "Use" in stripped
    assert "carefully" in stripped


def test_tu1_strip_code_blocks_empty(evidence_module):
    """TU1-c: 빈 body 입력 시 그대로 반환."""
    assert evidence_module.strip_code_blocks("") == ""
    assert evidence_module.strip_code_blocks(None) is None


def test_tu1_strip_code_blocks_no_code(evidence_module):
    """TU1-d: code block 없는 일반 텍스트는 변경 없이 반환."""
    body = "This is a normal review comment."
    stripped = evidence_module.strip_code_blocks(body)
    assert stripped == body


# ─────────────────────────────────────────────────────────────────────────────
# TU2: match_high_severity — 4종 패턴 직접 검증
# ─────────────────────────────────────────────────────────────────────────────

def test_tu2_match_high_severity_emoji_red(evidence_module):
    """TU2-a: 🔴 이모지 → emoji 패턴 매칭."""
    hits = evidence_module.match_high_severity("🔴 critical issue found")
    assert any("emoji" in h for h in hits), f"got: {hits}"


def test_tu2_match_high_severity_emoji_x(evidence_module):
    """TU2-b: ❌ 이모지 → emoji 패턴 매칭."""
    hits = evidence_module.match_high_severity("❌ do not merge")
    assert any("emoji" in h for h in hits), f"got: {hits}"


def test_tu2_match_high_severity_severity_keyword(evidence_module):
    """TU2-c: 'severity: critical' → severity 패턴 매칭."""
    hits = evidence_module.match_high_severity("severity: critical bug here")
    assert any("severity" in h for h in hits), f"got: {hits}"


def test_tu2_match_high_severity_blocking_keyword(evidence_module):
    """TU2-d: 'BLOCKING' 키워드 → keyword 패턴 매칭."""
    hits = evidence_module.match_high_severity("BLOCKING issue: auth bypass")
    assert any("keyword" in h or "BLOCKING" in h for h in hits), f"got: {hits}"


def test_tu2_match_high_severity_must_fix_keyword(evidence_module):
    """TU2-e: 'MUST FIX' 키워드 → keyword 패턴 매칭."""
    hits = evidence_module.match_high_severity("MUST FIX before merge")
    assert any("keyword" in h or "MUST" in h for h in hits), f"got: {hits}"


def test_tu2_match_high_severity_header_high(evidence_module):
    """TU2-f: '## High' h2 헤더 → header 패턴 매칭."""
    body = "Review:\n\n## High\n- SQL injection"
    hits = evidence_module.match_high_severity(body)
    assert any("header" in h for h in hits), f"got: {hits}"


def test_tu2_match_high_severity_header_blocking(evidence_module):
    """TU2-g: '## Blocking' h2 헤더 → header 패턴 매칭."""
    body = "## Blocking\nMust be fixed before merge."
    hits = evidence_module.match_high_severity(body)
    assert any("header" in h for h in hits), f"got: {hits}"


def test_tu2_match_high_severity_no_match(evidence_module):
    """TU2-h: severity 없는 일반 텍스트 → 빈 리스트."""
    hits = evidence_module.match_high_severity("LGTM, code looks clean")
    assert hits == []


def test_tu2_match_high_severity_blocking_lowercase_no_match(evidence_module):
    """TU2-i: 'blocking' 소문자는 매칭 안됨 (정확히 대문자만)."""
    hits = evidence_module.match_high_severity("This might be blocking the pipeline.")
    # _HS_KEYWORD는 \b(BLOCKING|CRITICAL|MUST FIX)\b — 정확히 대문자만
    assert not any("keyword" in h for h in hits), f"unexpected keyword match: {hits}"


def test_tu2_match_high_severity_empty(evidence_module):
    """TU2-j: 빈 body → 빈 리스트."""
    assert evidence_module.match_high_severity("") == []
    assert evidence_module.match_high_severity(None) == []


# ─────────────────────────────────────────────────────────────────────────────
# TU3: match_supplementary — security/data loss/regression 직접 검증
# ─────────────────────────────────────────────────────────────────────────────

def test_tu3_match_supplementary_security(evidence_module):
    """TU3-a: 'security' 단어 → security 보조 신호."""
    sigs = evidence_module.match_supplementary("This has security implications.")
    assert "security" in sigs, f"got: {sigs}"


def test_tu3_match_supplementary_data_loss(evidence_module):
    """TU3-b: 'data loss' 구문 → data_loss 보조 신호."""
    sigs = evidence_module.match_supplementary("Risk of data loss if not handled.")
    assert "data_loss" in sigs, f"got: {sigs}"


def test_tu3_match_supplementary_regression(evidence_module):
    """TU3-c: 'regression' 단어 → regression 보조 신호."""
    sigs = evidence_module.match_supplementary("This could cause a regression.")
    assert "regression" in sigs, f"got: {sigs}"


def test_tu3_match_supplementary_multiple(evidence_module):
    """TU3-d: 여러 보조 신호 동시에 존재."""
    body = "There's a security issue and risk of data loss and regression."
    sigs = evidence_module.match_supplementary(body)
    assert "security" in sigs
    assert "data_loss" in sigs
    assert "regression" in sigs


def test_tu3_match_supplementary_no_match(evidence_module):
    """TU3-e: 보조 신호 없는 텍스트 → 빈 리스트."""
    sigs = evidence_module.match_supplementary("LGTM, everything looks fine.")
    assert sigs == []


def test_tu3_match_supplementary_in_code_block_excluded(evidence_module):
    """TU3-f: code block 안 'security' → 보조 신호에서 제외."""
    body = "```python\n# security check here\npass\n```"
    sigs = evidence_module.match_supplementary(body)
    assert "security" not in sigs, f"expected no security signal from code block, got: {sigs}"


def test_tu3_match_supplementary_empty(evidence_module):
    """TU3-g: 빈 body → 빈 리스트."""
    assert evidence_module.match_supplementary("") == []
    assert evidence_module.match_supplementary(None) == []


def test_tu3_match_supplementary_case_insensitive(evidence_module):
    """TU3-h: 'Security' 대소문자 혼합도 매칭."""
    sigs = evidence_module.match_supplementary("Security concern noted.")
    assert "security" in sigs, f"got: {sigs}"
