"""tests/regression/test_git_evidence_hardening.py — task-2485 회귀.

git_evidence가 main repo dirty와 current worktree dirty를 분리 판단하는지 검증.
shared + dev1 두 모듈 모두 검증.
"""
from __future__ import annotations

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

import pytest

WORKSPACE = Path(__file__).resolve().parents[2]

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


def _load_module(modname: str, file_path: Path):
    if not file_path.exists():
        pytest.skip(f"{file_path} 없음")
    spec = importlib.util.spec_from_file_location(modname, str(file_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


@pytest.fixture(scope="module")
def shared_git_evidence():
    return _load_module(
        "ge_shared_t2485",
        WORKSPACE / "teams" / "shared" / "verifiers" / "git_evidence.py",
    )


@pytest.fixture(scope="module")
def dev1_git_evidence():
    return _load_module(
        "ge_dev1_t2485",
        WORKSPACE / "teams" / "dev1" / "qc" / "verifiers" / "git_evidence.py",
    )


def _init_repo(path: Path) -> Path:
    """git repo 초기화 + 커밋 1건."""
    subprocess.run(["git", "init", "-q"], cwd=path, check=True)
    subprocess.run(["git", "config", "user.email", "test@test"], cwd=path, check=True)
    subprocess.run(["git", "config", "user.name", "test"], cwd=path, check=True)
    (path / "README.md").write_text("init")
    subprocess.run(["git", "add", "."], cwd=path, check=True)
    subprocess.run(["git", "commit", "-q", "-m", "init"], cwd=path, check=True)
    return path


def test_resolve_project_dir_with_source_returns_tuple_shared(shared_git_evidence, tmp_path):
    """_resolve_project_dir_with_source는 (path, source) 튜플 반환."""
    _init_repo(tmp_path)
    result = shared_git_evidence._resolve_project_dir_with_source("task-test", str(tmp_path))
    assert isinstance(result, tuple) and len(result) == 2
    path, source = result
    assert isinstance(path, str) and path
    assert source in ("env_var", "task_timers", "task_file", "fallback")


def test_resolve_project_dir_with_source_returns_tuple_dev1(dev1_git_evidence, tmp_path):
    _init_repo(tmp_path)
    result = dev1_git_evidence._resolve_project_dir_with_source("task-test", str(tmp_path))
    assert isinstance(result, tuple) and len(result) == 2


def test_resolve_project_dir_legacy_signature_preserved_shared(shared_git_evidence, tmp_path):
    """기존 _resolve_project_dir(string 반환) API 유지 (하위 호환)."""
    _init_repo(tmp_path)
    result = shared_git_evidence._resolve_project_dir("task-test", str(tmp_path))
    assert isinstance(result, str)


def test_resolve_project_dir_legacy_signature_preserved_dev1(dev1_git_evidence, tmp_path):
    _init_repo(tmp_path)
    result = dev1_git_evidence._resolve_project_dir("task-test", str(tmp_path))
    assert isinstance(result, str)


def test_filter_dirty_to_task_scope_shared(shared_git_evidence):
    """다른 task의 파일은 task scope에서 제외."""
    files = [
        "memory/tasks/task-2485.md",      # 본 task
        "memory/tasks/task-9999.md",      # 다른 task
        "utils/state_repair.py",          # task_id 미포함
        "memory/reports/task-2485-foo.md", # 본 task
    ]
    scoped = shared_git_evidence._filter_dirty_to_task_scope(files, "task-2485")
    assert "memory/tasks/task-2485.md" in scoped
    assert "memory/reports/task-2485-foo.md" in scoped
    assert "memory/tasks/task-9999.md" not in scoped
    assert "utils/state_repair.py" not in scoped


def test_filter_dirty_to_task_scope_retry_suffix_dev1(dev1_git_evidence):
    """task-N+M의 base(task-N)도 scope로 인정."""
    files = [
        "memory/tasks/task-2472.md",       # base 매치
        "memory/tasks/task-2472+1.md",     # 정확 매치
        "memory/tasks/task-9999.md",       # 무관
    ]
    scoped = dev1_git_evidence._filter_dirty_to_task_scope(files, "task-2472+1")
    assert "memory/tasks/task-2472.md" in scoped
    assert "memory/tasks/task-2472+1.md" in scoped
    assert "memory/tasks/task-9999.md" not in scoped


# (Codex high #1 대응) 경계 강화: 부분문자열 매치 사고 방어
def test_filter_dirty_to_task_scope_boundary_no_substring_collision_shared(shared_git_evidence):
    """task-2472+1 검사 시 task-2472+10 / task-24720 / foo-task-2472-bar 가 본 scope 로 오인되지 않음."""
    files = [
        "memory/tasks/task-2472+10.md",     # task-2472+1 의 substring 이지만 다른 task
        "memory/tasks/task-24720.md",       # task-2472 의 substring 이지만 다른 task
        "memory/tasks/foo-task-2472-bar.md",  # 단어 중간 — 다른 컨텍스트
        "memory/tasks/task-2472+1.md",      # 본 task (정확 일치)
        "memory/tasks/task-2472.md",        # 본 task base
    ]
    scoped = shared_git_evidence._filter_dirty_to_task_scope(files, "task-2472+1")
    assert "memory/tasks/task-2472+1.md" in scoped, "본 task 정확 일치 누락"
    assert "memory/tasks/task-2472.md" in scoped, "본 task base 매치 누락"
    assert "memory/tasks/task-2472+10.md" not in scoped, (
        "task-2472+10 이 task-2472+1 로 오인 — 경계 검사 미흡"
    )
    assert "memory/tasks/task-24720.md" not in scoped, (
        "task-24720 이 task-2472 로 오인 — 경계 검사 미흡"
    )
    assert "memory/tasks/foo-task-2472-bar.md" not in scoped, (
        "단어 중간 task-2472 가 매치됨 — 앞쪽 경계 미흡"
    )


def test_filter_dirty_to_task_scope_boundary_dev1(dev1_git_evidence):
    """dev1 도 동일 경계 보장."""
    files = ["memory/tasks/task-2472+10.md", "memory/tasks/task-2472+1.md"]
    scoped = dev1_git_evidence._filter_dirty_to_task_scope(files, "task-2472+1")
    assert "memory/tasks/task-2472+1.md" in scoped
    assert "memory/tasks/task-2472+10.md" not in scoped


def test_verify_main_repo_fallback_ignores_other_task_dirty_shared(
    shared_git_evidence, tmp_path, monkeypatch
):
    """fallback 모드에서 다른 task의 dirty 파일은 무시 (현재 task FAIL 안 됨).

    회귀 7-6: main repo dirty와 worktree dirty 분리.
    """
    repo = _init_repo(tmp_path)
    # task 파일 + reports 디렉토리 생성 (verify 동작에 필요)
    (repo / "memory" / "tasks").mkdir(parents=True)
    (repo / "memory" / "tasks" / "task-test+1.md").write_text("# test")
    # task-test+1 커밋 추가 (COMMIT_EXISTS 위해)
    (repo / "src.py").write_text("x = 1")
    subprocess.run(["git", "add", "."], cwd=repo, check=True)
    subprocess.run(
        ["git", "commit", "-q", "-m", "[task-test+1] add"], cwd=repo, check=True
    )

    # 다른 task의 변경(unstaged)
    (repo / "other_task_file.py").write_text("from other")
    # 본 task의 변경 없음 — main repo는 dirty이지만 본 task scope는 clean

    # env_var 미설정 → fallback path
    monkeypatch.delenv("PROJECT_PATH", raising=False)
    monkeypatch.delenv("WORKTREE_PATH", raising=False)

    result = shared_git_evidence.verify("task-test+1", str(repo))
    # NO_UNCOMMITTED는 PASS여야 함 (다른 task 변경 무시)
    no_uncommitted_failed = any(
        "FAIL NO_UNCOMMITTED" in d for d in result["details"]
    )
    resolved_via_present = any("resolved_via=" in d for d in result["details"])
    assert resolved_via_present, "resolved_via= 라벨이 details에 없음"
    assert not no_uncommitted_failed, (
        f"main repo의 다른 task dirty로 NO_UNCOMMITTED FAIL — 분리 판단 미동작: {result['details']}"
    )


def test_verify_worktree_dirty_still_fails_shared(shared_git_evidence, tmp_path):
    """worktree 자체 dirty는 여전히 FAIL (회귀 7-6 다른 면).

    PROJECT_PATH=worktree로 설정하면 worktree dirty 검출 시 FAIL해야 한다.
    """
    repo = _init_repo(tmp_path)
    (repo / "memory" / "tasks").mkdir(parents=True)
    (repo / "memory" / "tasks" / "task-wt+1.md").write_text("# wt")
    # 본 task scope의 코드 파일 (system_auto_files 외 — task_id 포함 파일명)
    (repo / "task-wt+1-impl.py").write_text("y = 1")
    subprocess.run(["git", "add", "."], cwd=repo, check=True)
    subprocess.run(
        ["git", "commit", "-q", "-m", "[task-wt+1] add"], cwd=repo, check=True
    )
    # 커밋된 파일을 수정 → modified 상태가 git diff에 잡힘
    (repo / "task-wt+1-impl.py").write_text("y = 2  # task-wt+1 worktree dirty")

    os.environ["PROJECT_PATH"] = str(repo)
    try:
        result = shared_git_evidence.verify("task-wt+1", str(repo))
    finally:
        os.environ.pop("PROJECT_PATH", None)

    # worktree 모드에서 task scope 내 dirty가 있으므로 FAIL 유지 — forbidden_actions(dirty_check_removal) 방어
    resolved_via_present = any("resolved_via=env_var" in d for d in result["details"])
    assert resolved_via_present, f"resolved_via=env_var 라벨 누락: {result['details']}"
    assert result["status"] == "FAIL", (
        f"worktree 자체 dirty인데 FAIL이 아님 (dirty_check_removal 위반): {result['details']}"
    )
    assert any(
        "FAIL NO_UNCOMMITTED" in d for d in result["details"]
    ), f"NO_UNCOMMITTED FAIL 사유 누락: {result['details']}"
