"""tests/mixed_commit/test_detector.py — mixed_commit_detector 시나리오 테스트.

task-2459 Phase 2-C / dev5팀 닌기르수 (테스터).

scripts/mixed_commit_detector.py 의 5단계 알고리즘과 freeze/evidence 부수효과를
임시 git repo + subprocess 호출로 검증.

원칙:
  - 모듈 코드 read-only.
  - subprocess 로만 호출 (import 하지 않음 — CLI 동작 검증).
  - 각 시나리오는 독립 fixture 사용.
"""
from __future__ import annotations

import json
import os
import subprocess
import sys
from pathlib import Path

import pytest


REPO_ROOT = Path(__file__).resolve().parents[2]
DETECTOR = REPO_ROOT / "scripts" / "mixed_commit_detector.py"


# ---------------------------------------------------------------------------
# 헬퍼
# ---------------------------------------------------------------------------

def _git(*args: str, cwd: Path) -> subprocess.CompletedProcess:
    return subprocess.run(
        ["git", *args], cwd=str(cwd), capture_output=True, text=True, check=False
    )


def make_repo(tmp_path: Path) -> tuple[Path, str]:
    """임시 git repo 초기화. 초기 commit 1개 생성하여 base ref 로 사용.

    Returns:
        (repo_path, base_sha): repo 경로와 base commit sha (origin/main 대용).
    """
    repo = tmp_path / "repo"
    repo.mkdir()
    _git("init", "-q", "-b", "main", cwd=repo)
    _git("config", "user.email", "test@example.com", cwd=repo)
    _git("config", "user.name", "tester", cwd=repo)
    _git("config", "commit.gpgsign", "false", cwd=repo)

    # base commit
    (repo / "README.md").write_text("base\n", encoding="utf-8")
    _git("add", "README.md", cwd=repo)
    _git("commit", "-q", "-m", "base: initial", cwd=repo)
    res = _git("rev-parse", "HEAD", cwd=repo)
    base_sha = res.stdout.strip()
    return repo, base_sha


def add_commit(repo: Path, message: str, file_name: str | None = None) -> str:
    """repo 에 commit 추가. file 내용은 매 commit 다르도록 보장."""
    fname = file_name or f"f_{len(message)}_{os.urandom(4).hex()}.txt"
    (repo / fname).write_text(message + "\n", encoding="utf-8")
    _git("add", fname, cwd=repo)
    res = _git("commit", "-q", "-m", message, cwd=repo)
    if res.returncode != 0:
        raise RuntimeError(f"commit failed: {res.stderr}")
    sha_res = _git("rev-parse", "HEAD", cwd=repo)
    return sha_res.stdout.strip()


def run_detector(
    task_id: str,
    *extra_args: str,
    cwd: Path,
    workspace: Path | None = None,
) -> subprocess.CompletedProcess:
    """detector subprocess 실행. workspace 미지정 시 cwd 사용."""
    workspace = workspace or cwd
    cmd = [
        sys.executable,
        str(DETECTOR),
        task_id,
        "--workspace",
        str(workspace),
        *extra_args,
    ]
    return subprocess.run(
        cmd, cwd=str(cwd), capture_output=True, text=True, check=False
    )


# ---------------------------------------------------------------------------
# fixture
# ---------------------------------------------------------------------------

@pytest.fixture
def repo_with_base(tmp_path: Path) -> tuple[Path, str]:
    """초기 commit 1개를 가진 임시 git repo."""
    return make_repo(tmp_path)


# ---------------------------------------------------------------------------
# 시나리오
# ---------------------------------------------------------------------------

def test_s1_clean_single_task(repo_with_base):
    """S1 — clean: 단일 task 토큰만, alien 없음 → exit 0, freeze/evidence 미생성."""
    repo, base_sha = repo_with_base
    add_commit(repo, "[task-2459] feat: A")

    res = run_detector("task-2459", "--base", base_sha, cwd=repo)

    assert res.returncode == 0, f"stderr={res.stderr}\nstdout={res.stdout}"
    # freeze / evidence 미생성
    frozen = repo / ".tasks" / "locks" / "task-2459.frozen"
    assert not frozen.exists(), "clean 인데 freeze 마커 생성됨"
    ev_dir = repo / ".tasks" / "evidence" / "task-2459"
    if ev_dir.exists():
        evs = list(ev_dir.glob("mixed-commit-*.json"))
        assert evs == [], f"clean 인데 evidence 생성됨: {evs}"
    # stdout JSON 검증
    payload = json.loads(res.stdout)
    assert payload["status"] in ("clean", "clean_with_untagged")
    assert payload["task_id"] == "task-2459"


def test_s2_mixed_two_tasks(repo_with_base):
    """S2 — mixed: 2 task 혼합 → exit 1, freeze + evidence 생성."""
    repo, base_sha = repo_with_base
    add_commit(repo, "[task-2459] A")
    add_commit(repo, "[task-9999] B")

    res = run_detector("task-2459", "--base", base_sha, cwd=repo)

    assert res.returncode == 1, f"stdout={res.stdout}\nstderr={res.stderr}"
    frozen = repo / ".tasks" / "locks" / "task-2459.frozen"
    assert frozen.exists(), "mixed 감지 시 freeze 마커가 생성되어야 함"

    # frozen 내용
    frozen_data = json.loads(frozen.read_text())
    assert "task-2459" in frozen_data["mixed_tasks"]
    assert "task-9999" in frozen_data["mixed_tasks"]
    assert frozen_data["alien_tasks"] == ["task-9999"]

    # evidence
    ev_dir = repo / ".tasks" / "evidence" / "task-2459"
    evs = list(ev_dir.glob("mixed-commit-*.json"))
    assert len(evs) >= 1


