"""
test_verifier_fix_pack.py — task-2381 verifier 3건 통합 fix 회귀 테스트

대상 fix:
- Fix 1: teams.shared.verifiers.git_evidence — _resolve_project_dir nested lookup
- Fix 2: teams.shared.verifiers.critical_gap — 정규식 + 마커 패턴
- Fix 3: scripts.auto_merge — _sync_task_timers helper

작성: 모리건 (dev3 테스터)
원칙: TDD RED-first. 루의 구현이 끝난 뒤 GREEN 검증.
"""

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

WORKSPACE = Path("/home/jay/workspace/.worktrees/task-2381-dev3")
if str(WORKSPACE) not in sys.path:
    sys.path.insert(0, str(WORKSPACE))


# ──────────────────────────────────────────────
# 동적 모듈 로드 (teams/__init__.py, teams/shared/__init__.py 부재)
# ──────────────────────────────────────────────

def _load_module(name: str, file_path: Path):
    spec = importlib.util.spec_from_file_location(name, str(file_path))
    if spec is None or spec.loader is None:
        raise ImportError(f"Cannot load {name} from {file_path}")
    module = importlib.util.module_from_spec(spec)
    sys.modules[name] = module
    spec.loader.exec_module(module)
    return module


git_evidence = _load_module(
    "git_evidence_t2381",
    WORKSPACE / "teams" / "shared" / "verifiers" / "git_evidence.py",
)
critical_gap = _load_module(
    "critical_gap_t2381",
    WORKSPACE / "teams" / "shared" / "verifiers" / "critical_gap.py",
)
auto_merge = _load_module(
    "auto_merge_t2381",
    WORKSPACE / "scripts" / "auto_merge.py",
)


# ──────────────────────────────────────────────
# 헬퍼
# ──────────────────────────────────────────────

def _write_timers(workspace: Path, payload: dict) -> Path:
    memory_dir = workspace / "memory"
    memory_dir.mkdir(parents=True, exist_ok=True)
    timers = memory_dir / "task-timers.json"
    timers.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
    return timers


def _clear_env(monkeypatch):
    for env_var in ("PROJECT_PATH", "WORKTREE_PATH"):
        monkeypatch.delenv(env_var, raising=False)


# ══════════════════════════════════════════════
# Fix 1: git_evidence._resolve_project_dir — nested lookup
# ══════════════════════════════════════════════

def test_git_evidence_nested_lookup(tmp_path, monkeypatch):
    """timers.json의 tasks.<task_id>.worktree_path를 정상 발견해야 한다."""
    _clear_env(monkeypatch)

    # tmp_dir = worktree 후보 (.git 디렉토리 포함)
    tmp_dir = tmp_path / "worktree_target"
    tmp_dir.mkdir()
    (tmp_dir / ".git").mkdir()

    _write_timers(
        tmp_path,
        {"tasks": {"task-2400": {"worktree_path": str(tmp_dir)}}},
    )

    result = git_evidence._resolve_project_dir(
        "task-2400", workspace_root=str(tmp_path)
    )

    assert result == str(tmp_dir), (
        f"nested lookup 실패: expected={tmp_dir}, got={result}"
    )


def test_git_evidence_legacy_top_level_no_match(tmp_path, monkeypatch):
    """legacy top-level 구조({task_id: {...}})는 매칭되지 않고 fallback (D)로 가야 한다."""
    _clear_env(monkeypatch)

    tmp_dir = tmp_path / "legacy_target"
    tmp_dir.mkdir()
    (tmp_dir / ".git").mkdir()

    # tasks 키 없이 top-level에만 entry
    _write_timers(
        tmp_path,
        {"task-2400": {"worktree_path": str(tmp_dir)}},
    )

    result = git_evidence._resolve_project_dir(
        "task-2400", workspace_root=str(tmp_path)
    )

    # tmp_dir 가 반환되면 안 됨 (nested 만 인정)
    assert result != str(tmp_dir), (
        f"legacy top-level이 잘못 매칭됨: result={result}"
    )
    # fallback (D): workspace_root에서 git rev-parse → workspace_root 자체 또는 git 루트
    # 어느 쪽이든 tmp_dir 자체는 아니어야 함
    assert isinstance(result, str) and result, "fallback 결과가 비어있음"


# ══════════════════════════════════════════════
# Fix 2: critical_gap — 정규식 + 마커 패턴
# ══════════════════════════════════════════════

def test_critical_gap_plain_text_pass(tmp_path):
    """이슈 마커 형식이 아닌 본문 평문에서 'critical' 단어가 나와도 PASS."""
    body = (
        "이 영역은 critical 한 영역이라 신중하게 처리합니다.\n"
        "작업이 정상 완료되었습니다."
    )
    report = tmp_path / "task-X.md"
    report.write_text(body, encoding="utf-8")

    result = critical_gap.verify("task-X", report_path=str(report))

    assert result["status"] == "PASS", (
        f"평문 false positive: status={result['status']}, details={result.get('details')}"
    )


