"""4-source validator 회귀 테스트 (task-2694+1 MT-6).

Codex suggestion 4 — 6 fail + 1 pass dogfood case.
"""

from __future__ import annotations

import json
import os

import pytest

from utils.normal_callback_registration_validator import (  # pyright: ignore[reportMissingImports]
    ANU_KEY,
    FAIL,
    NON_AUTHORITATIVE,
    PASS,
    validate_callback_registration,
)


# --------------------------------------------------------------------------- #
# Fixtures
# --------------------------------------------------------------------------- #


@pytest.fixture
def tmp_envelope(tmp_path):
    """envelope JSON 작성 factory."""

    def _make(
        task_id: str = "task-test",
        schedule_id: str = "sid-test-001",
        schedule_type: str = "cron_once",
        owner_key: str = ANU_KEY,
        chair_facing_sid: str = "53e89540-5bed-4692-a726-ed857820758a",
        chat_id: str = "6937032012",
        extra=None,
    ) -> str:
        env = {
            "schema": "schemas.anu_normal_callback_envelope_v1",
            "task_id": task_id,
            "schedule_id": schedule_id,
            "schedule_type": schedule_type,
            "owner_key": owner_key,
            "chat_id": chat_id,
            "chair_facing_sid": chair_facing_sid,
            "emitted_at": "2026-05-27T00:00:00Z",
        }
        if extra:
            env.update(extra)
        path = tmp_path / f"{task_id}.envelope.json"
        path.write_text(json.dumps(env), encoding="utf-8")
        return str(path)

    return _make


@pytest.fixture
def tmp_schedule_history_dir(tmp_path):
    sh_dir = tmp_path / "schedule_history"
    sh_dir.mkdir()
    return str(sh_dir)


def _write_history(sh_dir: str, sid: str, status: str = "ok") -> str:
    log = os.path.join(sh_dir, f"{sid}.log")
    with open(log, "w", encoding="utf-8") as f:
        f.write(json.dumps({"status": status, "schedule_id": sid}) + "\n")
    return log


@pytest.fixture
def tmp_inbound_dir(tmp_path):
    d = tmp_path / "inbound"
    d.mkdir()
    return str(d)


# --------------------------------------------------------------------------- #
# Test 1: envelope-only PASS 금지 (envelope 파일 없음)
# --------------------------------------------------------------------------- #


def test_envelope_missing_fails(tmp_path, tmp_schedule_history_dir, tmp_inbound_dir):
    """envelope 파일 자체가 없으면 4-source 검증이 0번째 단계에서 FAIL."""
    result = validate_callback_registration(
        task_id="task-missing",
        envelope_path=str(tmp_path / "nonexistent.json"),
        schedule_history_dir=tmp_schedule_history_dir,
        inbound_search_dirs=[tmp_inbound_dir],
    )
    assert result.verdict == FAIL
    assert any("envelope" in r.lower() for r in result.reasons), (
        f"envelope-missing reason 누락: {result.reasons}"
    )


# --------------------------------------------------------------------------- #
# Test 2: deferred/to_be_registered/pending schedule_type 차단
# --------------------------------------------------------------------------- #


@pytest.mark.parametrize(
    "blocked_type",
    [
        "to_be_registered_by_finish_task_sh",
        "deferred",
        "pending",
    ],
)
def test_blocked_schedule_type(
    blocked_type, tmp_envelope, tmp_schedule_history_dir, tmp_inbound_dir
):
    """task-2693 사고 박제: blocked schedule_type 어구 즉시 FAIL."""
    env_path = tmp_envelope(task_id="task-blk", schedule_type=blocked_type)
    result = validate_callback_registration(
        task_id="task-blk",
        envelope_path=env_path,
        schedule_history_dir=tmp_schedule_history_dir,
        inbound_search_dirs=[tmp_inbound_dir],
    )
    assert result.verdict == FAIL
    assert any(
        "schedule_type" in r.lower() or "blocked" in r.lower() for r in result.reasons
    ), f"blocked schedule_type reason 누락: {result.reasons}"
    # source-level: schedule_id source 가 FAIL 로 박제되어야 함
    assert result.sources_checked.get("schedule_id") == FAIL


# --------------------------------------------------------------------------- #
# Test 3: schedule_history missing
# --------------------------------------------------------------------------- #


def test_schedule_history_missing(
    tmp_envelope, tmp_schedule_history_dir, tmp_inbound_dir
):
    """envelope 의 schedule_id 가 회수되었지만 schedule_history/<sid>.log 없음."""
    env_path = tmp_envelope(task_id="task-shm", schedule_id="sid-no-history")
    # NOTE: history log 작성 안 함
    result = validate_callback_registration(
        task_id="task-shm",
        envelope_path=env_path,
        schedule_history_dir=tmp_schedule_history_dir,
        inbound_search_dirs=[tmp_inbound_dir],
    )
    assert result.verdict == FAIL
    assert result.sources_checked.get("schedule_history") == FAIL, (
        f"schedule_history source verdict={result.sources_checked.get('schedule_history')}, "
        f"expected FAIL. sources={result.sources_checked}"
    )


# --------------------------------------------------------------------------- #
# Test 4: owner_key mismatch (self-key channel hit → NON_AUTHORITATIVE)
# --------------------------------------------------------------------------- #


