# Copyright (c) 2026 Nous Research
"""Channel inbox adapter routing stays private and capability-gated."""

from __future__ import annotations

from types import SimpleNamespace
from unittest.mock import AsyncMock

import pytest

from gateway.platforms.base import MessageType
from gateway.platforms.nutrition_coaching import (
    IncomingAddress,
    NutritionCoachingCoordinator,
    TelegramCustomerTransport,
)
from tests.gateway.channel_inbox_testkit import (
    BOT_ID,
    CANDIDATE_DIGEST,
    CHAT_ID,
    TOPIC_ID,
    ChannelMessageSpec,
    channel_adapter,
    channel_context,
    channel_message,
    channel_update,
    channel_user,
)


def test_channel_inbox_builds_private_customer_source() -> None:
    adapter = channel_adapter()

    event = adapter._build_message_event(channel_message(), MessageType.TEXT)

    assert event.source.chat_type == "dm"
    assert event.source.chat_id == CHAT_ID
    assert event.source.user_id == "42"
    assert event.source.thread_id == str(TOPIC_ID)


def test_uninitialized_legacy_adapter_keeps_channel_inbox_inert() -> None:
    adapter = object.__new__(type(channel_adapter()))

    assert not adapter._channel_inbox_reserves_ingress(channel_message())


def test_channel_inbox_builds_exact_nutrition_address() -> None:
    adapter = channel_adapter()

    address = adapter._nutrition_address(channel_message())

    assert address.key == ("42", CHAT_ID, str(TOPIC_ID))


def test_channel_inbox_synthetic_send_uses_only_direct_topic() -> None:
    adapter = channel_adapter()
    metadata = adapter._channel_inbox_thread_metadata(CHAT_ID, str(TOPIC_ID))
    assert metadata is not None

    kwargs = adapter._thread_kwargs_for_send(CHAT_ID, str(TOPIC_ID), metadata)

    assert kwargs["direct_messages_topic_id"] == TOPIC_ID
    assert kwargs["message_thread_id"] is None


@pytest.mark.parametrize(("enabled", "verified"), [(False, False), (True, False)])
def test_channel_inbox_delivery_fails_closed_without_authority(
    enabled: bool,
    verified: bool,
) -> None:
    adapter = channel_adapter(enabled=enabled)
    adapter._channel_inbox_authority_verified = verified

    with pytest.raises(RuntimeError, match="authority"):
        adapter.channel_inbox_delivery_kwargs(CHAT_ID, TOPIC_ID)