def test_critical_gap_marker_detected(tmp_path):
    """이슈 마커(- CRITICAL: ...) + 미해결 → FAIL."""
    body = (
        "## 발견 이슈\n"
        "- CRITICAL: token storage 누락\n"
        "해결 미완료."
    )
    report = tmp_path / "task-X.md"
    report.write_text(body, encoding="utf-8")

    result = critical_gap.verify("task-X", report_path=str(report))

    assert result["status"] == "FAIL", (
        f"마커 미감지: status={result['status']}, details={result.get('details')}"
    )


def test_critical_gap_marker_resolved(tmp_path):
    """마커 발견 + 후행 RESOLVED 키워드 → PASS."""
    body = (
        "## 발견 이슈\n"
        "- CRITICAL: token storage 누락\n"
        "\n"
        "## 해결\n"
        "**[Codex 위험 5건 모두 해결됨]**\n"
        "수정 완료.\n"
    )
    report = tmp_path / "task-X.md"
    report.write_text(body, encoding="utf-8")

    result = critical_gap.verify("task-X", report_path=str(report))

    assert result["status"] == "PASS", (
        f"resolved 인식 실패: status={result['status']}, details={result.get('details')}"
    )


# ══════════════════════════════════════════════
# Fix 3: auto_merge._sync_task_timers
# ══════════════════════════════════════════════

def _setup_task(workspace: Path, task_id: str, base_entry: dict):
    """workspace/memory/task-timers.json 에 task entry 작성."""
    payload = {"tasks": {task_id: base_entry}}
    return _write_timers(workspace, payload)


def _read_task(workspace: Path, task_id: str) -> dict:
    timers = workspace / "memory" / "task-timers.json"
    data = json.loads(timers.read_text(encoding="utf-8"))
    return data.get("tasks", {}).get(task_id, {})


def test_auto_merge_sync_status(tmp_path):
    """_sync_task_timers 호출 후 status=completed, end_time/sha/closed_by 기록."""
    task_id = "task-2400"
    _setup_task(
        tmp_path,
        task_id,
        {"status": "running", "start_time": "2026-05-02T00:00:00+00:00"},
    )

    ret = auto_merge._sync_task_timers(
        tmp_path, task_id, "abc1234", "auto_merged"
    )

    assert ret is True, f"반환값 불일치: {ret}"

    entry = _read_task(tmp_path, task_id)
    assert entry.get("status") == "completed", f"status: {entry.get('status')}"
    assert entry.get("end_time"), f"end_time 누락: {entry}"
    assert entry.get("merge_commit_sha") == "abc1234", (
        f"merge_commit_sha: {entry.get('merge_commit_sha')}"
    )
    closed_by = entry.get("closed_by", "")
    assert closed_by.startswith("auto_merge:"), (
        f"closed_by prefix 불일치: {closed_by}"
    )


def test_auto_merge_sync_atomic(tmp_path):
    """동일 task에 대한 중복 호출은 두 번째가 False (이미 completed) 반환."""
    task_id = "task-2400"
    _setup_task(
        tmp_path,
        task_id,
        {"status": "running", "start_time": "2026-05-02T00:00:00+00:00"},
    )

    ret1 = auto_merge._sync_task_timers(
        tmp_path, task_id, "abc1234", "auto_merged"
    )
    ret2 = auto_merge._sync_task_timers(
        tmp_path, task_id, "abc1234", "auto_merged"
    )

    assert ret1 is True, f"첫 호출 반환값: {ret1}"
    assert ret2 is False, f"두 번째 호출 반환값: {ret2} (이미 completed면 False여야 함)"

    # 동시성 추가 검증: threading 2개 동시 실행 후 정확히 1번만 True
    task_id2 = "task-2401"
    _setup_task(
        tmp_path,
        task_id2,
        {"status": "running", "start_time": "2026-05-02T00:00:00+00:00"},
    )

    results: list[bool] = []
    lock = threading.Lock()

    def worker():
        r = auto_merge._sync_task_timers(
            tmp_path, task_id2, "deadbeef", "auto_merged"
        )
        with lock:
            results.append(r)

    threads = [threading.Thread(target=worker) for _ in range(2)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

    true_count = sum(1 for r in results if r is True)
    assert true_count == 1, (
        f"race condition: True 반환 {true_count}회 (1회여야 함), results={results}"
    )
    entry2 = _read_task(tmp_path, task_id2)
    assert entry2.get("status") == "completed"


def test_auto_merge_sync_no_clobber(tmp_path):
    """이미 completed인 task의 end_time을 덮어쓰지 않아야 한다."""
    task_id = "task-2400"
    original_end_time = "2026-01-01T00:00:00+00:00"
    _setup_task(
        tmp_path,
        task_id,
        {
            "status": "completed",
            "start_time": "2025-12-31T00:00:00+00:00",
            "end_time": original_end_time,
            "merge_commit_sha": "preexisting",
        },
    )

    ret = auto_merge._sync_task_timers(
        tmp_path, task_id, "newsha99", "auto_merged"
    )

    assert ret is False, f"이미 completed일 때 False 기대: {ret}"

    entry = _read_task(tmp_path, task_id)
    assert entry.get("end_time") == original_end_time, (
        f"end_time이 덮어써짐: expected={original_end_time}, got={entry.get('end_time')}"
    )
