from __future__ import annotations

import asyncio
from datetime import datetime, timezone
from pathlib import Path
from types import SimpleNamespace

import pytest

import gateway.platforms.telegram as telegram_module
from gateway.platforms.telegram import TelegramAdapter
from gateway.platforms.telegram_customer_bootstrap import (
    BootstrapState,
    RecoverySlot,
    RoomBootstrapSession,
    RoomBootstrapStore,
    customer_consent_callback,
)
from .new_customer_consent_testkit import awaiting_consent


def test_telegram_consent_persists_handoff_before_registry_and_recovers_crash(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    store, receipted = awaiting_consent(tmp_path)
    callback_data = customer_consent_callback(
        receipted.customer_key,
        "privacy-v1",
        "g",
    )
    address = SimpleNamespace(user_id=10, chat_id=10, topic_id=0)
    callback_observations: list[tuple[BootstrapState, bool]] = []

    class Coordinator:
        def handle_customer_consent_callback(
            self,
            actual_address: object,
            data: str,
        ) -> object:
            current = store.get(receipted.session_id)
            callback_observations.append((
                current.state,
                current.consent_handoff is not None,
            ))
            assert actual_address is address
            assert data == callback_data
            return SimpleNamespace(
                reply=SimpleNamespace(accepted=True, notice="동의를 저장했습니다.")
            )

    started: list[RoomBootstrapSession] = []

    class Runtime:
        async def start_after_consent(
            self,
            *,
            session: object,
            **_kwargs: object,
        ) -> None:
            assert isinstance(session, RoomBootstrapSession)
            started.append(session)

    class Query:
        async def answer(self, **_kwargs: object) -> None:
            return None

        async def edit_message_text(self, **_kwargs: object) -> None:
            return None

    adapter = object.__new__(TelegramAdapter)
    adapter._room_bootstrap_transport = SimpleNamespace(store=store)
    adapter._get_nutrition_coaching = lambda: Coordinator()
    adapter._nutrition_address = lambda *_args: address
    adapter._get_nutrition_onboarding_runtime = lambda: Runtime()
    monkeypatch.setattr(telegram_module, "current_telegram_update_id", lambda: 701)

    original_reconcile = store.reconcile_committed_consent
    attempts = 0

    def fail_once(*args: object, **kwargs: object) -> RoomBootstrapSession:
        nonlocal attempts
        attempts += 1
        if attempts == 1:
            raise RuntimeError("simulated crash after registry consent")
        return original_reconcile(*args, **kwargs)

    monkeypatch.setattr(store, "reconcile_committed_consent", fail_once)
    old_card_timestamp = datetime(2000, 1, 1, tzinfo=timezone.utc)
    message = SimpleNamespace(message_id=500, date=old_card_timestamp)
    before = datetime.now(timezone.utc)

    with pytest.raises(RuntimeError, match="simulated crash"):
        asyncio.run(
            adapter._handle_nutrition_customer_consent_callback(
                Query(),
                callback_data,
                message,
            )
        )

    crashed = RoomBootstrapStore(store.state_dir).get(receipted.session_id)
    assert crashed.state is BootstrapState.AWAITING_CONSENT
    assert crashed.consent_handoff is not None
    assert crashed.consent_handoff.recorded_at >= before
    assert crashed.consent_handoff.recorded_at != old_card_timestamp

    asyncio.run(
        adapter._handle_nutrition_customer_consent_callback(
            Query(),
            callback_data,
            message,
        )
    )

    recovered = RoomBootstrapStore(store.state_dir).get(receipted.session_id)
    assert callback_observations == [
        (BootstrapState.AWAITING_CONSENT, True),
        (BootstrapState.AWAITING_CONSENT, True),
    ]
    assert recovered.state is BootstrapState.AWAITING_ACTIVATION
    assert recovered.consent_recovery_reconciled is True
    assert started == [recovered]


def test_uncertain_consent_publication_has_one_replacement_then_terminates(
    tmp_path: Path,
) -> None:
    store, uncertain = awaiting_consent(tmp_path, bind_receipt=False)

    superseded = store.recover_uncertain_consent_publication(
        uncertain.session_id,
        expected_generation=uncertain.generation,
    )
    assert superseded.state is BootstrapState.AWAITING_CONSENT
    assert superseded.recovery_attempts == ()

    publication = store.reserve_consent_publication(
        superseded.session_id,
        expected_generation=superseded.generation,
    )
    _ = store.reserve_recovery_attempt(
        publication.session_id,
        slot=RecoverySlot.CONSENT_CARD,
        chat_id="10",
        expected_generation=publication.generation,
    )
    second_uncertain = store.get(publication.session_id)
    exhausted = store.recover_uncertain_consent_publication(
        second_uncertain.session_id,
        expected_generation=second_uncertain.generation,
    )
    assert exhausted.state is BootstrapState.FAILED
    assert exhausted.failure_code == "consent_publication_uncertain_exhausted"

    replacement = store.prepare_rehearsal_customer_invite(
        exhausted.customer_draft,
        bot_username=exhausted.bot_username,
        owner_id=exhausted.owner_id,
    )
    assert replacement.session.session_id != exhausted.session_id


def test_adapter_restart_replaces_first_uncertain_consent_card(
    tmp_path: Path,
) -> None:
    store, uncertain = awaiting_consent(tmp_path, bind_receipt=False)

    class Nutrition:
        @staticmethod
        def refresh_live_registry() -> bool:
            return True

        @staticmethod
        def open_customer_onboarding(_address: object) -> object:
            return SimpleNamespace(text="동의 카드", buttons=())

    provider_calls: list[tuple[str, str]] = []

    async def send_topic(
        *,
        chat_id: str,
        topic_id: str,
        **_kwargs: object,
    ) -> object:
        provider_calls.append((chat_id, topic_id))
        return SimpleNamespace(message_id=600)

    adapter = object.__new__(TelegramAdapter)
    adapter._room_bootstrap_transport = SimpleNamespace(store=store)
    adapter._room_bootstrap_recovery_lock = asyncio.Lock()
    adapter._get_room_bootstrap_transport = lambda: adapter._room_bootstrap_transport
    adapter._get_nutrition_coaching = lambda: Nutrition()
    adapter._send_nutrition_topic = send_topic

    asyncio.run(adapter._recover_room_bootstrap_waiting_states())

    recovered = RoomBootstrapStore(store.state_dir).get(uncertain.session_id)
    assert provider_calls == [("10", "0")]
    assert recovered.state is BootstrapState.AWAITING_CONSENT
    assert recovered.consent_publication_attempt == 2
    assert recovered.consent_card_message_id == "600"
    assert recovered.recovery_attempts == ()
