"""tests/taskctl/test_admin_override.py
Admin Override audit log + cap 검증 (task-2467)

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

케이스:
    1. test_admin_override_records_audit_log — chairman email로 --admin → audit log 1줄
    2. test_admin_override_non_chairman_blocked — 다른 사용자 --admin → FAIL
    3. test_admin_override_hard_cap — 90일 내 5건 채운 후 6번째 → HARD_CAP_EXCEEDED
"""
from __future__ import annotations

import hashlib
import json
import os
import subprocess
from datetime import datetime, timedelta, timezone
from pathlib import Path

import pytest

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

# 명세 §6.1: chairman email
CHAIRMAN_EMAIL = "jonghyuk.jeon@gmail.com"
AUDIT_LOG_RELATIVE = Path("memory") / "orchestration-audit" / "admin-override.jsonl"


def _isolated_workspace(tmp_path: Path, chairman_email: str = CHAIRMAN_EMAIL) -> dict:
    """격리된 workspace + git config user.email 설정."""
    env = {**os.environ}
    env["WORKSPACE_ROOT"] = str(tmp_path)
    env["GIT_AUTHOR_EMAIL"] = chairman_email
    env["GIT_COMMITTER_EMAIL"] = chairman_email
    # git config override (taskctl이 git config user.email을 읽음)
    env["GIT_CONFIG_GLOBAL"] = str(tmp_path / ".gitconfig")

    (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)

    # 임시 gitconfig 생성
    gitconfig = tmp_path / ".gitconfig"
    gitconfig.write_text(
        f"[user]\n\temail = {chairman_email}\n\tname = Test Chairman\n"
    )

    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())


def _force_state_with_checksum(tmp_path: Path, task_id: str, target_state: str) -> None:
    """테스트 전용: state 강제 설정 + 체크섬 재계산."""
    p = tmp_path / ".tasks" / "state" / f"{task_id}.json"
    state = json.loads(p.read_text())
    state["current_state"] = target_state
    state.pop("_checksum", None)
    payload = {k: v for k, v in state.items() if k != "_checksum"}
    canon = json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
    checksum = hashlib.sha256(canon.encode("utf-8")).hexdigest()
    state["_checksum"] = checksum
    p.write_text(json.dumps(state, ensure_ascii=False, indent=2))


def _setup_to_human_approved(tmp_path: Path, env: dict, task_id: str,
                              pr_number: int = 1) -> bool:
    """HUMAN_APPROVED 상태까지 강제 진입."""
    for cmd in [["init", task_id], ["dispatch", task_id],
                ["ack", task_id], ["run", task_id],
                ["pr-open", task_id, "--pr", str(pr_number)]]:
        if _run(cmd, env).returncode != 0:
            return False

    # HUMAN_APPROVED 강제 설정
    _force_state_with_checksum(tmp_path, task_id, "HUMAN_APPROVED")
    # human_approved 플래그도 설정
    p = tmp_path / ".tasks" / "state" / f"{task_id}.json"
    state = json.loads(p.read_text())
    state["human_approved"] = True
    state.pop("_checksum", None)
    payload = {k: v for k, v in state.items() if k != "_checksum"}
    canon = json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
    state["_checksum"] = hashlib.sha256(canon.encode("utf-8")).hexdigest()
    p.write_text(json.dumps(state, ensure_ascii=False, indent=2))

    return _state(tmp_path, task_id)["current_state"] == "HUMAN_APPROVED"


def _read_audit_log(tmp_path: Path) -> list[dict]:
    """audit log JSONL 읽기."""
    audit_log = tmp_path / AUDIT_LOG_RELATIVE
    if not audit_log.exists():
        return []
    lines = [ln.strip() for ln in audit_log.read_text().splitlines() if ln.strip()]
    result = []
    for ln in lines:
        try:
            result.append(json.loads(ln))
        except json.JSONDecodeError:
            pass
    return result


