from __future__ import annotations

import asyncio
import json
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import AsyncMock

import pytest

from gateway.platforms.telegram_activation_notice import (
    ACTIVATION_COMPLETION_TEMPLATE,
    ActivationNoticeError,
    ActivationNoticeStore,
)


def _notice(store: ActivationNoticeStore):
    return store.reserve(
        customer_key="client_001",
        starts_on="2026-08-03",
        daily_time="08:00",
        destination={
            "user_id": "200",
            "chat_id": "200",
            "topic_id": "0",
        },
        authority_digest="a" * 64,
    )


def test_activation_notice_is_reserved_once_with_exact_customer_copy(tmp_path: Path) -> None:
    store = ActivationNoticeStore(tmp_path)

    first = _notice(store)
    replay = _notice(store)

    assert first == replay
    assert first.state == "prepared"
    assert first.body == ACTIVATION_COMPLETION_TEMPLATE.format(
        starts_on="2026-08-03",
        daily_time="08:00",
    )
    rows = [json.loads(line) for line in store.path.read_text().splitlines()]
    assert [row["state"] for row in rows] == ["prepared"]
    assert store.path.stat().st_mode & 0o777 == 0o600


def test_activation_notice_rejects_group_topic_destination(tmp_path: Path) -> None:
    store = ActivationNoticeStore(tmp_path)

    with pytest.raises(ActivationNoticeError, match="private Telegram chat"):
        _ = store.reserve(
            customer_key="client_001",
            starts_on="2026-08-03",
            daily_time="08:00",
            destination={
                "user_id": "200",
                "chat_id": "-100123",
                "topic_id": "70",
            },
            authority_digest="a" * 64,
        )


def test_activation_notice_unknown_is_terminal_and_cannot_reauthorize_provider(
    tmp_path: Path,
) -> None:
    store = ActivationNoticeStore(tmp_path)
    prepared = _notice(store)
    sending = store.mark_sending(prepared)
    unknown = store.mark_unknown(sending, reason="provider_unknown")

    replay = _notice(store)

    assert sending.provider_authority is True
    assert unknown.state == replay.state == "unknown"
    assert replay.provider_authority is False
    with pytest.raises(ActivationNoticeError, match="prepared"):
        store.mark_sending(replay)


@pytest.mark.asyncio
async def test_gateway_drains_activation_notice_once_with_audited_receipt(
    tmp_path: Path,
) -> None:
    from gateway.platforms.telegram import TelegramAdapter

    store = ActivationNoticeStore(tmp_path)
    prepared = _notice(store)
    provider = AsyncMock(return_value=SimpleNamespace(message_id="message-1"))
    adapter = object.__new__(TelegramAdapter)
    adapter._send_nutrition_topic = provider

    first = await adapter._send_activation_completion_notices(tmp_path)
    second = await adapter._send_activation_completion_notices(tmp_path)

    assert first == () and second == ()
    assert provider.await_count == 1
    await_args = provider.await_args
    assert await_args is not None
    assert await_args.kwargs == {
        "chat_id": "200",
        "topic_id": "0",
        "text": prepared.body,
    }
    assert [row.state for row in store.receipts()] == [prepared.state, "sending", "delivered", "sent_audited"]


@pytest.mark.asyncio
async def test_concurrent_activation_notice_drains_share_one_provider_authority(
    tmp_path: Path,
) -> None:
    from gateway.platforms.telegram import TelegramAdapter

    _notice(ActivationNoticeStore(tmp_path))
    entered = asyncio.Event()
    release = asyncio.Event()

    async def send_once(**_kwargs):
        entered.set()
        await release.wait()
        return SimpleNamespace(message_id="message-1")

    provider = AsyncMock(side_effect=send_once)
    adapter = object.__new__(TelegramAdapter)
    adapter._send_nutrition_topic = provider
    first = asyncio.create_task(adapter._send_activation_completion_notices(tmp_path))
    await asyncio.wait_for(entered.wait(), timeout=1)
    second = asyncio.create_task(adapter._send_activation_completion_notices(tmp_path))
    release.set()

    assert await first == ()
    assert await second == ()
    assert provider.await_count == 1
    assert ActivationNoticeStore(tmp_path).latest()[0].state == "sent_audited"


@pytest.mark.asyncio
@pytest.mark.parametrize("blocked", ["paused", "withdrawn", "wrong-route", "stale"])
async def test_activation_notice_fails_closed_before_provider(
    tmp_path: Path,
    blocked: str,
) -> None:
    from gateway.platforms.telegram import TelegramAdapter

    _notice(ActivationNoticeStore(tmp_path))
    provider = AsyncMock(return_value=SimpleNamespace(message_id="must-not-send"))
    adapter = object.__new__(TelegramAdapter)
    adapter._send_nutrition_topic = provider
    canonical_destination = SimpleNamespace(
        user_id="200",
        chat_id="200",
        topic_id="0",
    )
    customer = SimpleNamespace(spec=SimpleNamespace(telegram=canonical_destination))
    coordinator = SimpleNamespace(
        refresh_live_registry=lambda: blocked != "stale",
        customer=lambda _key: None if blocked == "withdrawn" else customer,
        customer_transport_allowed=lambda *_args, **_kwargs: blocked != "paused",
    )
    if blocked == "wrong-route":
        customer.spec.telegram = SimpleNamespace(
            user_id="200",
            chat_id="999",
            topic_id="0",
        )

    failures = await adapter._send_activation_completion_notices(
        tmp_path,
        coordinator,
    )

    assert failures
    provider.assert_not_awaited()
    assert ActivationNoticeStore(tmp_path).latest()[0].state == "unknown"


@pytest.mark.asyncio
async def test_gateway_never_retries_unknown_activation_notice(tmp_path: Path) -> None:
    from gateway.platforms.telegram import TelegramAdapter

    _notice(ActivationNoticeStore(tmp_path))
    provider = AsyncMock(side_effect=asyncio.TimeoutError())
    adapter = object.__new__(TelegramAdapter)
    adapter._send_nutrition_topic = provider

    first = await adapter._send_activation_completion_notices(tmp_path)
    second = await adapter._send_activation_completion_notices(tmp_path)

    assert first and second
    assert provider.await_count == 1
    assert ActivationNoticeStore(tmp_path).latest()[0].state == "unknown"
