"""
Aiogram adapter for UIRouter handler registration.

This adapter handles all aiogram Router integration:
- Registering all handlers (global, scene, fallback)
- Building filters for Message/CallbackQuery events
- Managing handler lifecycle and routing

The adapter extracts the handler registration logic from UIRouterExecutor,
keeping it clean and focused on execution logic rather than framework integration.
"""

import logging
from typing import TYPE_CHECKING, Any
from collections.abc import Callable

from aiogram import Router, F
from aiogram.types import (
    CallbackQuery,
    ChatBoostRemoved,
    ChatBoostUpdated,
    ChatJoinRequest,
    ChatMemberUpdated,
    ChosenInlineResult,
    InlineQuery,
    Message,
    PreCheckoutQuery,
)
from aiogram.filters import Command, ChatMemberUpdatedFilter
from aiogram.filters.chat_member_updated import (
    JOIN_TRANSITION,
    LEAVE_TRANSITION,
    PROMOTED_TRANSITION,
    IS_MEMBER,
    IS_NOT_MEMBER,
    IS_ADMIN,
    CREATOR,
    ADMINISTRATOR,
    MEMBER,
    RESTRICTED,
    LEFT,
    KICKED,
)

from ui_router.exceptions import ConfigurationError
from .middlewares import install_middleware
from ui_router.schema import EventType, Filter, Scene, Handler, GlobalHandler, TransitionType, ChatMemberFilter
from ui_router.state.context import NavigationState, ExecutionContext, NavigationManager
from ui_router.execution.callbacks import UICallbackData, CallbackDataManager
from .filters.inline_command import InlineCommand
from .filters.inline_prefix import PrefixInlineFilter
from .pre_checkout import pre_checkout_scope

if TYPE_CHECKING:
    from ui_router.schema import UIRouter
    from ui_router.registry import MasterRegistry
    from ui_router.services.handler_service import HandlerService
    from ui_router.services.scene_renderer import SceneRenderer

logger = logging.getLogger(__name__)