def _prepopulate_audit_log(tmp_path: Path, count: int, days_ago_max: int = 30) -> None:
    """audit log에 count건 미리 기록 (rolling window 내)."""
    audit_log = tmp_path / AUDIT_LOG_RELATIVE
    audit_log.parent.mkdir(parents=True, exist_ok=True)
    now = datetime.now(timezone.utc)
    with open(audit_log, "a", encoding="utf-8") as f:
        for i in range(count):
            ts = now - timedelta(days=i % days_ago_max + 1)
            record = {
                "ts": ts.strftime("%Y-%m-%dT%H:%M:%SZ"),
                "task_id": f"task-prepop-{i:03d}",
                "pr_number": 1000 + i,
                "actor": "chairman",
                "reason": f"prepopulated test record #{i}",
                "head_sha": f"abc{i:04d}",
                "bypassed_checks": ["test"],
                "soft_count_this_month": i + 1,
                "hard_count_this_quarter": i + 1,
            }
            f.write(json.dumps(record, ensure_ascii=False) + "\n")


# ---------------------------------------------------------------------------
# 케이스 1: admin override → audit log 1줄 추가 + ADMIN_OVERRIDE_USED 상태
# ---------------------------------------------------------------------------

def test_admin_override_records_audit_log(tmp_path):
    """chairman email로 taskctl merge --admin → audit log 1줄 + ADMIN_OVERRIDE_USED.

    신규 구현 필요. MVP에서는 --admin 플래그 미구현 시 xfail.
    """
    env = _isolated_workspace(tmp_path, chairman_email=CHAIRMAN_EMAIL)
    task_id = "task-adminoverride-01"
    pr_number = 701

    ok = _setup_to_human_approved(tmp_path, env, task_id, pr_number)
    if not ok:
        pytest.skip("HUMAN_APPROVED 상태 진입 실패")

    # admin override 시도
    proc = _run(["merge", task_id, "--admin", "--dry-run"], env)

    if proc.returncode == 0:
        # merge --admin 명령 성공 — audit log 기록 여부 검증
        entries = _read_audit_log(tmp_path)

        if len(entries) == 0:
            # MVP: --admin 플래그 인식하지만 audit log 미구현 → xfail
            pytest.xfail(
                "--admin 플래그 인식되나 audit log 미구현 (스바로그 대기): "
                f"merge 성공했지만 audit log 비어있음 (state={_state(tmp_path, task_id)['current_state']})"
            )

        last = entries[-1]

        # 필수 필드 검증 (명세 §6.3)
        for field in ("ts", "task_id", "actor", "reason", "hard_count_this_quarter"):
            assert field in last, f"audit log에 '{field}' 누락: {list(last.keys())}"

        assert last.get("task_id") == task_id, (
            f"audit log task_id 불일치: {last.get('task_id')}"
        )
        assert last.get("actor") in {"chairman", CHAIRMAN_EMAIL, "chairman (jonghyuk.jeon@gmail.com)"}, (
            f"audit log actor 이상: {last.get('actor')}"
        )

        # 상태 확인
        state = _state(tmp_path, task_id)
        current = state["current_state"]
        assert current in {"ADMIN_OVERRIDE_USED", "MERGED", "DONE"}, (
            f"admin override 후 예상 외 상태: {current}"
        )

    else:
        # MVP: --admin 플래그 미구현 또는 chairman 검증 실패 → xfail
        pytest.xfail(
            f"--admin 플래그 미구현 또는 chairman 검증 실패 (스바로그 대기): "
            f"{proc.stderr[:200]}"
        )


# ---------------------------------------------------------------------------
# 케이스 2: non-chairman이 --admin 시도 → FAIL
# ---------------------------------------------------------------------------

