#!/usr/bin/env python3
"""scripts/verify_task_id_hardening.py — task-2485 dry-run 검증.

local/CI 어디서나 다음을 한 번에 검증:
- task_id parser valid/invalid matrix
- browser_verify task-N+M acceptance
- git_evidence worktree 기준 판단 가능 여부
- legacy task-N backward compatibility

종료 코드:
- 0: PASS (모든 체크 통과)
- 1: FAIL (하나라도 실패)
"""
from __future__ import annotations

import importlib.util
import sys
from pathlib import Path

WORKSPACE = Path(__file__).resolve().parent.parent

# 워크트리/메인 어느 환경이든 자신의 워크스페이스 루트가 utils 검색 시 우선되도록 처리.
if str(WORKSPACE) in sys.path:
    sys.path.remove(str(WORKSPACE))
sys.path.insert(0, str(WORKSPACE))


def _load(name: str, path: Path):
    spec = importlib.util.spec_from_file_location(name, str(path))
    if spec is None or spec.loader is None:
        return None
    mod = importlib.util.module_from_spec(spec)
    sys.modules[name] = mod
    try:
        spec.loader.exec_module(mod)
    except Exception as e:
        print(f"  [LOAD FAIL] {path}: {e}")
        return None
    return mod


def check_parser():
    """utils.task_id_parser API matrix."""
    print("== Check 1: utils.task_id_parser matrix ==")
    failed = 0
    try:
        from utils.task_id_parser import (  # type: ignore[import-not-found]
            is_valid_task_id,
            extract_task_id,
            TASK_ID_RE,
            TASK_ID_PATTERN,
        )
    except ImportError as e:
        print(f"  FAIL import: {e}")
        return 1

    valid_cases = ["task-2472", "task-2472+1", "task-2472+2", "task-2467+3"]
    invalid_cases = ["task-abc", "task-2472+", "task-2472++1", "task-", ""]

    for tid in valid_cases:
        if not is_valid_task_id(tid):
            print(f"  FAIL valid {tid!r}")
            failed += 1
    for tid in invalid_cases:
        if is_valid_task_id(tid):
            print(f"  FAIL invalid (accepted) {tid!r}")
            failed += 1

    extract_cases = [
        ("foo-task-2472+1-bar", "task-2472+1"),
        ("task/task-2467+3-dev6", "task-2467+3"),
        ("memory/tasks/task-2472+2.md", "task-2472+2"),
    ]
    for text, expected in extract_cases:
        actual = extract_task_id(text)
        if actual != expected:
            print(f"  FAIL extract {text!r} → {actual!r} (expected {expected!r})")
            failed += 1

    import re
    if not isinstance(TASK_ID_RE, re.Pattern):
        print("  FAIL TASK_ID_RE not compiled Pattern")
        failed += 1
    if not isinstance(TASK_ID_PATTERN, str):
        print("  FAIL TASK_ID_PATTERN not str")
        failed += 1

    if failed == 0:
        print(f"  PASS: parser API matrix ({len(valid_cases)+len(invalid_cases)+len(extract_cases)+2} cases)")
    return failed


def check_browser_verify():
    """browser_verify가 task-N+M을 거부하지 않음."""
    print("== Check 2: browser_verify task-N+M acceptance ==")
    failed = 0
    for kind, path in [
        ("shared", WORKSPACE / "teams/shared/verifiers/browser_verify.py"),
        ("dev1", WORKSPACE / "teams/dev1/qc/verifiers/browser_verify.py"),
    ]:
        mod = _load(f"bv_{kind}_v", path)
        if mod is None:
            print(f"  FAIL load {kind}")
            failed += 1
            continue
        # task-2472+2 검증
        result = mod.verify("task-2472+2", "/tmp/nonexistent_workspace_t2485")
        if any("잘못된 task_id 형식" in d for d in result["details"]):
            print(f"  FAIL {kind}: task-2472+2 거부됨")
            failed += 1
        else:
            print(f"  PASS {kind}: task-2472+2 통과")
        # invalid 거부
        result = mod.verify("task-abc", "/tmp/nonexistent_workspace_t2485")
        if not any("잘못된 task_id 형식" in d for d in result["details"]):
            print(f"  FAIL {kind}: invalid 'task-abc'를 거부 안 함")
            failed += 1
    return failed