def test_nutrition_transport_uses_channel_direct_topic(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    adapter = channel_adapter()
    coordinator = object.__new__(NutritionCoachingCoordinator)
    canonical = SimpleNamespace(
        key=("42", CHAT_ID, str(TOPIC_ID)),
        chat_id=CHAT_ID,
        topic_id=str(TOPIC_ID),
    )
    customer = SimpleNamespace(spec=SimpleNamespace(telegram=canonical))
    monkeypatch.setattr(coordinator, "customer", lambda customer_key: customer)
    monkeypatch.setattr(
        coordinator,
        "customer_transport_allowed",
        lambda *args, **kwargs: True,
    )
    transport = TelegramCustomerTransport(adapter, coordinator)

    _, kwargs = transport._prepare_customer_send(
        "customer-1",
        IncomingAddress("42", CHAT_ID, str(TOPIC_ID)),
        "체크인 시간입니다.",
        adaptive=False,
    )

    assert kwargs["chat_id"] == CHAT_ID
    assert kwargs["direct_messages_topic_id"] == TOPIC_ID
    assert kwargs["message_thread_id"] is None


def test_channel_inbox_admin_and_unbound_customer_are_reserved() -> None:
    adapter = channel_adapter()

    assert adapter._channel_inbox_reserves_ingress(
        channel_message(ChannelMessageSpec(sender_id=99))
    )
    assert adapter._channel_inbox_reserves_ingress(channel_message())


def test_channel_inbox_registered_customer_nontext_is_reserved(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    adapter = channel_adapter()
    coordinator = SimpleNamespace(resolve=lambda address: object())
    monkeypatch.setattr(adapter, "_get_nutrition_coaching", lambda: coordinator)

    assert adapter._channel_inbox_reserves_ingress(channel_message())


@pytest.mark.asyncio
async def test_channel_inbox_runtime_authority_requires_bot_privilege(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    adapter = channel_adapter()
    adapter._channel_inbox_authority_verified = False
    bot = SimpleNamespace(
        get_me=AsyncMock(return_value=SimpleNamespace(id=int(BOT_ID))),
        get_chat=AsyncMock(
            return_value=SimpleNamespace(
                is_direct_messages=True,
                parent_chat=SimpleNamespace(id=-1008000000001),
            )
        ),
        get_chat_member=AsyncMock(
            return_value=SimpleNamespace(can_manage_direct_messages=True)
        ),
    )
    monkeypatch.setattr(adapter, "_bot", bot)
    monkeypatch.setattr(
        adapter,
        "_task26_candidate_digest",
        CANDIDATE_DIGEST,
        raising=False,
    )

    assert await adapter._verify_channel_inbox_authority()
    bot.get_chat_member.return_value = SimpleNamespace(can_manage_direct_messages=False)
    assert not await adapter._verify_channel_inbox_authority()


@pytest.mark.asyncio
async def test_channel_inbox_authority_precedes_background_start(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    adapter = channel_adapter()
    application = SimpleNamespace(start=AsyncMock())
    verify = AsyncMock(return_value=False)
    subscribe = AsyncMock()
    recover = AsyncMock()
    monkeypatch.setattr(adapter, "_app", application)
    monkeypatch.setattr(adapter, "_verify_channel_inbox_authority", verify)
    monkeypatch.setattr(adapter, "_arm_staff_membership_subscription", subscribe)
    monkeypatch.setattr(adapter, "_recover_task26_nutrition_background", recover)

    assert not await adapter._start_channel_inbox_authorized_network()
    verify.assert_awaited_once()
    application.start.assert_not_awaited()
    subscribe.assert_not_awaited()
    recover.assert_not_awaited()


@pytest.mark.asyncio
async def test_channel_inbox_admin_command_never_reaches_generic_agent(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    adapter = channel_adapter()
    generic_handler = AsyncMock()
    monkeypatch.setattr(adapter, "handle_message", generic_handler)
    update = channel_update(
        channel_message(ChannelMessageSpec(sender_id=99, text="/unknown")),
    )

    await adapter._handle_command(update, channel_context())

    generic_handler.assert_not_awaited()


def test_channel_inbox_callback_uses_clicking_customer_identity(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    adapter = channel_adapter()
    coordinator = SimpleNamespace(resolve=lambda address: object())
    monkeypatch.setattr(adapter, "_get_nutrition_coaching", lambda: coordinator)
    bot_authored_card = channel_message(ChannelMessageSpec(sender_id=999))
    customer = channel_user(42)

    assert adapter._channel_inbox_reserves_ingress(bot_authored_card)
    query = SimpleNamespace(from_user=customer)
    address = adapter._nutrition_address(query, bot_authored_card)
    assert address.key == ("42", CHAT_ID, str(TOPIC_ID))


def test_disabled_channel_inbox_preserves_existing_private_dm() -> None:
    adapter = channel_adapter(enabled=False)
    message = channel_message(
        ChannelMessageSpec(
            chat_id="42",
            topic_id=17,
            is_direct_messages=False,
            chat_type="private",
            include_topic=False,
        )
    )

    event = adapter._build_message_event(message, MessageType.TEXT)

    assert event.source.chat_id == "42"
    assert event.source.user_id == "42"
    assert event.source.thread_id is None