def test_admin_override_non_chairman_blocked(tmp_path):
    """다른 사용자가 --admin 시도 → FAIL ("chairman only").

    git config user.email을 다른 email로 설정.
    """
    non_chairman_email = "non-chairman@example.com"
    env = _isolated_workspace(tmp_path, chairman_email=non_chairman_email)
    task_id = "task-adminoverride-02"
    pr_number = 702

    ok = _setup_to_human_approved(tmp_path, env, task_id, pr_number)
    if not ok:
        pytest.skip("HUMAN_APPROVED 상태 진입 실패")

    proc = _run(["merge", task_id, "--admin", "--dry-run"], env)

    if proc.returncode == 0:
        # 성공하면 안 됨 (chairman이 아닌데 admin override)
        pytest.fail(
            f"non-chairman이 --admin 성공: returncode=0\n"
            f"stdout: {proc.stdout}\nstderr: {proc.stderr}"
        )
    else:
        # 차단 확인 — "chairman" 관련 메시지 또는 --admin 미구현 메시지
        combined = proc.stderr + proc.stdout
        is_chairman_blocked = (
            "chairman" in combined.lower()
            or "unauthorized" in combined.lower()
            or "permission" in combined.lower()
            or "only" in combined.lower()
        )
        is_not_implemented = (
            "--admin" in combined.lower()
            or "unknown" in combined.lower()
            or "unrecognized" in combined.lower()
        )

        if is_not_implemented and not is_chairman_blocked:
            pytest.xfail("--admin 플래그 미구현 (스바로그 대기)")

        # 차단이든 미구현이든 exit != 0이면 OK
        assert proc.returncode != 0


# ---------------------------------------------------------------------------
# 케이스 3: hard cap (90일 내 5건) 초과 → HARD_CAP_EXCEEDED
# ---------------------------------------------------------------------------

def test_admin_override_hard_cap(tmp_path):
    """audit log에 90일 내 5건 미리 채운 후 6번째 시도 → FAIL (HARD_CAP_EXCEEDED).

    명세 §6.2: hard_count >= 5 → "HARD_CAP_EXCEEDED".
    """
    env = _isolated_workspace(tmp_path, chairman_email=CHAIRMAN_EMAIL)
    task_id = "task-adminoverride-03"
    pr_number = 703

    ok = _setup_to_human_approved(tmp_path, env, task_id, pr_number)
    if not ok:
        pytest.skip("HUMAN_APPROVED 상태 진입 실패")

    # 90일 이내 5건 미리 채우기
    _prepopulate_audit_log(tmp_path, count=5, days_ago_max=89)
    entries_before = _read_audit_log(tmp_path)
    assert len(entries_before) == 5, f"prepopulate 실패: {len(entries_before)}건"

    # 6번째 시도
    proc = _run(["merge", task_id, "--admin", "--dry-run"], env)

    if proc.returncode == 0:
        # 성공하면 안 됨 (hard cap 초과)
        entries_after = _read_audit_log(tmp_path)
        if len(entries_after) > 5:
            pytest.fail(
                f"hard cap 초과에도 admin override 성공: "
                f"audit log {len(entries_after)}건\n"
                f"stdout: {proc.stdout}\nstderr: {proc.stderr}"
            )
        else:
            # 성공했지만 audit log 미기록 — 부분 구현
            pytest.xfail("hard cap 검사 미구현 (스바로그 대기)")
    else:
        combined = proc.stderr + proc.stdout
        # "HARD_CAP_EXCEEDED" 또는 관련 메시지 확인
        is_cap_exceeded = (
            "HARD_CAP" in combined.upper()
            or "cap" in combined.lower()
            or "exceeded" in combined.lower()
            or "limit" in combined.lower()
            or "5" in combined  # 횟수 언급
        )
        is_not_implemented = (
            "--admin" in combined.lower()
            or "unknown" in combined.lower()
            or "unrecognized" in combined.lower()
        )

        if is_not_implemented and not is_cap_exceeded:
            pytest.xfail("--admin 플래그 미구현 (스바로그 대기)")

        if not is_cap_exceeded and not is_not_implemented:
            # chairman 검증 실패 등 다른 이유 — soft fail
            pytest.xfail(
                f"hard cap 관련 메시지 없음 (chairman 검증 실패 가능성): {combined[:300]}"
            )

        # 차단 확인 OK
        assert proc.returncode != 0

        # audit log 건수는 5건 유지 (6번째 기록 안 됨)
        entries_after = _read_audit_log(tmp_path)
        assert len(entries_after) <= 5, (
            f"hard cap 초과 후에도 audit log에 기록됨: {len(entries_after)}건"
        )
