"""tests/taskctl/test_evidence.py
Evidence 9종 파일 생성 검증 (task-2467)

벨레스(개발6팀 테스터) 작성. 스바로그의 구현 완료 전 선작성(TDD).

케이스:
    1. test_evidence_dir_created_on_init
    2. test_evidence_files_have_required_fields
    3. test_evidence_pr_open_records_pr_author
"""
from __future__ import annotations

import json
import os
import subprocess
from pathlib import Path

import pytest

WORKSPACE = Path("/home/jay/workspace/.worktrees/task-2467-dev6")
TASKCTL = WORKSPACE / "scripts" / "taskctl.py"

# 명세 §5 필수 필드 (공통)
EVIDENCE_REQUIRED_FIELDS = {"command", "exit_code", "timestamp", "actor"}
# "sha", "pr_number"는 특정 단계에서만 필수 — 개별 검증


def _isolated_workspace(tmp_path: Path) -> dict:
    env = {**os.environ}
    env["WORKSPACE_ROOT"] = str(tmp_path)
    (tmp_path / ".tasks" / "state").mkdir(parents=True, exist_ok=True)
    (tmp_path / ".tasks" / "evidence").mkdir(parents=True, exist_ok=True)
    (tmp_path / "memory" / "events").mkdir(parents=True, exist_ok=True)
    (tmp_path / "memory" / "orchestration-audit").mkdir(parents=True, exist_ok=True)
    return env


def _run(args: list[str], env: dict) -> subprocess.CompletedProcess:
    return subprocess.run(
        ["python3", str(TASKCTL)] + args,
        capture_output=True, text=True, env=env, timeout=30,
    )


def _state(tmp_path: Path, task_id: str) -> dict:
    p = tmp_path / ".tasks" / "state" / f"{task_id}.json"
    assert p.exists(), f"state 파일 없음: {p}"
    return json.loads(p.read_text())


# ---------------------------------------------------------------------------
# 케이스 1: init 후 .tasks/evidence/<task-id>/ 디렉토리 + start.json 생성
# ---------------------------------------------------------------------------

def test_evidence_dir_created_on_init(tmp_path):
    """init 후 evidence 디렉토리와 start.json이 생성되어야 한다.

    명세 §5 Table #1: start.json — command, actor, ts, branch, head_sha.
    신규 구현에서 init/run 명령이 start.json을 생성.
    MVP에서는 evidence dict가 state 파일 내에 포함되므로 soft check.
    """
    env = _isolated_workspace(tmp_path)
    task_id = "task-evidence-01"

    proc = _run(["init", task_id], env)
    assert proc.returncode == 0, f"init 실패: {proc.stderr}"

    # state 파일 존재 확인
    state = _state(tmp_path, task_id)
    assert state["current_state"] == "CREATED"
    assert "evidence" in state, "state에 evidence 필드 없음"

    # 신규 구현: evidence 디렉토리 방식
    evidence_dir = tmp_path / ".tasks" / "evidence" / task_id
    start_json = evidence_dir / "start.json"

    if evidence_dir.exists() and start_json.exists():
        # 신규 방식 — 필드 검증
        ev = json.loads(start_json.read_text())
        # 명세 §5 start.json 핵심 필드
        for field in ("command", "actor", "ts"):
            alt = "timestamp" if field == "ts" else field
            assert field in ev or alt in ev, (
                f"start.json에 '{field}' 또는 '{alt}' 누락: {list(ev.keys())}"
            )
    else:
        # MVP 방식: state.evidence dict 검증
        ev = state["evidence"]
        # MVP에서는 init 직후 evidence가 비어있어도 허용
        # (run/verify 실행 후 채워짐)
        assert isinstance(ev, dict), "evidence가 dict가 아님"

    # run까지 진행 후 start.json 재확인 (신규: run 시 생성)
    for cmd in [["dispatch", task_id], ["ack", task_id], ["run", task_id]]:
        _run(cmd, env)

    if start_json.exists():
        ev = json.loads(start_json.read_text())
        required = {"command", "actor"}
        ts_variants = {"ts", "timestamp", "started_at"}
        assert required <= set(ev.keys()), f"start.json 필수 필드 누락: {set(ev.keys())}"
        assert ts_variants & set(ev.keys()), f"start.json에 timestamp 관련 필드 없음"


# ---------------------------------------------------------------------------
# 케이스 2: 각 evidence 파일에 공통 필수 필드 존재
# ---------------------------------------------------------------------------

