"""tests/lifecycle_guards/test_pre_push_guard.py — task-2471 회귀 테스트.

토르가 commit 0750481e 에서 ``scripts/pre_push_guard.py`` 에 적용한 4결함
수정을 영구 차단한다.

1. ``_parse_allowed_resources_yaml`` 의 numbered heading 인식
   (``## 7. allowed_resources`` 형식 + 기존 ``## allowed_resources`` 모두 OK)
2. ``_strip_yaml_inline_comment`` 헬퍼 (inline ``# 주석`` 제거)
3. ``extract_task_id_from_branch`` 헬퍼 (+N suffix 보존)
4. ``_resolve_allowed_resources`` capability snapshot + task 파일 모두 부재 시
   ``(None, error_msg)`` 반환 (실제 sys.exit 는 main 에서)

본 테스트는 ``tests/scripts/test_pre_push_guard.py`` (기존 7건 시나리오) 와
별도. 정적 단위 테스트만 수행.

헤임달(개발2팀 테스터) 작성.
"""
from __future__ import annotations

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

import pytest

WORKSPACE = Path(__file__).resolve().parents[2]
SCRIPTS_DIR = WORKSPACE / "scripts"
PRE_PUSH_GUARD_PATH = SCRIPTS_DIR / "pre_push_guard.py"


@pytest.fixture(scope="module")
def ppg():
    """``scripts/pre_push_guard.py`` 를 절대 경로 로드.

    ``pre_push_guard`` 자체가 ``sys.path`` 에 scripts/ 와 workspace root 를
    추가하므로 외부 setup 불필요. 단, qc_report_guard / task_scope 가 같은
    scripts 디렉토리에 있어야 함 (worktree 안에서 보장됨).
    """
    assert PRE_PUSH_GUARD_PATH.exists(), f"missing: {PRE_PUSH_GUARD_PATH}"
    # workspace root 와 scripts/ 를 sys.path 에 추가 — 모듈 import 가
    # 자체 추가하지만 명시적으로도 보장.
    for p in (str(WORKSPACE), str(SCRIPTS_DIR)):
        if p not in sys.path:
            sys.path.insert(0, p)

    spec = importlib.util.spec_from_file_location(
        "pre_push_guard_test_alias", str(PRE_PUSH_GUARD_PATH)
    )
    assert spec is not None and spec.loader is not None
    mod = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = mod
    spec.loader.exec_module(mod)
    return mod


# ---------------------------------------------------------------------------
# 1. numbered heading 인식
# ---------------------------------------------------------------------------


def test_yaml_numbered_heading_recognized(ppg):
    """``## 7. allowed_resources`` 형식 YAML 블록 정상 파싱."""
    text = (
        "# 헤더\n\n"
        "## 7. allowed_resources\n"
        "```yaml\n"
        "paths:\n"
        '  - "scripts/foo.py"\n'
        "  - tests/bar.py\n"
        "forbidden_paths:\n"
        "  - secret/**\n"
        "```\n"
    )
    out = ppg._parse_allowed_resources_yaml(text)
    assert out is not None, "numbered heading 미인식"
    assert "scripts/foo.py" in out["paths"]
    assert "tests/bar.py" in out["paths"]
    assert "secret/**" in out["forbidden_paths"]


def test_yaml_plain_heading_still_works(ppg):
    """기존 ``## allowed_resources`` heading 도 그대로 파싱 (regression)."""
    text = (
        "## allowed_resources\n"
        "```yaml\n"
        "paths:\n"
        "  - utils/x.py\n"
        "```\n"
    )
    out = ppg._parse_allowed_resources_yaml(text)
    assert out is not None
    assert out["paths"] == ["utils/x.py"]


def test_yaml_numbered_two_digit_heading(ppg):
    """``## 12. allowed_resources`` 처럼 두 자리 숫자도 OK."""
    text = (
        "## 12. allowed_resources\n"
        "```yaml\n"
        "paths:\n"
        "  - a.py\n"
        "```\n"
    )
    out = ppg._parse_allowed_resources_yaml(text)
    assert out is not None
    assert out["paths"] == ["a.py"]


# ---------------------------------------------------------------------------
# 2. inline comment strip
# ---------------------------------------------------------------------------


def test_strip_inline_comment_basic(ppg):
    """``path/to/file  # 주석`` -> ``path/to/file``."""
    out = ppg._strip_yaml_inline_comment('path/to/file  # 주석')
    assert out == "path/to/file"


def test_strip_inline_comment_no_quote(ppg):
    """quote 없는 단순 path."""
    out = ppg._strip_yaml_inline_comment("path  # comment")
    assert out == "path"


