"""
Динамические клавиатуры с пагинацией
"""

import logging
import re
import math
from typing import Any
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton

from ui_router.schema import DynamicKeyboard, PaginationConfig, ButtonTemplate, Button, RuleCondition, ConditionOperator
from ui_router.state.context import ExecutionContext
from .callbacks import CallbackDataManager
from ui_router.rules.engine import ConditionEvaluator
from ui_router.exceptions import KeyboardRenderError
from .formatting import flag_formatter


logger = logging.getLogger(__name__)


class DynamicKeyboardRenderer:
    """Рендеринг динамических клавиатур"""

    def __init__(self, callback_manager: CallbackDataManager) -> None:
        self.callback_manager = callback_manager

    async def render(
        self,
        keyboard: DynamicKeyboard,
        context: ExecutionContext,
        scene_id: str,
    ) -> InlineKeyboardMarkup:
        """
        Рендерить динамическую клавиатуру

        Args:
            keyboard: Конфигурация клавиатуры
            context: Контекст выполнения
            scene_id: ID текущей сцены

        Returns:
            InlineKeyboardMarkup
        """
        data = self._get_data_source(keyboard.data_source, context)

        if not isinstance(data, list):
            msg = f"Data source '{keyboard.data_source}' must return a list"
            raise KeyboardRenderError(msg)

        current_page = context.get_from_event("_pagination_page", 1)

        if keyboard.pagination.enabled:
            total_items = len(data)
            total_pages = math.ceil(total_items / keyboard.pagination.page_size) if total_items > 0 else 1

            current_page = max(1, min(current_page, total_pages))

            start_idx = (current_page - 1) * keyboard.pagination.page_size
            end_idx = start_idx + keyboard.pagination.page_size
            page_data = data[start_idx:end_idx]
        else:
            page_data = data
            total_pages = 1

        data_buttons = []
        for item in page_data:
            if (
                keyboard.button_template.condition
                and not self._eval_condition(keyboard.button_template.condition, item)
            ):
                continue

            button = await self._create_button_from_template(
                keyboard.button_template,
                item,
                context,
                scene_id,
            )
            data_buttons.append(button)

        rows = self._apply_layout(data_buttons, keyboard.layout, keyboard.columns)

        if keyboard.header_buttons:
            for header_row in keyboard.header_buttons:
                header_btn_row = []
                for btn in header_row:
                    rendered_btn = await self._render_button(btn, context, scene_id)
                    header_btn_row.append(rendered_btn)
                rows.insert(0, header_btn_row)

        if keyboard.pagination.enabled and total_pages > 1:
            pagination_row = await self._create_pagination_row(
                current_page,
                total_pages,
                keyboard.pagination,
                keyboard.data_source,
                scene_id,
            )
            rows.append(pagination_row)

        if keyboard.footer_buttons:
            for footer_row in keyboard.footer_buttons:
                footer_btn_row = []
                for btn in footer_row:
                    rendered_btn = await self._render_button(btn, context, scene_id)
                    footer_btn_row.append(rendered_btn)
                rows.append(footer_btn_row)

        return InlineKeyboardMarkup(inline_keyboard=rows)

    def _get_data_source(self, data_source: str, context: ExecutionContext) -> list:
        """Получить данные из флага"""
        data = context.get_flag(data_source)

        if data is None:
            return []

        if not isinstance(data, list):
            msg = f"Data source '{data_source}' must be a list, got {type(data)}"
            raise KeyboardRenderError(msg)

        return data

    def _eval_condition(self, condition: str | RuleCondition, item: dict[str, Any]) -> bool:
        """
        Оценка условия через ConditionEvaluator

        Args:
            condition: Строка условия (deprecated, для обратной совместимости) или RuleCondition
            item: Данные item для проверки

        Returns:
            bool: Результат проверки

        Note:
            Строковые условия deprecated, используйте RuleCondition
        """
        try:
            if isinstance(condition, RuleCondition):
                result = ConditionEvaluator.evaluate_simple_condition(condition, item)
            else:
                parsed_condition = self._parse_string_condition(condition, item)
                if parsed_condition:
                    result = ConditionEvaluator.evaluate_simple_condition(parsed_condition, item)
                else:
                    logger.warning("Warning: Could not parse condition '%s', treating as true", condition)
                    result = True
        except Exception:
            logger.exception("Error evaluating condition '%s'", condition)
            return True
        else:
            return result

    def _parse_string_condition(self, condition: str, item: dict[str, Any]) -> RuleCondition | None:
        """
        Парсинг строкового условия в RuleCondition (deprecated)

        Поддерживает простые условия вида:
        - field == value
        - field != value
        - field > value
        - field >= value
        - field < value
        - field <= value

        Returns:
            RuleCondition или None если не удалось распарсить
        """
        operators_map = {
            "==": ConditionOperator.EQ,
            "!=": ConditionOperator.NE,
            ">=": ConditionOperator.GTE,
            "<=": ConditionOperator.LTE,
            ">": ConditionOperator.GT,
            "<": ConditionOperator.LT,
        }

        condition = condition.strip()

        for op_str, op_enum in operators_map.items():
            if op_str in condition:
                parts = condition.split(op_str, 1)
                if len(parts) == 2:
                    variable = parts[0].strip()
                    value_str = parts[1].strip()

                    value: Any = value_str
                    if value_str in {"True", "true"}:
                        value = True
                    elif value_str in {"False", "false"}:
                        value = False
                    elif value_str in {"None", "null"}:
                        value = None
                    elif value_str.isdigit():
                        value = int(value_str)
                    elif value_str.replace(".", "", 1).isdigit():
                        value = float(value_str)
                    elif (value_str.startswith('"') and value_str.endswith('"')) or (
                        value_str.startswith("'") and value_str.endswith("'")
                    ):
                        value = value_str[1:-1]

                    return RuleCondition(variable=variable, operator=op_enum, value=value)

        return None

    async def _create_button_from_template(
        self,
        template: ButtonTemplate,
        item: dict,
        context: ExecutionContext,
        scene_id: str,
    ) -> InlineKeyboardButton:
        """Создать кнопку из темплейта и данных"""

        text = self._resolve_template(template.text, item, context)
        action = self._resolve_template(template.callback_action, item, context)

        if template.url:
            url = self._resolve_template(template.url, item, context)
            return InlineKeyboardButton(
                text=text,
                url=url,
                style=template.style,
                icon_custom_emoji_id=template.icon_custom_emoji_id,
            )

        params = {}
        for key, value_template in template.callback_params.items():
            params[key] = self._resolve_template(value_template, item, context)

        callback_data = await self.callback_manager.encode(
            handler_name=action,
            scene_id=scene_id,
            is_global=False,
            params=params,
        )

        return InlineKeyboardButton(
            text=text,
            callback_data=callback_data,
            style=template.style,
            icon_custom_emoji_id=template.icon_custom_emoji_id,
        )

    def _resolve_template(self, template: str, item: dict, context: ExecutionContext) -> str:
        """
        Резолвить темплейт с подстановкой данных

        Поддерживает:
        - {field} и {field.nested} - из item (dot-notation)
        - {flag:name} - из context flags
        """
        result = template

        flag_pattern = r"\{flag:(\w+)\}"
        for match in re.finditer(flag_pattern, result):
            flag_name = match.group(1)
            flag_value = context.get_flag(flag_name, "")
            result = result.replace(match.group(0), str(flag_value))

        return flag_formatter.format(result, **item)

    def _apply_layout(
        self,
        buttons: list[InlineKeyboardButton],
        layout: str,
        columns: int,
    ) -> list[list[InlineKeyboardButton]]:
        """Применить layout к кнопкам"""

        if layout == "list":
            return [[btn] for btn in buttons]

        if layout == "grid":
            rows = []
            for i in range(0, len(buttons), columns):
                row = buttons[i : i + columns]
                rows.append(row)
            return rows

        if layout == "inline":
            return [buttons] if buttons else []

        msg = f"Unknown layout: {layout}"
        raise KeyboardRenderError(msg)

    async def _create_pagination_row(
        self,
        current_page: int,
        total_pages: int,
        config: PaginationConfig,
        data_source: str,
        scene_id: str,
    ) -> list[InlineKeyboardButton]:
        """
        Создать ряд кнопок пагинации

        Всегда 5 кнопок: [1] [◀️] [·N·] [▶️] [Last]
        Циклическая навигация (1 ← last, 1 → last)
        """
        buttons = []
        noop_callback = await self.callback_manager.encode(
            handler_name="_i_p",
            scene_id=scene_id,
            is_global=False,
            params={"action": "noop", "data_source": data_source},
        )

        first_callback = await self.callback_manager.encode(
            handler_name="_i_p",
            scene_id=scene_id,
            is_global=False,
            params={"action": "goto_page", "page": "1", "data_source": data_source},
        )
        buttons.append(InlineKeyboardButton(text="1", callback_data=first_callback))

        prev_page = current_page - 1 if current_page > 1 else total_pages
        prev_callback = await self.callback_manager.encode(
            handler_name="_i_p",
            scene_id=scene_id,
            is_global=False,
            params={"action": "goto_page", "page": str(prev_page), "data_source": data_source},
        )
        buttons.extend((
            InlineKeyboardButton(text=config.prev_button_text, callback_data=prev_callback),
            InlineKeyboardButton(text=f"· {current_page} ·", callback_data=noop_callback),
        ))

        next_page = current_page + 1 if current_page < total_pages else 1
        next_callback = await self.callback_manager.encode(
            handler_name="_i_p",
            scene_id=scene_id,
            is_global=False,
            params={"action": "goto_page", "page": str(next_page), "data_source": data_source},
        )
        buttons.append(InlineKeyboardButton(text=config.next_button_text, callback_data=next_callback))

        last_callback = await self.callback_manager.encode(
            handler_name="_i_p",
            scene_id=scene_id,
            is_global=False,
            params={"action": "goto_page", "page": str(total_pages), "data_source": data_source},
        )
        buttons.append(InlineKeyboardButton(text=str(total_pages), callback_data=last_callback))

        return buttons

    async def _render_button(
        self,
        button: Button,
        context: ExecutionContext,
        scene_id: str,
    ) -> InlineKeyboardButton:
        """Рендерить обычную кнопку"""
        from .actions import ContentResolver

        resolver = ContentResolver(self.callback_manager, None)
        text = resolver.resolve_string(button.text, context)

        if button.url:
            url = resolver.resolve_string(button.url, context)
            return InlineKeyboardButton(
                text=text,
                url=url,
                style=button.style,
                icon_custom_emoji_id=button.icon_custom_emoji_id,
            )

        handler_name = button.callback_action or button.callback_global
        is_global = button.callback_global is not None

        params = {}
        for key, value_template in button.callback_params.items():
            params[key] = resolver.resolve_string(value_template, context)

        callback_data = await self.callback_manager.encode(
            handler_name=handler_name,
            scene_id=scene_id,
            is_global=is_global,
            params=params,
        )

        return InlineKeyboardButton(
            text=text,
            callback_data=callback_data,
            style=button.style,
            icon_custom_emoji_id=button.icon_custom_emoji_id,
        )