def test_s3_untagged_commits_not_alien(repo_with_base):
    """S3 — token 없는 commit 은 alien 으로 간주하지 않음 → exit 0."""
    repo, base_sha = repo_with_base
    add_commit(repo, "random commit message without token")
    add_commit(repo, "[task-2459] A")

    res = run_detector("task-2459", "--base", base_sha, cwd=repo)

    assert res.returncode == 0, f"stdout={res.stdout}\nstderr={res.stderr}"
    frozen = repo / ".tasks" / "locks" / "task-2459.frozen"
    assert not frozen.exists()
    payload = json.loads(res.stdout)
    # untagged 1개 이상 → status == clean_with_untagged
    assert payload["status"] == "clean_with_untagged"
    assert payload["untagged_commit_count"] == 1


def test_s4_freeze_marker_schema(repo_with_base):
    """S4 — freeze 마커 필수 필드 검증."""
    repo, base_sha = repo_with_base
    add_commit(repo, "[task-2459] A")
    add_commit(repo, "[task-9999] B")

    res = run_detector("task-2459", "--base", base_sha, cwd=repo)
    assert res.returncode == 1

    frozen = repo / ".tasks" / "locks" / "task-2459.frozen"
    data = json.loads(frozen.read_text())

    # 필수 필드
    for key in (
        "task_id",
        "mixed_tasks",
        "alien_tasks",
        "base_sha",
        "head_sha",
        "commits",
    ):
        assert key in data, f"freeze marker 필드 누락: {key}"

    # ISO8601 timestamp (frozen_at 또는 verified_at — spec v1.0 둘 다 가능)
    ts_field = data.get("frozen_at") or data.get("verified_at")
    assert ts_field is not None, "타임스탬프 필드 부재 (frozen_at/verified_at)"
    assert ts_field.endswith("Z") and "T" in ts_field

    # commits 항목 구조
    assert isinstance(data["commits"], list) and data["commits"]
    for c in data["commits"]:
        assert "sha" in c and "subject" in c and "tokens" in c
        assert isinstance(c["tokens"], list)


def test_s5_evidence_escalation_message(repo_with_base):
    """S5 — evidence JSON 에 escalation_message 존재."""
    repo, base_sha = repo_with_base
    add_commit(repo, "[task-2459] A")
    add_commit(repo, "[task-9999] foreign")

    res = run_detector("task-2459", "--base", base_sha, cwd=repo)
    assert res.returncode == 1

    ev_dir = repo / ".tasks" / "evidence" / "task-2459"
    evs = sorted(ev_dir.glob("mixed-commit-*.json"))
    assert len(evs) >= 1

    ev = json.loads(evs[-1].read_text())
    assert "escalation_message" in ev, "evidence 에 escalation_message 누락"
    assert "task-9999" in ev["escalation_message"]
    assert ev.get("freeze_marker_path", "").endswith("task-2459.frozen")


def test_s6_json_dry_run_no_side_effects(repo_with_base):
    """S6 — `--json` dry-run: 감지하지만 파일 생성 안 함."""
    repo, base_sha = repo_with_base
    add_commit(repo, "[task-2459] A")
    add_commit(repo, "[task-9999] B")

    res = run_detector("task-2459", "--base", base_sha, "--json", cwd=repo)
    assert res.returncode == 1, f"stdout={res.stdout}\nstderr={res.stderr}"

    # stdout 이 valid JSON
    payload = json.loads(res.stdout)
    assert payload["mixed"] is True
    assert "task-9999" in payload["alien_tasks"]

    # 부수효과 없음
    frozen = repo / ".tasks" / "locks" / "task-2459.frozen"
    assert not frozen.exists(), "dry-run 에서 freeze 마커가 생성됨"
    ev_dir = repo / ".tasks" / "evidence" / "task-2459"
    if ev_dir.exists():
        evs = list(ev_dir.glob("mixed-commit-*.json"))
        assert evs == [], "dry-run 에서 evidence 가 생성됨"


def test_s7_inline_alien_token_in_subject(repo_with_base):
    """S7 — subject 본문 안의 토큰도 alien 감지 (prefix anchor 금지 검증)."""
    repo, base_sha = repo_with_base
    add_commit(repo, "[task-2459] step1")
    # prefix 가 본 task 인데 본문에 alien 토큰 포함 → alien 감지되어야 함
    add_commit(repo, "[task-2459] follow up to [task-9999] cleanup")

    res = run_detector("task-2459", "--base", base_sha, cwd=repo)

    assert res.returncode == 1, (
        f"본문 토큰이 감지되지 않음 (prefix anchor 사용 의심)\n"
        f"stdout={res.stdout}\nstderr={res.stderr}"
    )
    frozen = repo / ".tasks" / "locks" / "task-2459.frozen"
    assert frozen.exists()
    data = json.loads(frozen.read_text())
    assert "task-9999" in data["alien_tasks"]


def test_empty_branch_returns_status_empty(repo_with_base):
    """추가 — base..HEAD 비어 있으면 status=empty, exit 0."""
    repo, base_sha = repo_with_base
    # 커밋 추가 없이 base==HEAD
    res = run_detector("task-2459", "--base", base_sha, cwd=repo)

    assert res.returncode == 0
    payload = json.loads(res.stdout)
    assert payload["status"] == "empty"
    assert payload["n_commits"] == 0
