"""
test_content_qc_2769.py — content 팀 전용 lightweight QC 회귀.

검증: PASS/FAIL(디스클레이머/권유/PII/외부발송) · 코드혼입 부적격 · self-contained(PYTHONPATH 독립) ·
      finish-task.sh 무수정 · shared/dev/design QC 무변경.
회장 승인: C-minimal — teams/content/qc/qc_verify.py 1파일, finish-task 무수정.
"""
from __future__ import annotations

import importlib.util
import json
import subprocess
import sys
from pathlib import Path

_ROOT = Path(__file__).resolve().parents[2]
_QC = _ROOT / "teams" / "content" / "qc" / "qc_verify.py"

_spec = importlib.util.spec_from_file_location("content_qc_2769", _QC)
cqc = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(cqc)

_GOOD_DOC = """# 연금 정보
연금저축과 IRP는 노후 대비 제도입니다(일반 정보).
어떤 상품을 권하는 글이 아니라 제도 이해용입니다.

## 디스클레이머
본 글은 일반 정보이며 특정 상품 권유가 아닙니다. 가입 전 본인 상황에 맞는 전문가 상담을 권합니다.
세법은 매년 개정될 수 있으므로 최신 기준을 확인하시기 바랍니다.
"""

_TASK_MD = """# task-x

## 레벨
문서만

## allowed_resources
allowed_resources:
  paths:
    - "memory/work/insurance_content/doc.md"
  merge_policy: "none"
"""


def _mk(tmp_path, task_md=_TASK_MD, doc=_GOOD_DOC, doc_rel="memory/work/insurance_content/doc.md", tid="task-ct-2769"):
    (tmp_path / "memory" / "tasks").mkdir(parents=True, exist_ok=True)
    (tmp_path / "memory" / "tasks" / f"{tid}.md").write_text(task_md, encoding="utf-8")
    if doc is not None:
        dp = tmp_path / doc_rel
        dp.parent.mkdir(parents=True, exist_ok=True)
        dp.write_text(doc, encoding="utf-8")
    return tid


def _run(tmp_path, tid):
    return cqc.run_gate(tid, "content", tmp_path)


def test_good_doc_pass(tmp_path):
    tid = _mk(tmp_path)
    r = _run(tmp_path, tid)
    assert r["qc_result"] == "PASS", r["reasons"]


def test_missing_disclaimer_fail(tmp_path):
    tid = _mk(tmp_path, doc="# 글\n연금저축과 IRP 비교(일반 정보).\n끝.")
    r = _run(tmp_path, tid)
    assert r["qc_result"] == "FAIL"
    assert any("disclaimer" in x for x in r["reasons"])


def test_solicitation_fail(tmp_path):
    bad = _GOOD_DOC + "\n지금 가입하세요! 이 상품에 가입하시기 바랍니다."
    tid = _mk(tmp_path, doc=bad)
    r = _run(tmp_path, tid)
    assert r["qc_result"] == "FAIL"
    assert any("no_solicitation" in x for x in r["reasons"])


def test_pii_fail(tmp_path):
    for pii in ("주민번호 900101-1234567", "연락처 010-1234-5678", "메일 hong@example.com"):
        tid = _mk(tmp_path, doc=_GOOD_DOC + "\n" + pii, tid="task-pii")
        r = _run(tmp_path, tid)
        assert r["qc_result"] == "FAIL", f"PII 미탐: {pii}"
        assert any("no_pii" in x for x in r["reasons"])


def test_external_send_fail(tmp_path):
    bad = _GOOD_DOC + "\n고객에게 발송 완료했습니다. 블로그 게시 완료."
    tid = _mk(tmp_path, doc=bad)
    r = _run(tmp_path, tid)
    assert r["qc_result"] == "FAIL"
    assert any("external_send" in x for x in r["reasons"])


def test_code_file_mixed_rejected(tmp_path):
    md = _TASK_MD.replace('- "memory/work/insurance_content/doc.md"',
                          '- "memory/work/insurance_content/doc.md"\n    - "dispatch/foo.py"')
    tid = _mk(tmp_path, task_md=md)
    r = _run(tmp_path, tid)
    assert r["qc_result"] == "FAIL"
    assert any("혼입" in x or "부적격" in x for x in r["reasons"])


def test_no_level_marker_rejected(tmp_path):
    md = _TASK_MD.replace("## 레벨\n문서만\n\n", "")
    tid = _mk(tmp_path, task_md=md)
    r = _run(tmp_path, tid)
    assert r["qc_result"] == "FAIL"


def test_qc_result_file_written(tmp_path):
    tid = _mk(tmp_path)
    _run(tmp_path, tid)
    rp = tmp_path / "memory" / "events" / f"{tid}.qc-result"
    assert rp.exists()
    data = json.loads(rp.read_text(encoding="utf-8"))
    assert "qc_result" in data and data["mode"] == "content_lightweight_qc"