def test_strip_inline_comment_no_comment_unchanged(ppg):
    """주석 없으면 원본 그대로."""
    out = ppg._strip_yaml_inline_comment("path/to/file")
    assert out == "path/to/file"


def test_yaml_block_with_inline_comments(ppg):
    """전체 YAML 파싱 흐름에서 inline 주석 제거 확인."""
    text = (
        "## allowed_resources\n"
        "```yaml\n"
        "paths:\n"
        '  - "scripts/foo.py"  # 본 task 핵심\n'
        "  - utils/bar.py    # 헬퍼\n"
        "forbidden_paths:\n"
        "  - secret/**  # ABSOLUTE forbidden\n"
        "```\n"
    )
    out = ppg._parse_allowed_resources_yaml(text)
    assert out is not None
    assert "scripts/foo.py" in out["paths"]
    assert "utils/bar.py" in out["paths"]
    # 주석이 path 에 섞이면 안 됨
    for p in out["paths"]:
        assert "#" not in p, f"inline 주석이 path 에 잔존: {p!r}"
    assert "secret/**" in out["forbidden_paths"]


# ---------------------------------------------------------------------------
# 3. branch parsing
# ---------------------------------------------------------------------------


def test_branch_parsing_with_retry_suffix(ppg):
    """``task/task-2467+3-dev6`` -> ``task-2467+3``."""
    out = ppg.extract_task_id_from_branch("task/task-2467+3-dev6")
    assert out == "task-2467+3"


def test_branch_parsing_main_returns_none(ppg):
    """``main`` 같은 일반 브랜치는 None."""
    out = ppg.extract_task_id_from_branch("main")
    assert out is None


def test_branch_parsing_full_phase_parallel_retry(ppg):
    """phase + parallel + retry 모두 보존."""
    out = ppg.extract_task_id_from_branch("task/task-2470_1.2_a+5-dev3")
    assert out == "task-2470_1.2_a+5"


def test_branch_parsing_empty_input(ppg):
    assert ppg.extract_task_id_from_branch("") is None


# ---------------------------------------------------------------------------
# 4. _resolve_allowed_resources fail-closed
# ---------------------------------------------------------------------------


def test_resolve_allowed_resources_returns_none_when_both_absent(ppg, tmp_path):
    """capability snapshot 부재 + task 파일 부재 → ``(None, error_msg)`` 반환."""
    # tmp_path 안에 capability 디렉토리도 task 파일도 없음
    ar, err = ppg._resolve_allowed_resources("task-9999", str(tmp_path))
    assert ar is None
    assert err  # non-empty error message
    # task 파일이 없다는 메시지가 포함되어야 함
    assert "없음" in err or "not" in err.lower()


def test_resolve_allowed_resources_uses_capability_snapshot(ppg, tmp_path):
    """capability snapshot 이 있으면 그것을 사용 (task 파일 fallback X)."""
    cap_dir = tmp_path / "memory" / "capabilities"
    cap_dir.mkdir(parents=True)
    snap = {
        "allowed_resources": {
            "paths": ["scripts/foo.py"],
            "forbidden_paths": ["secret/**"],
        }
    }
    (cap_dir / "task-2471.json").write_text(json.dumps(snap))

    ar, err = ppg._resolve_allowed_resources("task-2471", str(tmp_path))
    assert err == ""
    assert ar is not None
    assert "scripts/foo.py" in ar["paths"]
    assert "secret/**" in ar["forbidden_paths"]


def test_resolve_allowed_resources_falls_back_to_task_file(ppg, tmp_path):
    """capability snapshot 없으면 task .md 파일의 YAML 블록 사용."""
    tasks_dir = tmp_path / "memory" / "tasks"
    tasks_dir.mkdir(parents=True)
    (tasks_dir / "task-9000.md").write_text(
        "# task-9000\n\n"
        "## 5. allowed_resources\n"
        "```yaml\n"
        "paths:\n"
        "  - utils/x.py  # OK\n"
        "```\n"
    )

    ar, err = ppg._resolve_allowed_resources("task-9000", str(tmp_path))
    assert err == ""
    assert ar is not None
    assert "utils/x.py" in ar["paths"]


def test_resolve_allowed_resources_task_file_without_yaml_block(ppg, tmp_path):
    """task .md 파일은 있지만 ``## allowed_resources`` 블록 없으면 ``(None, msg)``."""
    tasks_dir = tmp_path / "memory" / "tasks"
    tasks_dir.mkdir(parents=True)
    (tasks_dir / "task-7777.md").write_text("# task\n\n본문만 있고 YAML 블록 없음\n")

    ar, err = ppg._resolve_allowed_resources("task-7777", str(tmp_path))
    assert ar is None
    assert err  # 에러 메시지 존재