class AiogramAdapter:
    """
    Adapter for aiogram Router integration.

    Responsible for:
    - Registering all handlers from the UIRouter schema to aiogram Router
    - Creating filters for Message/CallbackQuery events
    - Routing events to appropriate handlers
    - Managing fallback and pagination

    The adapter is stateless after initialization - all registration happens
    in __init__ and the router is ready to handle events.
    """

    def __init__(
        self,
        schema: "UIRouter",
        callback_manager: CallbackDataManager,
        navigation_manager: NavigationManager,
        handler_service: "HandlerService",
        registry: "MasterRegistry",
        scene_renderer: "SceneRenderer",
    ) -> None:
        """
        Initialize the adapter with specific dependencies.

        The adapter immediately registers all handlers from the schema.

        Args:
            schema: UIRouter configuration
            callback_manager: CallbackDataManager for callback_data encoding/decoding
            navigation_manager: NavigationManager for navigation state
            handler_service: HandlerService for handler processing
            registry: MasterRegistry for custom functions
            scene_renderer: SceneRenderer for scene rendering
        """
        if not schema:
            msg = "schema is required"
            raise ConfigurationError(msg)

        self.schema = schema
        self.callback_manager = callback_manager
        self.navigation_manager = navigation_manager
        self.handler_service = handler_service
        self.registry = registry
        self.scene_renderer = scene_renderer

        self.router = Router(name=schema.name)
        install_middleware(self.router, callback_manager, navigation_manager)

        logger.debug("Initializing AiogramAdapter for schema: %s", schema.name)

        self._register_handlers()

        logger.debug("AiogramAdapter initialized successfully")

    def reload(
        self,
        schema: "UIRouter",
        handler_service: "HandlerService",
        scene_renderer: "SceneRenderer",
        registry: "MasterRegistry",
    ) -> None:
        """
        Reload adapter with new schema and services.

        Clears all registered handlers and re-registers them from the new schema.
        This preserves the same Router instance that's already connected to Dispatcher.

        Args:
            schema: New UIRouter configuration
            handler_service: New HandlerService instance
            scene_renderer: New SceneRenderer instance
            registry: New or updated MasterRegistry
        """
        if not schema:
            msg = "schema is required"
            raise ConfigurationError(msg)

        logger.debug("Reloading AiogramAdapter for schema: %s", schema.name)

        self.schema = schema
        self.handler_service = handler_service
        self.scene_renderer = scene_renderer
        self.registry = registry

        self.router.message.handlers.clear()
        self.router.callback_query.handlers.clear()
        self.router.edited_message.handlers.clear()
        self.router.my_chat_member.handlers.clear()
        self.router.chat_member.handlers.clear()
        self.router.chat_boost.handlers.clear()
        self.router.removed_chat_boost.handlers.clear()
        self.router.chat_join_request.handlers.clear()
        self.router.pre_checkout_query.handlers.clear()
        self.router.inline_query.handlers.clear()
        self.router.chosen_inline_result.handlers.clear()

        if hasattr(self.router.callback_query, "outer_middleware") and hasattr(
            self.router.callback_query.outer_middleware, "_middlewares"
        ):
            self.router.callback_query.outer_middleware._middlewares.clear()

        install_middleware(self.router, self.callback_manager, self.navigation_manager)

        self._register_handlers()

        logger.debug("AiogramAdapter reloaded successfully")

    def _register_handlers(self) -> None:
        """
        Register all handlers from the schema to the aiogram Router.

        Registration order (by priority):
        1. Pagination handler (internal for dynamic keyboards)
        2. Global handlers (by priority)
        3. Scene handlers
        4. Fallback handlers (lowest priority)
        """
        self._register_pagination_handler()

        sorted_globals = sorted(self.schema.global_handlers, key=lambda h: h.priority)
        for global_handler in sorted_globals:
            self._register_global_handler(global_handler)

        for scene in self.schema.scenes:
            for handler in scene.handlers:
                self._register_scene_handler(scene, handler)

        self._register_fallback_handlers()

    def _register_pagination_handler(self) -> None:
        """
        Register internal handler for dynamic keyboard pagination.

        This handler intercepts callback_data containing "_i_p" (internal pagination)
        and updates the pagination state, then re-renders the scene.
        """

        @self.router.callback_query(F.data.contains("_i_p"))
        async def handle_pagination(
            callback: CallbackQuery, ui_call_data: UICallbackData, context: ExecutionContext
        ) -> None:
            """Internal handler for paginating dynamic keyboards."""
            try:
                params = ui_call_data.params or {}

                action = params.get("action")
                page = int(params.get("page", 1))

                if action == "goto_page":
                    new_page = page
                elif action == "noop":
                    return
                else:
                    return

                context.event_data["_pagination_page"] = new_page

                await self.scene_renderer.enter_scene(
                    scene_id=ui_call_data.scene_id,
                    context=context,
                    callback_query=callback,
                    transition=TransitionType.EDIT_SMART,
                )
            except Exception:  # noqa: BLE001, RUF100
                logger.exception("Pagination error")

    def _register_fallback_handlers(self) -> None:
        """
        Register fallback handlers for unprocessed events.

        Fallback handlers are registered last and have the lowest priority.
        They handle:
        - Unexpected messages (fallback.unexpected_message)
        - Unexpected callbacks (fallback.unexpected_callback)
        - Unexpected edited messages (fallback.unexpected_edited_message)
        - Stale callbacks (automatic answer with fallback.stale_callback_text)
        """
        for handler in self.schema.fallback.unexpected_message:
            filters = self._build_filters(handler.filters)
            nav_filter = self._create_navigation_filter(handler.require_state)
            if nav_filter:
                filters.append(nav_filter)

            def _make_message_fb(h: Handler, f: list) -> None:
                @self.router.message(*f)
                async def fallback_message(message: Message, context: ExecutionContext) -> None:
                    await self.handler_service.process_handler(h, message, context)

            _make_message_fb(handler, filters)

        for handler in self.schema.fallback.unexpected_callback:
            filters = self._build_filters(handler.filters)
            nav_filter = self._create_navigation_filter(handler.require_state)
            if nav_filter:
                filters.append(nav_filter)

            def _make_callback_fb(h: Handler, f: list) -> None:
                @self.router.callback_query(*f)
                async def fallback_callback(callback: CallbackQuery, context: ExecutionContext) -> None:
                    await self.handler_service.process_handler(h, callback, context)

            _make_callback_fb(handler, filters)

        for handler in self.schema.fallback.unexpected_edited_message:
            filters = self._build_filters(handler.filters)
            nav_filter = self._create_navigation_filter(handler.require_state)
            if nav_filter:
                filters.append(nav_filter)

            def _make_edited_fb(h: Handler, f: list) -> None:
                @self.router.edited_message(*f)
                async def fallback_edited(message: Message, context: ExecutionContext) -> None:
                    await self.handler_service.process_handler(h, message, context)

            _make_edited_fb(handler, filters)

        if self.schema.fallback.stale_callback_text:

            @self.router.callback_query()
            async def stale_callback(callback: CallbackQuery) -> None:
                await callback.answer(self.schema.fallback.stale_callback_text)

    def _register_global_handler(self, handler: GlobalHandler) -> None:
        """
        Register a global handler.

        Global handlers are processed before scene handlers and can match
        events across all scenes. They're useful for global commands like /help,
        /start, or /admin that work from anywhere.

        Args:
            handler: GlobalHandler configuration from schema
        """
        filters = self._build_filters(handler.filters)

        nav_filter = self._create_navigation_filter(handler.require_state)
        if nav_filter:
            filters = [*filters, nav_filter]

        condition_filters = self._build_condition_filters(handler.conditions)
        if condition_filters:
            filters = [*filters, *condition_filters]

        if handler.event_type == EventType.MESSAGE:

            @self.router.message(*filters)
            async def global_message_handler(message: Message, context: ExecutionContext) -> None:
                await self.handler_service.process_global_handler(handler, message, context)

        elif handler.event_type == EventType.CALLBACK:
            has_callback_filter = any(
                f.type in {"callback_data", "callback_data_regexp"}
                for f in (handler.filters or [])
            )

            if has_callback_filter:
                @self.router.callback_query(*filters)
                async def global_callback_handler(callback: CallbackQuery, context: ExecutionContext) -> None:
                    await self.handler_service.process_global_handler(handler, callback, context)
            else:
                async def global_callback_filter(_: CallbackQuery, ui_call_data: UICallbackData | None = None) -> bool:
                    if ui_call_data is None:
                        return False
                    try:
                        return ui_call_data.is_global and ui_call_data.handler_name == handler.name
                    except Exception:  # noqa: BLE001, RUF100
                        return False

                @self.router.callback_query(*filters, global_callback_filter)
                async def global_callback_handler_encoded(callback: CallbackQuery, context: ExecutionContext) -> None:
                    await self.handler_service.process_global_handler(handler, callback, context)

        elif handler.event_type == EventType.EDITED_MESSAGE:

            @self.router.edited_message(*filters)
            async def global_edited_handler(message: Message, context: ExecutionContext) -> None:
                await self.handler_service.process_global_handler(handler, message, context)

        elif handler.event_type in {EventType.MY_CHAT_MEMBER, EventType.CHAT_MEMBER}:
            self._register_chat_member_handler(handler, filters)

        elif handler.event_type == EventType.CHAT_BOOST:

            @self.router.chat_boost(*filters)
            async def global_chat_boost_handler(event: ChatBoostUpdated, context: ExecutionContext) -> None:
                await self.handler_service.process_global_handler(handler, event, context)

        elif handler.event_type == EventType.REMOVED_CHAT_BOOST:

            @self.router.removed_chat_boost(*filters)
            async def global_removed_chat_boost_handler(event: ChatBoostRemoved, context: ExecutionContext) -> None:
                await self.handler_service.process_global_handler(handler, event, context)

        elif handler.event_type == EventType.CHAT_JOIN_REQUEST:

            @self.router.chat_join_request(*filters)
            async def global_chat_join_request_handler(event: ChatJoinRequest, context: ExecutionContext) -> None:
                await self.handler_service.process_global_handler(handler, event, context)

        elif handler.event_type == EventType.PRE_CHECKOUT_QUERY:

            @self.router.pre_checkout_query(*filters)
            async def global_pre_checkout_handler(event: PreCheckoutQuery, context: ExecutionContext) -> None:
                async with pre_checkout_scope(event, context):
                    await self.handler_service.process_global_handler(handler, event, context)

        elif handler.event_type == EventType.INLINE_QUERY:

            @self.router.inline_query(*filters)
            async def global_inline_query_handler(event: InlineQuery, context: ExecutionContext) -> None:
                await self.handler_service.process_global_handler(handler, event, context)

        elif handler.event_type == EventType.CHOSEN_INLINE_RESULT:

            @self.router.chosen_inline_result(*filters)
            async def global_chosen_inline_handler(event: ChosenInlineResult, context: ExecutionContext) -> None:
                await self.handler_service.process_global_handler(handler, event, context)

    def _register_chat_member_handler(self, handler: GlobalHandler, filters: list) -> None:
        """Register a my_chat_member or chat_member handler with ChatMemberUpdatedFilter."""
        member_filter = self._build_chat_member_filter(handler.filters)
        if member_filter is not None:
            filters = [member_filter, *filters]

        observer = (
            self.router.my_chat_member
            if handler.event_type == EventType.MY_CHAT_MEMBER
            else self.router.chat_member
        )

        @observer(*filters)
        async def chat_member_handler(event: ChatMemberUpdated, context: ExecutionContext) -> None:
            await self.handler_service.process_global_handler(handler, event, context)

    def _build_chat_member_filter(self, filters: list[Filter]) -> ChatMemberUpdatedFilter | None:
        """Build ChatMemberUpdatedFilter from schema filters.

        Looks for a filter with type="chat_member" and constructs the
        appropriate ChatMemberUpdatedFilter from preset or custom transitions.
        """
        for f in filters:
            if f.type == "chat_member" and f.chat_member_filter:
                return self._chat_member_filter_from_schema(f.chat_member_filter)
        return None

    @staticmethod
    def _chat_member_filter_from_schema(cmf: ChatMemberFilter) -> ChatMemberUpdatedFilter:
        """Convert schema ChatMemberFilter to aiogram ChatMemberUpdatedFilter."""
        if cmf.preset:
            if cmf.preset == "any":
                return None
            transition = {
                "join": JOIN_TRANSITION,
                "leave": LEAVE_TRANSITION,
                "promoted": PROMOTED_TRANSITION,
                "demoted": IS_ADMIN >> (MEMBER | RESTRICTED),
            }[cmf.preset]
            return ChatMemberUpdatedFilter(member_status_changed=transition)

        status_map = {
            "creator": CREATOR,
            "administrator": ADMINISTRATOR,
            "member": MEMBER,
            "restricted": RESTRICTED,
            "+restricted": +RESTRICTED,
            "-restricted": -RESTRICTED,
            "left": LEFT,
            "kicked": KICKED,
            "is_member": IS_MEMBER,
            "is_admin": IS_ADMIN,
            "is_not_member": IS_NOT_MEMBER,
        }

        def _resolve_statuses(names: list[str]) -> Any | None:
            result = None
            for name in names:
                s = status_map.get(name.lower())
                if s is None:
                    logger.warning("Unknown chat member status: %s", name)
                    continue
                result = s if result is None else (result | s)
            return result

        old = _resolve_statuses(cmf.old_status) if cmf.old_status else IS_MEMBER | IS_NOT_MEMBER
        new = _resolve_statuses(cmf.new_status) if cmf.new_status else IS_MEMBER | IS_NOT_MEMBER

        return ChatMemberUpdatedFilter(member_status_changed=old >> new)

    def _register_scene_handler(self, scene: Scene, handler: Handler) -> None:
        """
        Register a handler for a specific scene.

        Scene handlers are only processed when the user is on that scene.
        The adapter ensures scene_id and handler_name are correctly matched
        for callback queries.

        Args:
            scene: Scene containing the handler
            handler: Handler configuration from scene
        """
        filters = self._build_filters(handler.filters)

        async def scene_filter(_: Message | CallbackQuery, nav_state: NavigationState) -> bool:
            return nav_state.current_scene == scene.id

        async def handler_name_filter(callback: CallbackQuery, ui_call_data: UICallbackData) -> bool:
            try:
                if ui_call_data.is_global:
                    return False

                matches = ui_call_data.scene_id == scene.id and ui_call_data.handler_name == handler.name

                if not matches and ui_call_data.scene_id == scene.id:
                    logger.debug(
                        "Handler '%s' not found for scene '%s' (callback_data: %s)",
                        ui_call_data.handler_name,
                        scene.id,
                        callback.data,
                    )
                return matches  # noqa: TRY300
            except Exception:  # noqa: BLE001, RUF100
                logger.warning("Error in handler_name_filter")
                return False

        nav_filter = self._create_navigation_filter(handler.require_state)

        condition_filters = self._build_condition_filters(handler.conditions)

        if handler.event_type == EventType.MESSAGE:
            all_filters = [*filters, scene_filter]
            if nav_filter:
                all_filters.append(nav_filter)
            if condition_filters:
                all_filters.extend(condition_filters)

            @self.router.message(*all_filters)
            async def scene_message_handler(message: Message, context: ExecutionContext) -> None:
                await self.handler_service.process_scene_handler(scene, handler, message, context)

        elif handler.event_type == EventType.CALLBACK:
            all_filters = [*filters, handler_name_filter]
            if nav_filter:
                all_filters.append(nav_filter)
            if condition_filters:
                all_filters.extend(condition_filters)

            @self.router.callback_query(*all_filters)
            async def scene_callback_handler(callback: CallbackQuery, context: ExecutionContext) -> None:
                await self.handler_service.process_scene_handler(scene, handler, callback, context)

        elif handler.event_type == EventType.EDITED_MESSAGE:
            all_filters = [*filters, scene_filter]
            if nav_filter:
                all_filters.append(nav_filter)
            if condition_filters:
                all_filters.extend(condition_filters)

            @self.router.edited_message(*all_filters)
            async def scene_edited_handler(message: Message, context: ExecutionContext) -> None:
                await self.handler_service.process_scene_handler(scene, handler, message, context)

    def _build_filters(self, filters: list[Filter]) -> list:
        """
        Build aiogram filters from Filter schema objects.

        Converts schema-level filter definitions to aiogram MagicFilter instances.

        Supported filter types:
        - command: /start, /help, etc. (uses aiogram Command filter)
        - text: exact text match or list of texts
        - regexp: regex pattern matching
        - content_type: message content type filtering
        - custom: custom filter from registry.conditions

        Args:
            filters: List of Filter objects from schema

        Returns:
            List of aiogram filter functions
        """
        result: list[Any] = []

        for f in filters:
            if f.type == "command":
                result.append(Command(*f.commands))

            elif f.type == "text":
                if f.text is None:
                    result.append(F.text)
                elif isinstance(f.text, list):
                    result.append(F.text.in_(f.text))
                else:
                    result.append(F.text == f.text)

            elif f.type == "regexp":
                result.append(F.text.regexp(f.pattern))

            elif f.type == "content_type":
                pass

            elif f.type == "callback_data":
                if f.callback_data is None:
                    result.append(F.data)
                elif isinstance(f.callback_data, list):
                    result.append(F.data.in_(f.callback_data))
                else:
                    result.append(F.data == f.callback_data)

            elif f.type == "callback_data_regexp":
                result.append(F.data.regexp(f.pattern))

            elif f.type == "inline_command":
                result.append(InlineCommand(*f.commands))

            elif f.type == "inline_prefix":
                if isinstance(f.text, list):
                    result.append(PrefixInlineFilter(*f.text))
                elif f.text:
                    result.append(PrefixInlineFilter(f.text))

            elif f.type == "inline_data":
                prefix = f.prefix or ""
                sep = f.sep or ":"
                if prefix:
                    def _make_inline_data_filter(pfx: str, separator: str) -> Callable:
                        async def inline_data_filter(event: InlineQuery) -> bool | dict:
                            query = event.query
                            if query == pfx:
                                return {"inline_data_prefix": pfx, "inline_data_parts": []}
                            if query.startswith(pfx + separator):
                                parts = query[len(pfx) + len(separator):].split(separator)
                                return {"inline_data_prefix": pfx, "inline_data_parts": parts}
                            return False
                        return inline_data_filter
                    result.append(_make_inline_data_filter(prefix, sep))

            elif f.type == "chattype":
                if f.chat_types:
                    if len(f.chat_types) == 1:
                        result.append(F.chat_type == f.chat_types[0])
                    else:
                        result.append(F.chat_type.in_(f.chat_types))

            elif f.type == "chat_member":
                pass

            elif f.type == "custom" and f.custom_function:
                custom_func_name = f.custom_function

                def get_filter(func_name: str) -> Callable:
                    async def filter_impl(event: Any, context: ExecutionContext, user_id: int | None) -> bool:
                        if not user_id:
                            return False
                        return await self.registry.conditions.check(func_name, context)

                    return filter_impl

                result.append(get_filter(custom_func_name))

        return result

    def _create_navigation_filter(self, require_state: bool = True) -> Callable | None:
        """
        Create a filter for checking navigation state.

        This filter ensures the user has an initialized navigation state
        (i.e., has entered a scene before).

        Args:
            require_state: If True, require that nav_state.current_scene exists.
                          If False, skip this filter (return None).

        Returns:
            Async filter function or None
        """
        if not require_state:
            return None

        async def navigation_filter(event: Message | CallbackQuery, nav_state: NavigationState) -> bool:
            """Check that user has navigation state (has entered a scene)."""
            return bool(nav_state.current_scene)

        return navigation_filter

    def _build_condition_filters(self, conditions: list) -> list:
        """
        Build aiogram filters from handler conditions.

        Conditions are checked using the registry.conditions service,
        which can resolve custom functions and apply business logic.

        Args:
            conditions: List of condition strings/objects from Handler

        Returns:
            List containing single async filter function (if conditions exist)
        """
        if not conditions:
            return []

        result: list[Any] = []

        async def condition_filter(event: Message | CallbackQuery, context: ExecutionContext) -> bool:
            """Check all conditions using registry."""
            return await self.registry.conditions.check_all(conditions, context)

        result.append(condition_filter)
        return result

    def get_router(self) -> Router:
        """
        Get the registered aiogram Router.

        Returns:
            Router: The aiogram Router with all handlers registered
        """
        return self.router