def check_git_evidence():
    """git_evidence가 worktree/main repo 분리 판단."""
    print("== Check 3: git_evidence worktree/main repo separation ==")
    import os as _os
    import subprocess as _subp
    import tempfile as _tmp
    failed = 0

    for kind, path in [
        ("shared", WORKSPACE / "teams/shared/verifiers/git_evidence.py"),
        ("dev1", WORKSPACE / "teams/dev1/qc/verifiers/git_evidence.py"),
    ]:
        mod = _load(f"ge_{kind}_v", path)
        if mod is None:
            print(f"  FAIL load {kind}")
            failed += 1
            continue
        # 3-1) helper 존재
        if not hasattr(mod, "_resolve_project_dir_with_source"):
            print(f"  FAIL {kind}: _resolve_project_dir_with_source 미정의")
            failed += 1
        if not hasattr(mod, "_filter_dirty_to_task_scope"):
            print(f"  FAIL {kind}: _filter_dirty_to_task_scope 미정의")
            failed += 1
            continue
        # 3-2) filter 경계 매칭 (Codex high #1)
        scoped = mod._filter_dirty_to_task_scope(
            [
                "memory/tasks/task-2472+1.md",   # 본 task 정확
                "memory/tasks/task-2472+10.md",  # 경계 위반 후보 — 다른 task
                "memory/tasks/task-2472.md",     # base 매치 — 본 task scope
                "memory/tasks/task-9999.md",    # 무관
            ],
            "task-2472+1",
        )
        if "memory/tasks/task-2472+10.md" in scoped:
            print(f"  FAIL {kind}: 경계 검사 미흡 — task-2472+10 이 task-2472+1 으로 오인")
            failed += 1
        elif "memory/tasks/task-9999.md" in scoped:
            print(f"  FAIL {kind}: filter가 무관 task 파일 포함")
            failed += 1
        elif "memory/tasks/task-2472+1.md" not in scoped or "memory/tasks/task-2472.md" not in scoped:
            print(f"  FAIL {kind}: filter가 본 task scope 파일을 제외")
            failed += 1
        else:
            print(f"  PASS {kind}: filter 경계 매칭 정상")

        # 3-3) 실제 verify() 시나리오 (Codex high #2)
        with _tmp.TemporaryDirectory() as td:
            repo = _os.path.join(td, "main_repo")
            _os.makedirs(repo, exist_ok=True)
            try:
                _subp.run(["git", "init", "-q"], cwd=repo, check=True)
                _subp.run(["git", "config", "user.email", "t@t"], cwd=repo, check=True)
                _subp.run(["git", "config", "user.name", "t"], cwd=repo, check=True)
                # 본 task 커밋
                _os.makedirs(_os.path.join(repo, "memory", "tasks"), exist_ok=True)
                with open(_os.path.join(repo, "memory", "tasks", "task-vsh+1.md"), "w") as fp:
                    fp.write("# task-vsh+1")
                with open(_os.path.join(repo, "task-vsh+1-impl.py"), "w") as fp:
                    fp.write("x = 1")
                _subp.run(["git", "add", "."], cwd=repo, check=True)
                _subp.run(
                    ["git", "commit", "-q", "-m", "[task-vsh+1] add"], cwd=repo, check=True
                )
                # 다른 task 의 unstaged dirty 추가 (task-vsh+1 scope 외)
                with open(_os.path.join(repo, "other_task_file.py"), "w") as fp:
                    fp.write("from_other = True")

                # env 미설정 → fallback path
                _os.environ.pop("PROJECT_PATH", None)
                _os.environ.pop("WORKTREE_PATH", None)

                result = mod.verify("task-vsh+1", repo)
                # 케이스 A: main repo 다른 task dirty → 본 task NO_UNCOMMITTED PASS 여야 함
                if result["status"] == "FAIL" and any(
                    "FAIL NO_UNCOMMITTED" in d for d in result["details"]
                ):
                    print(f"  FAIL {kind}: main repo 다른 task dirty 가 본 task 를 FAIL 시킴")
                    failed += 1
                else:
                    print(f"  PASS {kind}: main repo fallback 에서 다른 task dirty 무시")

                # 케이스 B: 본 task scope 의 modified → FAIL
                impl_file = _os.path.join(repo, "task-vsh+1-impl.py")
                with open(impl_file, "w") as fp:
                    fp.write("x = 2  # modified")
                # PROJECT_PATH 로 worktree 모드 강제
                _os.environ["PROJECT_PATH"] = repo
                try:
                    result_wt = mod.verify("task-vsh+1", repo)
                finally:
                    _os.environ.pop("PROJECT_PATH", None)
                if result_wt["status"] != "FAIL" or not any(
                    "FAIL NO_UNCOMMITTED" in d for d in result_wt["details"]
                ):
                    print(f"  FAIL {kind}: worktree 자체 dirty 인데 NO_UNCOMMITTED FAIL 미발생")
                    failed += 1
                else:
                    print(f"  PASS {kind}: worktree dirty 는 여전히 FAIL")
            except _subp.CalledProcessError as e:
                print(f"  FAIL {kind}: 임시 git repo 시나리오 실패 — {e}")
                failed += 1
    return failed


def main():
    failed = 0
    failed += check_parser()
    failed += check_browser_verify()
    failed += check_git_evidence()

    print()
    if failed == 0:
        print("[verify_task_id_hardening] OVERALL PASS")
        return 0
    print(f"[verify_task_id_hardening] OVERALL FAIL ({failed} 항목)")
    return 1


if __name__ == "__main__":
    sys.exit(main())
