"""
Content service for resolving and displaying message content.

This service extracts content resolution and display logic from UIRouterExecutor,
providing a clean API for rendering scenes with proper handling of:
- Conditional content with priority-based selection
- Template and localization resolution
- Smart message editing with media support
- Fallback to delete+send when edit fails
"""

import contextlib
import logging
from collections.abc import Awaitable, Callable
from typing import Any

from ui_router.exceptions import ContentError

from aiogram import Bot
from aiogram.types import (
    Message,
    CallbackQuery,
    InlineKeyboardMarkup,
    ReplyKeyboardMarkup,
    InputMediaPhoto,
    InputMediaVideo,
    InputMediaDocument,
    InputMediaAudio,
)

from ui_router.state.context import ExecutionContext
from ui_router.execution.actions import ContentResolver
from ui_router.rules.engine import ConditionEvaluator
from ui_router.schema import (
    MessageContent,
    MediaContent,
    Scene,
    TransitionType,
)

logger = logging.getLogger(__name__)


class ContentService:
    """
    Service for resolving and displaying message content.

    Handles:
    - Resolving scene content based on conditions
    - Displaying content with appropriate transition types
    - Smart message editing with media support
    - Sending additional messages

    Attributes:
        content_resolver: ContentResolver for template and string resolution.
    """

    def __init__(
        self,
        content_resolver: ContentResolver,
    ) -> None:
        """
        Initialize ContentService.

        Args:
            content_resolver: ContentResolver instance for template resolution.
        """
        self.content_resolver = content_resolver

    async def resolve_scene_content(
        self,
        scene: Scene,
        context: ExecutionContext,
        get_variable_fn: Callable[[str, ExecutionContext], Awaitable[Any]],
        resolve_condition_value_fn: Callable[[Any, ExecutionContext], Awaitable[Any]],
    ) -> tuple[MessageContent | None, InlineKeyboardMarkup | ReplyKeyboardMarkup | None]:
        """
        Resolve scene content based on conditions.

        Evaluates conditional_content by priority order and returns the first
        matching content, or falls back to default_content if no conditions match.

        Args:
            scene: The scene to resolve content for.
            context: ExecutionContext containing flags and state.
            get_variable_fn: Async function to get variable values.
            resolve_condition_value_fn: Async function to resolve condition values.

        Returns:
            Tuple of (content, keyboard) where content is MessageContent (or None)
            and keyboard is the resolved InlineKeyboardMarkup or ReplyKeyboardMarkup.
        """
        if scene.conditional_content:
            sorted_content = sorted(scene.conditional_content, key=lambda c: c.priority, reverse=True)

            for cond_content in sorted_content:
                all_match = True
                for condition in cond_content.conditions:
                    var_value = await get_variable_fn(condition.variable, context)
                    compare_value = await resolve_condition_value_fn(condition.value, context)

                    if not ConditionEvaluator._apply_operator(
                        var_value, condition.operator, compare_value
                    ):
                        all_match = False
                        break

                if all_match:
                    keyboard = None
                    if cond_content.keyboard:
                        keyboard = await self.content_resolver.resolve_keyboard(
                            cond_content.keyboard, context, scene.id
                        )
                    return cond_content.content, keyboard

        keyboard = None
        if scene.default_keyboard:
            keyboard = await self.content_resolver.resolve_keyboard(scene.default_keyboard, context, scene.id)

        return scene.default_content, keyboard

    async def display_content(
        self,
        content: MessageContent | None,
        keyboard: InlineKeyboardMarkup | ReplyKeyboardMarkup | None,
        user_id: int,
        transition: TransitionType,
        callback_query: CallbackQuery | None,
        context: ExecutionContext,
    ) -> None:
        """
        Display content with appropriate transition type.

        Uses smart edit for EDIT_SMART transitions, send for SEND, and answer
        for ANSWER transitions.

        Args:
            content: MessageContent to display (or None for text-only).
            keyboard: Keyboard markup to display with content.
            user_id: User ID for sending messages.
            transition: TransitionType determining how to display (SEND, EDIT_SMART, ANSWER).
            callback_query: CallbackQuery object if editing a message.
            context: ExecutionContext with bot and resolved flags.
        """
        text = ""
        if content and content.text:
            text = self.content_resolver.resolve_string(content.text, context)

        if transition == TransitionType.EDIT_SMART and callback_query and callback_query.message:
            await self._smart_edit(
                bot=context.bot,
                old_message=callback_query.message,
                new_content=content,
                text=text,
                keyboard=keyboard,
                user_id=user_id,
                fallback_to_send=(transition == TransitionType.EDIT_SMART),
            )
        else:
            await self._send_content(
                bot=context.bot,
                content=content,
                text=text,
                keyboard=keyboard,
                user_id=user_id,
            )

    async def _smart_edit(
        self,
        bot: Bot,
        old_message: Message,
        new_content: MessageContent | None,
        text: str,
        keyboard: InlineKeyboardMarkup | ReplyKeyboardMarkup | None,
        user_id: int,
        fallback_to_send: bool,
    ) -> None:
        """
        Perform smart message edit with media handling.

        Attempts to edit the message in place, with fallback behavior:
        - Text to Text: simple edit_text
        - Media to Media (same type): edit_media
        - Text to Media or Media to Text: delete and send new
        - Media type mismatch: delete and send new (if fallback enabled)

        Args:
            bot: Bot instance for sending messages.
            old_message: The message to edit.
            new_content: New MessageContent to display.
            text: Resolved text content.
            keyboard: Keyboard to display.
            user_id: User ID for fallback sends.
            fallback_to_send: Whether to fallback to delete+send on edit failure.
        """
        old_has_media = bool(old_message.photo or old_message.video or old_message.document or old_message.audio)
        new_has_media = bool(new_content and new_content.media)

        try:
            if not new_has_media and not old_has_media:
                await old_message.edit_text(
                    text=text or "...",
                    reply_markup=keyboard,
                    parse_mode=new_content.parse_mode if new_content else "HTML",
                )
                return

            if new_has_media and old_has_media:
                if self._can_edit_media_type(old_message, new_content.media):
                    await self._edit_media(old_message, new_content.media, text, keyboard)
                elif fallback_to_send:
                    await old_message.delete()
                    await self._send_content(bot, new_content, text, keyboard, user_id)
                else:
                    msg = "Cannot edit media type"
                    raise ContentError(msg)
                return

            if fallback_to_send:
                await old_message.delete()
                await self._send_content(bot, new_content, text, keyboard, user_id)
            else:
                msg = "Cannot edit between text and media"
                raise ContentError(msg)

        except Exception:
            if fallback_to_send:
                with contextlib.suppress(Exception):
                    await old_message.delete()
                await self._send_content(bot, new_content, text, keyboard, user_id)
            else:
                raise

    def _can_edit_media_type(
        self,
        old_message: Message,
        new_media: MediaContent | None,
    ) -> bool:
        """
        Check if media type can be edited in place.

        Returns True only if the old message media type matches the new media type.

        Args:
            old_message: The existing message to edit.
            new_media: The new media content (should have 'type' attribute).

        Returns:
            True if media types match and in-place edit is possible.
        """
        if not new_media:
            return False

        old_type = None
        if old_message.photo:
            old_type = "photo"
        elif old_message.video:
            old_type = "video"
        elif old_message.document:
            old_type = "document"
        elif old_message.audio:
            old_type = "audio"
        elif old_message.animation:
            old_type = "animation"

        new_type = new_media.type if hasattr(new_media, "type") else None

        return old_type == new_type

    async def _edit_media(
        self,
        message: Message,
        new_media: MediaContent,
        text: str,
        keyboard: InlineKeyboardMarkup | ReplyKeyboardMarkup | None,
    ) -> None:
        """
        Edit media in an existing message.

        Creates appropriate InputMedia object based on media type and performs
        the edit_media operation.

        Args:
            message: The message to edit.
            new_media: New media content (must be MediaContent).
            text: Caption text for the media.
            keyboard: Keyboard to display with media.
        """
        if not isinstance(new_media, MediaContent):
            msg = f"new_media must be MediaContent, got {type(new_media).__name__}"
            raise ContentError(msg)

        media_obj = None
        media_source = new_media.source if isinstance(new_media.source, str) else str(new_media.source)

        if new_media.type == "photo":
            media_obj = InputMediaPhoto(
                media=media_source,
                caption=text or None,
                parse_mode="HTML",
            )
        elif new_media.type == "video":
            media_obj = InputMediaVideo(
                media=media_source,
                caption=text or None,
                parse_mode="HTML",
            )
        elif new_media.type == "document":
            media_obj = InputMediaDocument(
                media=media_source,
                caption=text or None,
                parse_mode="HTML",
            )
        elif new_media.type == "audio":
            media_obj = InputMediaAudio(
                media=media_source,
                caption=text or None,
                parse_mode="HTML",
            )

        if media_obj:
            await message.edit_media(media=media_obj, reply_markup=keyboard)

    async def _send_content(
        self,
        bot: Bot,
        content: MessageContent | None,
        text: str,
        keyboard: InlineKeyboardMarkup | ReplyKeyboardMarkup | None,
        user_id: int,
    ) -> None:
        """
        Send content as a new message.

        Handles both text-only and media content, using appropriate bot methods
        (send_photo, send_video, send_document, send_audio, send_message).

        Args:
            bot: Bot instance for sending.
            content: MessageContent to send (or None for text-only).
            text: The resolved text content.
            keyboard: Keyboard to display with message.
            user_id: Chat ID for sending the message.
        """
        if content and content.media:
            media = content.media
            media_source = media.source if isinstance(media.source, str) else str(media.source)

            if media.type == "photo":
                await bot.send_photo(
                    chat_id=user_id,
                    photo=media_source,
                    caption=text or None,
                    reply_markup=keyboard,
                    parse_mode=content.parse_mode,
                )
            elif media.type == "video":
                await bot.send_video(
                    chat_id=user_id,
                    video=media_source,
                    caption=text or None,
                    reply_markup=keyboard,
                    parse_mode=content.parse_mode,
                )
            elif media.type == "document":
                await bot.send_document(
                    chat_id=user_id,
                    document=media_source,
                    caption=text or None,
                    reply_markup=keyboard,
                    parse_mode=content.parse_mode,
                )
            elif media.type == "audio":
                await bot.send_audio(
                    chat_id=user_id,
                    audio=media_source,
                    caption=text or None,
                    reply_markup=keyboard,
                    parse_mode=content.parse_mode,
                )
        else:
            await bot.send_message(
                chat_id=user_id,
                text=text or "...",
                reply_markup=keyboard,
                parse_mode=content.parse_mode if content else "HTML",
            )

    async def send_additional_messages(
        self,
        messages: list[MessageContent],
        user_id: int,
        context: ExecutionContext,
    ) -> None:
        """
        Send additional messages after main content.

        Resolves text for each message and sends them as new messages,
        supporting both text and media content.

        Args:
            messages: List of MessageContent to send.
            user_id: User ID for sending messages.
            context: ExecutionContext with resolved flags and bot instance.
        """
        for msg_content in messages:
            text = ""
            if msg_content.text:
                text = self.content_resolver.resolve_string(msg_content.text, context)

            await self._send_content(context.bot, msg_content, text, None, user_id)
