from __future__ import annotations

import importlib
from pathlib import Path
from typing import Any, Protocol, cast

from gateway.platforms.telegram_nutrition_addresses import (
    AddressConfigurationError,
    CustomerPrivateDeliveryAddress,
    StaffReviewAddress,
    StaffReviewRole,
    parse_customer_private_delivery_address,
    parse_staff_review_address,
)
from gateway.platforms.telegram_customer_bootstrap import BootstrapState, Role
from gateway.platforms.telegram_nutrition_onboarding_runtime_errors import (
    NutritionOnboardingRuntimeError,
)


class _BootstrapStore(Protocol):
    def list_sessions(self) -> tuple[Any, ...]: ...


class _BootstrapTransport(Protocol):
    store: _BootstrapStore


class _RuntimeContext(Protocol):
    adapter: Any
    bootstrap: _BootstrapTransport
    domain: Any
    profile_root: Path


class TelegramNutritionOnboardingRuntimeAuthorityMixin:
    def _context(self) -> _RuntimeContext:
        return cast(_RuntimeContext, cast(object, self))

    def _registry_document(self) -> Any:
        module = importlib.import_module("checkin_cli.customer_coaching")
        profile_root = self._context().profile_root
        return module.load_customer_registry(
            profile_root / "customers" / "registry.json",
            profile_root,
        )

    def _registry_owner(self) -> Any:
        return self._registry_document().owner

    def _registry_customer(self, session: Any) -> Any:
        matches = tuple(
            runtime.spec
            for runtime in self._registry_document().customers
            if runtime.spec.customer_key == session.customer_key
        )
        if len(matches) != 1:
            raise NutritionOnboardingRuntimeError(
                "canonical customer registry binding is unavailable"
            )
        return matches[0]

    @staticmethod
    def _customer_address(customer: Any) -> CustomerPrivateDeliveryAddress:
        address = customer.telegram
        try:
            return parse_customer_private_delivery_address(
                {
                    "user_id": address.user_id,
                    "chat_id": address.chat_id,
                    "topic_id": address.topic_id,
                }
            )
        except AddressConfigurationError as exc:
            raise NutritionOnboardingRuntimeError(
                "customer DM binding is unavailable or invalid"
            ) from exc

    @staticmethod
    def _staff_address(
        role: StaffReviewRole,
        address: Any,
    ) -> StaffReviewAddress:
        try:
            return parse_staff_review_address(
                role,
                {
                    "user_id": address.user_id,
                    "chat_id": address.chat_id,
                    "topic_id": address.topic_id,
                },
            )
        except AddressConfigurationError as exc:
            raise NutritionOnboardingRuntimeError(
                f"{role.value} review binding is unavailable or invalid"
            ) from exc

    def _session_for_customer_message(self, message: Any) -> Any | None:
        actor_id = getattr(getattr(message, "from_user", None), "id", None)
        chat_id = getattr(getattr(message, "chat", None), "id", None)
        topic_id = getattr(message, "message_thread_id", None)
        if topic_id is None:
            topic_id = 0
        if type(actor_id) is not int or type(chat_id) is not int:
            return None
        for session in self._context().bootstrap.store.list_sessions():
            if session.state is not BootstrapState.AWAITING_ACTIVATION:
                continue
            try:
                customer = self._registry_customer(session)
                address = self._customer_address(customer)
            except NutritionOnboardingRuntimeError:
                continue
            if (
                address.user_id == actor_id
                and address.chat_id == chat_id
                and address.topic_id == topic_id
            ):
                return session
        return None

    def _authority(self, session: Any) -> Any:
        customer_claim = session.role_claim(Role.CUSTOMER)
        if customer_claim is None or session.owner_id is None:
            raise NutritionOnboardingRuntimeError(
                "bootstrap onboarding authority is incomplete"
            )
        registry_customer = self._registry_customer(session)
        customer = self._customer_address(registry_customer)
        owner = self._registry_owner()
        owner_address = self._staff_address(StaffReviewRole.OWNER, owner)
        if (
            str(customer.user_id) != customer_claim.user_id
            or str(owner_address.user_id) != str(session.owner_id)
        ):
            raise NutritionOnboardingRuntimeError(
                "bootstrap identity and registry authority disagree"
            )
        return self._context().domain.OnboardingAuthority(
            customer_key=session.customer_key,
            customer_user_id=customer.user_id,
            customer_chat_id=customer.chat_id,
            customer_topic_id=customer.topic_id,
            owner_user_id=owner_address.user_id,
            owner_chat_id=owner_address.chat_id,
            owner_topic_id=owner_address.topic_id,
            consent_notice_version="privacy-v1",
            consent_granted=True,
            customer_enabled=False,
        )

    def _current_authority(self, session: Any) -> Any:
        authority = self._authority(session)
        context = self._context()
        context.domain.validate_current_registry_authority(
            context.profile_root,
            authority,
        )
        return authority

    def _route(self, session: Any, role: str) -> tuple[str, str]:
        if role == "customer":
            customer = self._registry_customer(session)
            address = self._customer_address(customer)
            return str(address.chat_id), str(address.topic_id)
        if role == "owner":
            address = self._staff_address(
                StaffReviewRole.OWNER,
                self._registry_owner(),
            )
            return str(address.chat_id), str(address.topic_id)
        raise NutritionOnboardingRuntimeError(
            f"unsupported onboarding route role: {role}"
        )

    def _evidence(
        self,
        *,
        actor_id: Any,
        message: Any,
        update_id: Any,
    ) -> Any:
        chat = getattr(message, "chat", None)
        chat_id = getattr(chat, "id", None)
        if chat_id is None:
            chat_id = getattr(message, "chat_id", None)
        if chat_id is None:
            raise NutritionOnboardingRuntimeError(
                "onboarding message chat is missing"
            )
        topic_id = getattr(message, "message_thread_id", None)
        if topic_id is None:
            topic_id = 0
        return self._context().domain.MessageEvidence(
            actor_user_id=int(actor_id),
            chat_id=int(chat_id),
            topic_id=int(topic_id),
            message_id=int(message.message_id),
            update_id=int(update_id),
        )

    async def _member_present(
        self,
        chat_id: Any,
        actor_id: Any,
    ) -> bool:
        member = await self._context().adapter._bot.get_chat_member(
            chat_id=chat_id,
            user_id=actor_id,
        )
        return str(getattr(member, "status", "")).lower() not in {
            "left",
            "kicked",
        }