def test_self_key_channel_hit_non_authoritative(
    tmp_envelope, tmp_schedule_history_dir, tmp_inbound_dir
):
    """executor_key == envelope.owner_key 이고 ANU_KEY 가 아니면 NON_AUTHORITATIVE."""
    executor_key = "deadbeef00000000"  # 16-char hex, NOT ANU_KEY
    env_path = tmp_envelope(
        task_id="task-self",
        owner_key=executor_key,
        schedule_id="sid-self",
    )
    _write_history(tmp_schedule_history_dir, "sid-self")
    # self-key 신호만 분리하기 위해 inbound 파일은 작성 (PASS 시키기)
    with open(
        os.path.join(tmp_inbound_dir, "task-self-normal-completion.json"),
        "w",
        encoding="utf-8",
    ) as fp:
        fp.write("{}")
    result = validate_callback_registration(
        task_id="task-self",
        envelope_path=env_path,
        executor_key=executor_key,
        schedule_history_dir=tmp_schedule_history_dir,
        inbound_search_dirs=[tmp_inbound_dir],
    )
    assert result.verdict == NON_AUTHORITATIVE, (
        f"expected NON_AUTHORITATIVE, got verdict={result.verdict}, "
        f"reasons={result.reasons}, sources={result.sources_checked}"
    )
    assert any(
        "self-key" in r.lower() or "non_authoritative" in r.lower() or "executor" in r.lower()
        for r in result.reasons
    ), f"self-key channel reason 누락: {result.reasons}"
    # source-level: owner_key 단계가 NON_AUTHORITATIVE 로 박제되어야 함
    assert result.sources_checked.get("owner_key") == NON_AUTHORITATIVE


# --------------------------------------------------------------------------- #
# Test 5: inbound/receipt missing
# --------------------------------------------------------------------------- #


def test_inbound_missing(tmp_envelope, tmp_schedule_history_dir, tmp_inbound_dir):
    """schedule_id + schedule_history + owner_key 는 PASS 인데 inbound 가 없음."""
    env_path = tmp_envelope(task_id="task-inb", schedule_id="sid-inb")
    _write_history(tmp_schedule_history_dir, "sid-inb")
    # inbound receipt 작성 안 함
    result = validate_callback_registration(
        task_id="task-inb",
        envelope_path=env_path,
        schedule_history_dir=tmp_schedule_history_dir,
        inbound_search_dirs=[tmp_inbound_dir],
    )
    assert result.verdict == FAIL
    assert result.sources_checked.get("inbound_receipt") == FAIL, (
        f"inbound_receipt source verdict={result.sources_checked.get('inbound_receipt')}, "
        f"expected FAIL. sources={result.sources_checked}"
    )
    # 다른 3개 source 는 PASS 였어야 본 case 가 inbound 단독 결손을 보장
    assert result.sources_checked.get("schedule_id") == PASS
    assert result.sources_checked.get("schedule_history") == PASS
    assert result.sources_checked.get("owner_key") == PASS


# --------------------------------------------------------------------------- #
# Test 6: stale sid reused (envelope.task_id 와 호출 task_id mismatch)
# --------------------------------------------------------------------------- #


def test_stale_envelope_task_id_mismatch(
    tmp_envelope, tmp_schedule_history_dir, tmp_inbound_dir
):
    """envelope 의 task_id 가 다른 task 의 것 — stale receipt 재사용 차단."""
    env_path = tmp_envelope(task_id="task-other", schedule_id="sid-other")
    _write_history(tmp_schedule_history_dir, "sid-other")
    with open(
        os.path.join(tmp_inbound_dir, "task-mine-normal-completion.json"),
        "w",
        encoding="utf-8",
    ) as fp:
        fp.write("{}")
    # 호출 task_id=task-mine 인데 envelope.task_id=task-other → 결속 실패
    result = validate_callback_registration(
        task_id="task-mine",
        envelope_path=env_path,
        schedule_history_dir=tmp_schedule_history_dir,
        inbound_search_dirs=[tmp_inbound_dir],
    )
    assert result.verdict == FAIL
    assert any(
        "task_id" in r.lower() or "mismatch" in r.lower() for r in result.reasons
    ), f"task_id mismatch reason 누락: {result.reasons}"
    # 결속 검증은 source loop 전에 즉시 return 됨 — task_id_binding source 만 기록
    assert result.sources_checked.get("task_id_binding") == FAIL


# --------------------------------------------------------------------------- #
# Test 7: ★ dogfood — 모든 source PASS 정상 케이스
# --------------------------------------------------------------------------- #


def test_all_sources_pass(tmp_envelope, tmp_schedule_history_dir, tmp_inbound_dir):
    """7-source(여기서는 4-source) 모두 PASS 정상 케이스 → verdict=PASS."""
    env_path = tmp_envelope(task_id="task-ok", schedule_id="sid-ok")
    _write_history(tmp_schedule_history_dir, "sid-ok")
    with open(
        os.path.join(tmp_inbound_dir, "task-ok-normal-completion.json"),
        "w",
        encoding="utf-8",
    ) as fp:
        fp.write("{}")
    result = validate_callback_registration(
        task_id="task-ok",
        envelope_path=env_path,
        schedule_history_dir=tmp_schedule_history_dir,
        inbound_search_dirs=[tmp_inbound_dir],
    )
    assert result.verdict == PASS, (
        f"expected PASS, got verdict={result.verdict}, "
        f"reasons={result.reasons}, sources={result.sources_checked}"
    )
    for src, verdict in result.sources_checked.items():
        assert verdict == PASS, (
            f"source {src} = {verdict}, expected PASS in dogfood case"
        )
