"""Multiplex secondaries that enable shared-ingress platforms (WhatsApp/Relay) must not be
silently unserved: one INFO per skipped platform, a stamped ``<profile>:<platform>`` status
entry, and the loud "not being served" WARNING when no profile (default included) runs it."""

from __future__ import annotations

import logging
from contextlib import contextmanager
from pathlib import Path

import pytest

from gateway import run as gateway_run
from gateway.config import GatewayConfig, Platform, PlatformConfig
from gateway.run import GatewayRunner


def _runner():
    runner = GatewayRunner.__new__(GatewayRunner)
    runner.config = GatewayConfig(multiplex_profiles=True)
    runner._running = True
    runner.adapters = {}
    runner._failed_platforms = {}
    runner._profile_adapters = {}
    runner._profile_failed_platforms = {}
    runner._background_tasks = set()
    runner._snapshot_profile_busy_modes = lambda *a, **k: None
    runner._platform_lock_takeover_on_start = True
    runner._startup_fail_fatal_config = lambda reason: None
    return runner


def _install_secondary(monkeypatch, runner, stamps):
    @contextmanager
    def fake_scope(profile_home, *, hydrate_secrets=True):
        yield

    monkeypatch.setattr(gateway_run, "_profile_runtime_scope", fake_scope)
    monkeypatch.setattr(gateway_run, "_load_gateway_config", lambda: {})
    monkeypatch.setattr("hermes_cli.env_loader.hydrate_profile_secret_sources", lambda home: {})
    monkeypatch.setattr("hermes_cli.plugins.discover_plugins", lambda: None)
    monkeypatch.setattr(
        "gateway.config.load_gateway_config",
        lambda: GatewayConfig(
            multiplex_profiles=True,
            platforms={Platform.WHATSAPP: PlatformConfig(enabled=True)},
        ),
    )
    monkeypatch.setattr(
        runner, "_update_platform_runtime_status",
        lambda key, **kw: stamps.append((key, kw)),
    )
    runner._create_adapter = lambda platform, config: pytest.fail("shared ingress must never build a secondary adapter")


@pytest.mark.asyncio
async def test_secondary_whatsapp_without_primary_is_reported_unserved(monkeypatch, caplog):
    runner = _runner()
    stamps = []
    _install_secondary(monkeypatch, runner, stamps)

    with caplog.at_level(logging.INFO, logger="gateway.run"):
        assert await runner._start_one_profile_adapters("work", Path("/profiles/work"), {}) == 0
        # The secondary was skipped: the startup phase folds it into the loud warning.
        aborted, _count = await runner._start_secondary_profiles(0, [])

    assert aborted is False
    infos = [r for r in caplog.records if r.levelno == logging.INFO and "not served" in r.getMessage()]
    assert infos and "'work'" in infos[0].getMessage() and "default profile" in infos[0].getMessage()
    warnings = [r.getMessage() for r in caplog.records if r.levelno == logging.WARNING]
    assert any("whatsapp" in w and "not being served" in w and "work" in w for w in warnings), warnings
    # Stamped so `hermes gateway status --profile work` / the dashboard can show the reason.
    assert ("work:whatsapp", {
        "platform_state": "disabled", "error_code": "multiplex_shared_ingress",
        "error_message": "not served under multiplex (shared ingress owned by default)",
    }) in stamps
    # ...and the CLI reader turns that stamp into the status line.
    from gateway.status import write_runtime_status
    from hermes_cli import gateway_multiplex_served as served
    key, kw = next(s for s in stamps if s[0] == "work:whatsapp")
    write_runtime_status(platform=key, **kw)
    monkeypatch.setattr(served, "live_default_gateway_pid", lambda: 4242)
    assert served.served_profile_unserved_platforms("work") == {
        "whatsapp": "not served under multiplex (shared ingress owned by default)"}
    assert served.served_profile_unserved_platforms("other") == {}


@pytest.mark.asyncio
async def test_secondary_whatsapp_served_by_default_is_not_warned(monkeypatch, caplog):
    """The default owns WhatsApp: the secondary IS served through the shared adapter — no WARNING,
    the INFO line still explains where the channel lives."""
    runner = _runner()
    runner.adapters[Platform.WHATSAPP] = object()
    _install_secondary(monkeypatch, runner, [])

    with caplog.at_level(logging.INFO, logger="gateway.run"):
        await runner._start_one_profile_adapters("work", Path("/profiles/work"), {})
        await runner._start_secondary_profiles(1, [])

    assert not [r for r in caplog.records if r.levelno == logging.WARNING and "not being served" in r.getMessage()]
    assert [r for r in caplog.records if r.levelno == logging.INFO and "not served" in r.getMessage()]


@pytest.mark.asyncio
async def test_boot_replays_the_launch_ledger_before_secondaries_and_watchers(monkeypatch, tmp_path):
    """The gateway's idle notification loop (``_async_delegation_watcher``, spawned after this phase)
    reads ``completion_queue`` directly, so the launch profile's durable completions must already
    be queued by the boot hook — in the LAUNCH scope, before any secondary is bound (#123265)."""
    from tools import async_delegation, process_registry as pr_mod
    monkeypatch.setenv("HERMES_HOME", str(tmp_path / "launch"))
    monkeypatch.setattr(pr_mod.process_registry, "_completions_restored", False)
    order = []
    monkeypatch.setattr(async_delegation, "restore_undelivered_completions",
                        lambda q: order.append(("restore", async_delegation._db_path())) or 0)
    runner = _runner()

    async def secondaries():
        order.append(("secondaries", None))
        return 0

    runner._start_secondary_profile_adapters = secondaries
    runner._unserved_shared_ingress_warnings = lambda: []
    assert await runner._start_secondary_profiles(0, []) == (False, 0)
    assert order == [("restore", tmp_path / "launch" / "state.db"), ("secondaries", None)]