def test_evidence_files_have_required_fields(tmp_path):
    """생성된 evidence 파일에 명세 §5 공통 필수 필드가 존재해야 함.

    공통 필수 필드: command, actor, timestamp(또는 ts), exit_code
    pr-open 이후 생성되는 evidence 파일을 샘플 검증.
    """
    env = _isolated_workspace(tmp_path)
    task_id = "task-evidence-02"

    # init → dispatch → ack → run → pr-open 체인
    for cmd in [["init", task_id], ["dispatch", task_id],
                ["ack", task_id], ["run", task_id],
                ["pr-open", task_id, "--pr", "200"]]:
        proc = _run(cmd, env)
        assert proc.returncode == 0, f"명령 실패: {cmd} → {proc.stderr}"

    evidence_dir = tmp_path / ".tasks" / "evidence" / task_id

    # 신규 구현에서 evidence 파일이 생성된 경우 검증
    if evidence_dir.exists():
        json_files = list(evidence_dir.glob("*.json"))
        if json_files:
            for ev_file in json_files:
                ev = json.loads(ev_file.read_text())
                ts_variants = {"ts", "timestamp", "started_at", "created_at"}
                exit_variants = {"exit_code", "returncode"}

                assert ts_variants & set(ev.keys()), (
                    f"{ev_file.name}에 timestamp 관련 필드 없음: {list(ev.keys())}"
                )
                assert exit_variants & set(ev.keys()), (
                    f"{ev_file.name}에 exit_code 관련 필드 없음: {list(ev.keys())}"
                )
        else:
            pytest.xfail("evidence 디렉토리 존재하나 파일 없음 (스바로그 대기)")
    else:
        # MVP: state.evidence dict로 대체 검증
        state = _state(tmp_path, task_id)
        ev = state.get("evidence", {})
        # pr-open 후 pr_number 기록 확인
        assert ev.get("pr_number") == 200, (
            f"pr_number 미기록: {ev.get('pr_number')}"
        )
        # exit_codes 기록 구조 확인
        assert isinstance(ev.get("exit_codes", {}), dict), "exit_codes가 dict가 아님"

    # verify 실행 후 추가 evidence 확인
    _run(["verify", task_id], env)

    state = _state(tmp_path, task_id)
    ev = state.get("evidence", {})
    assert "guard_sh_result" in ev, "verify 후 guard_sh_result 미기록"
    assert "qc_report_guard_result" in ev, "verify 후 qc_report_guard_result 미기록"
    assert isinstance(ev.get("exit_codes", {}), dict), "exit_codes 구조 이상"


# ---------------------------------------------------------------------------
# 케이스 3: pr-open 후 evidence/pr-open.json에 pr_author/pr_number 박제
# ---------------------------------------------------------------------------

def test_evidence_pr_open_records_pr_author(tmp_path):
    """pr-open 후 evidence/pr-open.json에 pr_author 및 pr_number가 박제.

    명세 §5 Table #3: pr-open.json 핵심 필드 — pr_number, pr_author, base_sha, head_sha.
    bot token 없는 환경에서 pr_author는 current_user로 기록됨 (pr-lifecycle-spec §2.2).
    """
    env = _isolated_workspace(tmp_path)
    task_id = "task-evidence-03"
    pr_number = 300

    for cmd in [["init", task_id], ["dispatch", task_id],
                ["ack", task_id], ["run", task_id]]:
        assert _run(cmd, env).returncode == 0

    proc = _run(["pr-open", task_id, "--pr", str(pr_number)], env)
    assert proc.returncode == 0, f"pr-open 실패: {proc.stderr}"

    # 신규 구현: evidence/pr-open.json 파일 검증
    pr_open_ev = tmp_path / ".tasks" / "evidence" / task_id / "pr-open.json"

    if pr_open_ev.exists():
        ev = json.loads(pr_open_ev.read_text())

        # pr_number 일치 확인
        assert ev.get("pr_number") == pr_number, (
            f"pr-open.json pr_number 불일치: {ev.get('pr_number')} != {pr_number}"
        )

        # pr_author 기록 확인 (bot 또는 current_user)
        assert "pr_author" in ev, f"pr-open.json에 pr_author 누락: {list(ev.keys())}"
        assert ev["pr_author"] is not None and ev["pr_author"] != "", (
            f"pr_author가 비어있음: {ev['pr_author']}"
        )

        # created_by 필드 (bot 여부)
        if "created_by" in ev:
            # bot token 미발급 시 'manual' 또는 'current_user' 허용 (pr-lifecycle-spec §2.2)
            assert ev["created_by"] in {"bot", "current_user", "human", "taskctl", "manual"}, (
                f"created_by 값 이상: {ev['created_by']}"
            )

        # 명세 §3 Table #3: pr-open.json 추가 필드
        ts_variants = {"ts", "timestamp", "created_at"}
        assert ts_variants & set(ev.keys()), (
            f"pr-open.json에 timestamp 관련 필드 없음: {list(ev.keys())}"
        )

    elif (tmp_path / ".tasks" / "evidence" / task_id).exists():
        pytest.xfail("evidence 디렉토리 존재하나 pr-open.json 없음 (스바로그 대기)")
    else:
        # MVP: state.evidence dict로 대체 검증
        state = _state(tmp_path, task_id)
        ev = state.get("evidence", {})
        assert ev.get("pr_number") == pr_number, (
            f"MVP evidence.pr_number 불일치: {ev.get('pr_number')}"
        )
        # MVP에서 pr_author는 별도 기록 없을 수 있음 (xfail)
        if "pr_author" not in ev:
            pytest.xfail(
                "MVP에서 pr_author 미기록 — 신규 구현에서 추가 예정 (스바로그 대기)"
            )
        else:
            assert ev["pr_author"] is not None, "pr_author가 None"