def test_self_contained_no_pythonpath(tmp_path):
    # ★ PYTHONPATH 미설정 + cwd=tmp 에서도 ModuleNotFoundError 0 (self-contained)
    tid = _mk(tmp_path)
    env = {"PATH": "/usr/bin:/bin"}  # PYTHONPATH 의도적 제거
    proc = subprocess.run(
        [sys.executable, str(_QC), "--gate", "--task-id", tid, "--team", "content",
         "--workspace", str(tmp_path)],
        capture_output=True, text=True, env=env, cwd=str(tmp_path),
    )
    assert proc.returncode == 0, proc.stderr
    assert "ModuleNotFoundError" not in proc.stderr
    assert (tmp_path / "memory" / "events" / f"{tid}.qc-result").exists()


def test_finish_task_and_shared_qc_unchanged():
    # 무수정 증거 (해시 비교는 보고 단계에서 — 여기선 파일 존재/내용 무관 가드)
    assert (_ROOT / "scripts" / "finish-task.sh").exists()
    assert (_ROOT / "teams" / "shared" / "qc_verify.py").exists()
    # content qc 가 shared verifiers / utils 를 import 하지 않음(self-contained).
    #   주석의 설명 문자열은 허용 — 실제 import 문만 검사.
    import re as _re
    src = _QC.read_text(encoding="utf-8")
    assert "from utils" not in src
    assert not _re.search(r"^\s*(from|import)\s+\S*verifiers", src, _re.M)


# ── Gemini HIGH/medium 대응 회귀 (PR #247) ──

def test_high1_task_id_traversal_rejected(tmp_path):
    # HIGH-1: 안전하지 않은 task_id 는 경로 구성 전 거부(FAIL)
    _mk(tmp_path, tid="task-tpl-2769")  # workspace 준비
    for bad in ("../etc/passwd", "a/b", "..", "foo bar", "", "x;rm"):
        r = cqc.run_gate(bad, "content", tmp_path)
        assert r["qc_result"] == "FAIL", f"unsafe task_id 통과: {bad!r}"
        assert any("task_id" in x for x in r["reasons"])


def test_high1_valid_task_id_pass(tmp_path):
    # 정상 task_id 는 통과(검증이 정상 케이스를 막지 않음)
    tid = _mk(tmp_path, tid="task-2770")
    r = cqc.run_gate("task-2770", "content", tmp_path)
    assert r["qc_result"] == "PASS", r["reasons"]


def test_high2_absolute_path_rejected(tmp_path):
    # HIGH-2: allowed_resources.paths 의 절대경로 거부
    md = _TASK_MD.replace('- "memory/work/insurance_content/doc.md"', '- "/etc/passwd"')
    tid = _mk(tmp_path, task_md=md)
    r = _run(tmp_path, tid)
    assert r["qc_result"] == "FAIL"
    assert any("절대경로" in x for x in r["reasons"])


def test_high2_traversal_path_rejected(tmp_path):
    # HIGH-2: paths 의 ".." traversal 은 workspace 밖 → 거부
    md = _TASK_MD.replace('- "memory/work/insurance_content/doc.md"',
                          '- "../../../../etc/passwd"')
    tid = _mk(tmp_path, task_md=md)
    r = _run(tmp_path, tid)
    assert r["qc_result"] == "FAIL"
    assert any("workspace 밖" in x or "traversal" in x for x in r["reasons"])


def test_high2_internal_relative_path_pass(tmp_path):
    # workspace 내부 정상 상대경로는 PASS (방어가 정상 케이스를 막지 않음)
    tid = _mk(tmp_path)
    r = _run(tmp_path, tid)
    assert r["qc_result"] == "PASS", r["reasons"]


def test_medium4_directory_not_crash(tmp_path):
    # medium-4: 산출물 경로가 디렉토리여도 traceback crash 아니라 structured FAIL
    (tmp_path / "memory" / "tasks").mkdir(parents=True, exist_ok=True)
    (tmp_path / "memory" / "work" / "insurance_content" / "doc.md").mkdir(parents=True, exist_ok=True)  # 디렉토리로 생성
    (tmp_path / "memory" / "tasks" / "task-dir-2769.md").write_text(_TASK_MD, encoding="utf-8")
    r = cqc.run_gate("task-dir-2769", "content", tmp_path)
    assert r["qc_result"] == "FAIL"
    assert any("디렉토리" in x for x in r["reasons"])


def test_medium3_trailing_comment_paths(tmp_path):
    # medium-3: paths 항목의 trailing comment(#) 무시하고 정상 파싱
    md = _TASK_MD.replace('- "memory/work/insurance_content/doc.md"',
                          '- "memory/work/insurance_content/doc.md"   # 산출물')
    tid = _mk(tmp_path, task_md=md)
    r = _run(tmp_path, tid)
    assert r["qc_result"] == "PASS", r["reasons"]
