from __future__ import annotations

import logging
from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock

import pytest

from gateway.platforms.telegram_nutrition_onboarding import (
    encode_callback_hash,
)
from gateway.platforms.telegram_nutrition_onboarding_runtime import (
    TelegramNutritionOnboardingRuntime,
)
from gateway.platforms.telegram_customer_bootstrap import BootstrapState, Role


@pytest.mark.asyncio
async def test_collecting_accepts_authorized_direct_text_without_reply() -> None:
    runtime = object.__new__(TelegramNutritionOnboardingRuntime)
    session = SimpleNamespace(
        customer_key="customer_001",
        session_id="session-1",
        state=BootstrapState.AWAITING_ACTIVATION,
    )
    runtime._session_for_customer_message = Mock(return_value=session)
    authority = object()
    runtime._current_authority = Mock(return_value=authority)
    next_status = object()
    service = SimpleNamespace(
        status=Mock(
            return_value=SimpleNamespace(
                state=SimpleNamespace(value="collecting"),
                next_field="date_of_birth",
            )
        ),
        store=SimpleNamespace(
            load_session=Mock(
                return_value=SimpleNamespace(
                    message_id=16,
                    payload={"state": "collecting"},
                )
            )
        ),
        submit_answer=Mock(return_value=next_status),
    )
    runtime._service = Mock(return_value=service)
    evidence = object()
    runtime._evidence = Mock(return_value=evidence)
    runtime._publish = AsyncMock()
    message = SimpleNamespace(
        message_id=35,
        text="1970-01-01",
        from_user=SimpleNamespace(id=10),
        chat=SimpleNamespace(id=10),
        message_thread_id=None,
        reply_to_message=None,
        reply_text=AsyncMock(),
    )

    handled = await runtime.handle_text(
        SimpleNamespace(update_id=44),
        message,
    )

    assert handled is True
    runtime._session_for_customer_message.assert_called_once_with(message)
    service.submit_answer.assert_called_once_with(
        field="date_of_birth",
        value="1970-01-01",
        authority=authority,
        evidence=evidence,
    )
    runtime._publish.assert_awaited_once_with(
        session,
        service,
        next_status,
        reply_anchor_message_id=35,
    )


@pytest.mark.asyncio
async def test_stale_reply_emits_structured_rejection_reason(
    caplog: pytest.LogCaptureFixture,
) -> None:
    runtime = object.__new__(TelegramNutritionOnboardingRuntime)
    session = SimpleNamespace(
        customer_key="customer_001",
        session_id="session-1",
        state=BootstrapState.AWAITING_ACTIVATION,
    )
    runtime._session_for_customer_message = Mock(return_value=session)
    runtime._current_authority = Mock(return_value=object())
    service = SimpleNamespace(
        status=Mock(
            return_value=SimpleNamespace(
                state=SimpleNamespace(value="collecting"),
                next_field="date_of_birth",
            )
        ),
        store=SimpleNamespace(
            load_session=Mock(
                return_value=SimpleNamespace(
                    message_id=16,
                    payload={"state": "collecting"},
                )
            )
        ),
        submit_answer=Mock(),
    )
    runtime._service = Mock(return_value=service)
    message = SimpleNamespace(
        message_id=35,
        text="1970-01-01",
        from_user=SimpleNamespace(id=10),
        chat=SimpleNamespace(id=10),
        message_thread_id=None,
        reply_to_message=SimpleNamespace(message_id=15),
        reply_text=AsyncMock(),
    )

    with caplog.at_level(
        logging.INFO,
        logger=(
            "gateway.platforms."
            "telegram_nutrition_onboarding_runtime_collection"
        ),
    ):
        handled = await runtime.handle_text(
            SimpleNamespace(update_id=45),
            message,
        )

    assert handled is True
    service.submit_answer.assert_not_called()
    assert (
        "nutrition_ingress stage=validation update_id=45 "
        "reason_code=stale_reply"
        in caplog.text
    )
    assert "1970-01-01" not in caplog.text
    assert "actor_id=" not in caplog.text


@pytest.mark.asyncio
async def test_staff_surface_cannot_submit_customer_answer() -> None:
    runtime = object.__new__(TelegramNutritionOnboardingRuntime)
    runtime._session_for_customer_message = Mock(return_value=None)
    message = SimpleNamespace(
        message_id=35,
        text="1970-01-01",
        from_user=SimpleNamespace(id=201),
        chat=SimpleNamespace(id=-100),
        message_thread_id=71,
    )

    handled = await runtime.handle_text(SimpleNamespace(update_id=46), message)

    assert handled is False
    runtime._session_for_customer_message.assert_called_once_with(message)


@pytest.mark.asyncio
async def test_customer_callback_checks_customer_dm_not_staff_forum() -> None:
    runtime = object.__new__(TelegramNutritionOnboardingRuntime)
    session = SimpleNamespace(
        state=BootstrapState.AWAITING_ACTIVATION,
        customer_key="customer_001",
        session_id="session-1",
        sid_hash="a" * 64,
        chat_id="-100",
    )
    runtime.bootstrap = SimpleNamespace(
        store=SimpleNamespace(get_by_sid_hash=Mock(return_value=session))
    )
    runtime._service = Mock(
        return_value=SimpleNamespace(
            store=SimpleNamespace(
                load_session=Mock(
                    return_value=SimpleNamespace(
                        generation=1,
                        message_id=16,
                        payload={"state": "collecting"},
                    )
                )
            )
        )
    )
    runtime._route = Mock(return_value=("10", "0"))
    runtime._member_present = AsyncMock(return_value=False)
    query = SimpleNamespace(
        id="customer-dm-route",
        from_user=SimpleNamespace(id=10),
        answer=AsyncMock(),
    )
    message = SimpleNamespace(message_id=16)

    await runtime.handle_callback(
        query,
        encode_callback_hash(
            action="prev",
            generation=1,
            sid_hash="a" * 64,
        ),
        message,
    )

    runtime._route.assert_called_once_with(session, "customer")
    runtime._member_present.assert_awaited_once_with("10", 10)


def test_registry_routes_keep_customer_dm_separate_from_staff() -> None:
    runtime = object.__new__(TelegramNutritionOnboardingRuntime)
    customer = SimpleNamespace(
        customer_key="customer_001",
        telegram=SimpleNamespace(user_id="10", chat_id="10", topic_id="0"),
        trainer=SimpleNamespace(user_id="201", chat_id="-100", topic_id="71"),
    )
    owner = SimpleNamespace(user_id="100", chat_id="-100", topic_id="90")
    runtime._registry_customer = Mock(return_value=customer)
    runtime._registry_owner = Mock(return_value=owner)
    session = SimpleNamespace(
        customer_key="customer_001",
        owner_id="100",
        role_claim=Mock(
            side_effect=lambda role: {
                Role.CUSTOMER: SimpleNamespace(user_id="10"),
                Role.TRAINER: SimpleNamespace(user_id="201"),
            }[role]
        ),
    )

    assert runtime._route(session, "customer") == ("10", "0")
    with pytest.raises(ValueError, match="unsupported onboarding route role"):
        runtime._route(session, "trainer")
    assert runtime._route(session, "owner") == ("-100", "90")
