from __future__ import annotations

import asyncio
from types import SimpleNamespace

from gateway.platforms.telegram_customer_bootstrap import BootstrapState
from gateway.platforms.telegram_nutrition_onboarding_runtime_callback import (
    TelegramNutritionOnboardingRuntimeCallbackMixin,
)
from gateway.platforms.telegram_nutrition_onboarding_runtime_collection import (
    TelegramNutritionOnboardingRuntimeCollectionMixin,
)


def test_text_and_callback_mutations_share_one_full_operation_lock() -> None:
    class Runtime(
        TelegramNutritionOnboardingRuntimeCollectionMixin,
        TelegramNutritionOnboardingRuntimeCallbackMixin,
    ):
        def __init__(self) -> None:
            self.entered = asyncio.Event()
            self.release = asyncio.Event()
            self.operations: list[str] = []
            self.active = 0
            self.max_active = 0

        async def _handle_text_locked(
            self,
            update: object,
            message: object,
        ) -> bool:
            del update, message
            self.active += 1
            self.max_active = max(self.max_active, self.active)
            self.operations.append("text")
            self.entered.set()
            await self.release.wait()
            self.active -= 1
            return True

        async def _handle_callback_locked(
            self,
            query: object,
            data: str,
            message: object,
            *,
            update_id: int | None = None,
        ) -> None:
            del query, data, message, update_id
            self.active += 1
            self.max_active = max(self.max_active, self.active)
            self.operations.append("callback")
            self.active -= 1

    async def scenario() -> Runtime:
        runtime = Runtime()
        text_task = asyncio.create_task(runtime.handle_text(object(), object()))
        await runtime.entered.wait()
        callback_started = asyncio.Event()

        async def invoke_callback() -> None:
            callback_started.set()
            await runtime.handle_callback(
                object(),
                "ignored",
                object(),
                update_id=2,
            )

        callback_task = asyncio.create_task(invoke_callback())
        await callback_started.wait()
        assert runtime.operations == ["text"]
        runtime.release.set()
        await asyncio.gather(text_task, callback_task)
        return runtime

    runtime = asyncio.run(scenario())
    assert runtime.operations == ["text", "callback"]
    assert runtime.max_active == 1


def test_second_invalid_text_publishes_durable_input_help_state() -> None:
    collecting = SimpleNamespace(
        state=SimpleNamespace(value="collecting"),
        next_field="goal_type",
        questionnaire_version="customer_v2",
    )
    input_help = SimpleNamespace(
        state=SimpleNamespace(value="input_help"),
        help_field="goal_type",
        answer_failure_count=2,
    )

    class Service:
        def __init__(self) -> None:
            self.store = SimpleNamespace(
                load_session=lambda _session_id: SimpleNamespace(
                    message_id=11,
                    payload={},
                )
            )
            self.failures = 0

        def status(self) -> SimpleNamespace:
            return collecting

        def record_answer_failure(self, **_kwargs: object) -> SimpleNamespace:
            self.failures += 1
            return collecting if self.failures == 1 else input_help

    class Message:
        def __init__(self, message_id: int) -> None:
            self.text = "건강해지고 싶어요"
            self.message_id = message_id
            self.from_user = SimpleNamespace(id=10)
            self.reply_to_message = None
            self.replies: list[str] = []

        async def reply_text(self, text: str) -> None:
            self.replies.append(text)

    class Runtime(TelegramNutritionOnboardingRuntimeCollectionMixin):
        def __init__(self) -> None:
            self.session = SimpleNamespace(
                state=BootstrapState.AWAITING_ACTIVATION,
                customer_key="customer-1",
                session_id="session-1",
            )
            self.service = Service()
            self.published: list[SimpleNamespace] = []

        def _session_for_customer_message(
            self,
            _message: object,
        ) -> SimpleNamespace:
            return self.session

        def _service(self, _customer_key: str) -> Service:
            return self.service

        def _current_authority(self, _session: object) -> object:
            return object()

        def _evidence(self, **_kwargs: object) -> object:
            return object()

        def replayed_status_if_consumed(
            self,
            _service: object,
            _update_id: object,
        ) -> None:
            return None

        async def _publish(
            self,
            _session: object,
            _service: object,
            status: SimpleNamespace,
            *,
            reply_anchor_message_id: int | None = None,
        ) -> None:
            del reply_anchor_message_id
            self.published.append(status)

    async def scenario() -> tuple[Runtime, Message, Message]:
        runtime = Runtime()
        first = Message(12)
        second = Message(13)
        assert await runtime.handle_text(SimpleNamespace(update_id=1), first)
        assert await runtime.handle_text(SimpleNamespace(update_id=2), second)
        return runtime, first, second

    runtime, first, second = asyncio.run(scenario())

    assert runtime.service.failures == 2
    assert len(first.replies) == 1
    assert len(second.replies) == 1
    assert runtime.published == [input_help]
