"""eid.tools.dispatch 단위 테스트 — 고정 enum · 동적해석 0 · egress 잠금 (stdlib only).

실행: python3 tests/eid/test_dispatch.py   (또는 pytest)
"""

from __future__ import annotations

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "app"))

from eid.tools.dispatch import (  # noqa: E402
    ALLOWED_ACTIONS,
    _FORBIDDEN_EGRESS_VERBS,
    EidAction,
    _HANDLERS,
    dispatch,
    register_handler,
)


def _reset_handlers():
    _HANDLERS.clear()


def test_unknown_action_rejected():
    _reset_handlers()
    r = dispatch("frobnicate")
    assert r.ok is False
    assert "unknown" in r.reason.lower() or "화이트리스트" in r.reason


def test_no_egress_verb_in_enum():
    # 이중 보증: 화이트리스트 ∩ egress verb = 0
    assert ALLOWED_ACTIONS.isdisjoint(_FORBIDDEN_EGRESS_VERBS)


def test_egress_verb_dispatch_rejected():
    _reset_handlers()
    for verb in ("send_smtp_email", "create_caldav_todo", "call_fallback", "httpx"):
        r = dispatch(verb)
        assert r.ok is False, f"egress verb {verb} 가 통과됨"


def test_external_approval_immediate_reject_no_enqueue():
    _reset_handlers()
    r = dispatch("request_external_approval", {"to": "x@y.com", "body": "..."})
    assert r.ok is False
    assert "거부" in r.reason or "권한 0" in r.reason  # Phase1 즉시거부


def test_external_approval_handler_cannot_register():
    raised = False
    try:
        register_handler(EidAction.REQUEST_EXTERNAL_APPROVAL, lambda a: None)
    except ValueError:
        raised = True
    assert raised, "request_external_approval 핸들러 등록이 허용됨(즉시거부 위반)"


def test_registered_handler_runs():
    _reset_handlers()
    register_handler(EidAction.READ_DOCUMENTS, lambda a: {"rows": 3, "echo": a})
    r = dispatch("read_documents", {"q": "vessel"})
    assert r.ok is True
    assert r.data == {"rows": 3, "echo": {"q": "vessel"}}


def test_unregistered_known_action_rejected():
    _reset_handlers()
    # 화이트리스트엔 있으나 핸들러 미등록(W3 이전) → reject (동적 해석으로 새지 않음)
    r = dispatch("read_events")
    assert r.ok is False
    assert "미등록" in r.reason or "handler" in r.reason.lower()


def test_handler_error_becomes_reject():
    _reset_handlers()

    def _boom(_a):
        raise RuntimeError("db down")

    register_handler(EidAction.READ_STUDY, _boom)
    r = dispatch("read_study")
    assert r.ok is False
    assert "error" in r.reason.lower()


def _run():
    fns = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
    fails = 0
    for fn in fns:
        try:
            fn()
            print(f"  PASS {fn.__name__}")
        except Exception as exc:  # noqa: BLE001
            fails += 1
            print(f"  FAIL {fn.__name__}: {type(exc).__name__}: {exc}")
    print(f"\n{len(fns) - fails}/{len(fns)} passed")
    return 1 if fails else 0


if __name__ == "__main__":
    raise SystemExit(_run())
