"""Restate Cloud invocation-signing identity keys.

`restate.app(..., identity_keys=[...])` makes the SDK verify the signature on
every incoming invocation against the registered public key — without it the
handler endpoint is an unauthenticated workflow trigger, and we expose it on
a publicly-reachable Scaleway Serverless Container hostname.

Two coverage layers:

1. ``_resolve_identity_keys()`` — pure env-var parsing. The helper reads
   ``os.environ`` on every call, so we exercise it directly with
   ``monkeypatch.setenv`` rather than reimporting the module.
2. ``restate.app(..., identity_keys=...)`` — the parsed value reaches the
   SDK call site at import time. We patch ``restate.app`` and reimport
   ``main`` once per scenario to capture exactly what is passed.

Layer 2 is necessary because the SDK does its own base58 validation on
real keys; we don't want to ship fake-but-valid keys through it, and the
contract we care about is "the parsed list reaches the SDK verbatim".
"""

from __future__ import annotations

import importlib
import os
import sys
from pathlib import Path
from unittest.mock import patch

import pytest

# restate_services/main.py lives one level up — point sys.path at it.
_ROOT = Path(__file__).resolve().parents[2]
_RESTATE_DIR = _ROOT / "restate_services"
if str(_RESTATE_DIR) not in sys.path:
    sys.path.insert(0, str(_RESTATE_DIR))

# main.py builds a SQLAlchemy engine + configures Logfire at import time.
os.environ.setdefault(
    "NEON_DATABASE_URL",
    "postgresql://user:pass@localhost:5432/testdb",
)
os.environ.setdefault("LOGFIRE_SEND_TO_LOGFIRE", "false")

# Import once — the module-level restate.app(...) runs with an empty env, so
# identity_keys=None, which the SDK accepts. The helper is callable any time
# after that.
import main  # noqa: E402  # ty: ignore[unresolved-import]

# ── _resolve_identity_keys: pure parsing tests ──────────────────────────────


def test_resolve_identity_keys_unset_returns_none(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.delenv("RESTATE_IDENTITY_KEY", raising=False)
    assert main._resolve_identity_keys() is None


def test_resolve_identity_keys_empty_string_returns_none(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setenv("RESTATE_IDENTITY_KEY", "")
    assert main._resolve_identity_keys() is None


def test_resolve_identity_keys_whitespace_only_returns_none(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setenv("RESTATE_IDENTITY_KEY", "   ,  ,,")
    assert main._resolve_identity_keys() is None


def test_resolve_identity_keys_single_key(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setenv("RESTATE_IDENTITY_KEY", "publickey_v1")
    assert main._resolve_identity_keys() == ["publickey_v1"]


def test_resolve_identity_keys_multiple_keys_tolerates_whitespace(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setenv("RESTATE_IDENTITY_KEY", "  key_old , key_new ,, , key_third  ")
    assert main._resolve_identity_keys() == ["key_old", "key_new", "key_third"]


# ── module-level wiring: restate.app gets the parsed value ──────────────────


def _reimport_main_with_patched_app() -> tuple[object, dict[str, object]]:
    """Patch ``restate.app`` to capture its kwargs and reimport ``main``.

    Returns ``(reimported_main_module, captured_kwargs)``. Strips ``main``
    from ``sys.modules`` first so the re-exec hits our patched ``restate.app``.
    """
    captured: dict[str, object] = {}

    def fake_app(services, *, protocol=None, identity_keys=None):
        captured["services"] = list(services)
        captured["protocol"] = protocol
        captured["identity_keys"] = identity_keys
        return object()  # stand-in ASGI app — never invoked

    import restate

    sys.modules.pop("main", None)
    with patch.object(restate, "app", side_effect=fake_app):
        reimported = importlib.import_module("main")

    return reimported, captured


def test_restate_app_receives_parsed_identity_keys(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setenv("RESTATE_IDENTITY_KEY", "k1, k2 , k3")
    _, captured = _reimport_main_with_patched_app()

    assert captured["identity_keys"] == ["k1", "k2", "k3"]
    # 13 services registered (4 CRUD objects + 7 phase-3 services +
    # 2 auth lifecycle workflows). Sanity check: not accidentally empty.
    assert len(captured["services"]) == 13  # type: ignore[arg-type]


def test_restate_app_receives_none_when_env_unset(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.delenv("RESTATE_IDENTITY_KEY", raising=False)
    _, captured = _reimport_main_with_patched_app()

    assert captured["identity_keys"] is None
