"""
Pydantic схемы для декларативного UI роутера
"""

from typing import Annotated, Literal, Any, Self
from pydantic import BaseModel, Discriminator, Field, Tag, model_validator
from enum import StrEnum

from .exceptions import SchemaValidationError


class TransitionType(StrEnum):
    """Тип перехода между сценами"""

    SEND = "send"
    EDIT_SMART = "edit_smart"
    ANSWER = "answer"


class EventType(StrEnum):
    """Тип события"""

    MESSAGE = "message"
    CALLBACK = "callback_query"
    EDITED_MESSAGE = "edited_message"
    SCENE_ENTER = "scene_enter"
    SCENE_EXIT = "scene_exit"

    MY_CHAT_MEMBER = "my_chat_member"
    CHAT_MEMBER = "chat_member"
    CHAT_BOOST = "chat_boost"
    REMOVED_CHAT_BOOST = "removed_chat_boost"
    CHAT_JOIN_REQUEST = "chat_join_request"

    PRE_CHECKOUT_QUERY = "pre_checkout_query"

    INLINE_QUERY = "inline_query"
    CHOSEN_INLINE_RESULT = "chosen_inline_result"


class ActionType(StrEnum):
    """Типы действий в хендлере"""

    SEND_MESSAGE = "send_message"
    EDIT_MESSAGE = "edit_message"
    SEND_PHOTO = "send_photo"
    SEND_VIDEO = "send_video"
    SEND_DOCUMENT = "send_document"
    DELETE_MESSAGE = "delete_message"
    ANSWER_CALLBACK = "answer_callback"

    GOTO_SCENE = "goto_scene"
    BACK = "back"

    SAVE_INPUT = "save_input"
    CLEAR_DATA = "clear_data"

    SET_VARIABLE = "set_variable"

    SCHEDULE_EVENT = "schedule_event"
    EMIT_EVENT = "emit_event"

    BUSINESS_ACTION = "business_action"

    APPROVE_JOIN_REQUEST = "approve_join_request"
    DECLINE_JOIN_REQUEST = "decline_join_request"

    ANSWER_INLINE_QUERY = "answer_inline_query"

    CUSTOM = "custom"


class VariableScope(StrEnum):
    """Область видимости переменной"""

    USER = "user"
    BOT = "bot"
    CHAT = "chat"


class VariableType(StrEnum):
    """Тип переменной"""

    INT = "int"
    FLOAT = "float"
    BOOL = "bool"
    STR = "str"
    DATETIME = "datetime"
    JSON = "json"


class ConditionOperator(StrEnum):
    """Операторы для условий в rule engine"""

    EQ = "eq"
    NE = "ne"
    GT = "gt"
    GTE = "gte"
    LT = "lt"
    LTE = "lte"
    IN = "in"
    NOT_IN = "not_in"
    CONTAINS = "contains"
    STARTS_WITH = "starts_with"
    ENDS_WITH = "ends_with"


class EventSourceType(StrEnum):
    """Тип источника события"""

    INTERNAL = "internal"
    WEBHOOK = "webhook"
    SCHEDULED = "scheduled"
    MANUAL = "manual"


class ScheduleType(StrEnum):
    """Тип расписания"""

    ONCE = "once"
    CRON = "cron"
    INTERVAL = "interval"


class BotVariable(BaseModel):
    """Описание переменной бота"""

    name: str
    scope: VariableScope
    type: VariableType
    default: Any = None
    description: str | None = None


class RuleCondition(BaseModel):
    """Условие для проверки в rule engine"""

    variable: str
    operator: ConditionOperator
    value: Any


class Rule(BaseModel):
    """Правило с условиями и результатом"""

    conditions: list[RuleCondition] = Field(default_factory=list)
    result: Any
    priority: int = 0


class ConditionalFlag(BaseModel):
    """Флаг с декларативной логикой (rule engine)"""

    name: str
    rules: list[Rule]
    default: Any
    description: str | None = None


class ConditionalContent(BaseModel):
    """Контент с условиями отображения"""

    conditions: list[RuleCondition] = Field(default_factory=list)
    content: "MessageContent"
    keyboard: "Keyboard | None" = None
    priority: int = 0


class EventDefinition(BaseModel):
    """Определение события"""

    name: str
    source_type: EventSourceType
    description: str | None = None

    schedule_type: ScheduleType | None = None
    schedule_value: str | None = None

    webhook_path: str | None = None

    expected_data: dict[str, str] = Field(default_factory=dict)


class EventHandler(BaseModel):
    """Обработчик события"""

    event_name: str
    conditions: list[RuleCondition] = Field(default_factory=list)
    actions: list["ActionInstruction"]
    priority: int = 0

    target_scope: Literal["user", "bot", "chat"] = "user"


class FlagGetter(BaseModel):
    """Геттер для получения значения флага"""

    type: Literal["static", "function", "context", "user_input"]
    value: Any | None = None
    function: str | None = None
    context_key: str | None = None
    input_key: str | None = None


class Flag(BaseModel):
    """Флаг - динамические данные доступные в сцене"""

    name: str
    getter: FlagGetter
    default: Any = None


class ContentTemplate(BaseModel):
    """Темплейт для текста с поддержкой флагов"""

    template: str
    flags: list[str] = Field(default_factory=list)


class LocalizedTemplate(BaseModel):
    """Темплейт с локализацией"""

    translations: dict[str, str]
    flags: list[str] = Field(default_factory=list)
    locale_flag: str = "locale"
    default_locale: str = "en"


class LocalizedContent(BaseModel):
    """Простой локализованный текст без темплейтов"""

    type: Literal["localized"]
    translations: dict[str, str]
    locale_getter: str
    default_locale: str = "en"


class DynamicContent(BaseModel):
    """Динамический контент (через темплейт или функцию)"""

    type: Literal["template", "function", "localized_template"]
    template: ContentTemplate | None = None
    localized_template: LocalizedTemplate | None = None
    function: str | None = None


class MediaContent(BaseModel):
    """Медиа контент"""

    type: Literal["photo", "video", "document", "audio"]
    source: str | DynamicContent
    caption: str | DynamicContent | None = None


class MessageContent(BaseModel):
    """Контент сообщения"""

    text: str | DynamicContent | LocalizedContent | None = None
    media: MediaContent | None = None
    parse_mode: str = "HTML"
    disable_web_page_preview: bool = False


class SwitchInlineQueryChosenChat(BaseModel):
    """Параметры для switch_inline_query_chosen_chat кнопки."""

    query: str | None = None
    allow_user_chats: bool | None = None
    allow_bot_chats: bool | None = None
    allow_group_chats: bool | None = None
    allow_channel_chats: bool | None = None


class Button(BaseModel):
    """Кнопка"""

    text: str | DynamicContent | LocalizedContent

    callback_action: str | None = None
    callback_global: str | None = None
    url: str | DynamicContent | None = None

    switch_inline_query: str | None = None
    switch_inline_query_current_chat: str | None = None
    switch_inline_query_chosen_chat: SwitchInlineQueryChosenChat | None = None

    web_app_url: str | None = None

    callback_params: dict[str, str] = Field(default_factory=dict)

    style: Literal["danger", "success", "primary"] | None = None
    icon_custom_emoji_id: str | None = None


class Keyboard(BaseModel):
    """Клавиатура"""

    buttons: list[list[Button]]
    is_inline: bool = True
    resize_keyboard: bool = False
    one_time_keyboard: bool = False


class PaginationConfig(BaseModel):
    """Конфигурация пагинации"""

    enabled: bool = False
    page_size: int = 10

    cache_ttl: int | None = 300
    cache_key_template: str = "pagination:{scene_id}:{data_source}"

    prev_button_text: str = "◀️"
    next_button_text: str = "▶️"


class ButtonTemplate(BaseModel):
    """Темплейт для генерации кнопок из данных"""

    text: str
    callback_action: str
    callback_params: dict[str, str] = Field(default_factory=dict)

    url: str | None = None

    condition: str | None = None

    style: Literal["danger", "success", "primary"] | None = None
    icon_custom_emoji_id: str | None = None


class DynamicKeyboard(BaseModel):
    """Динамическая клавиатура из данных"""

    type: Literal["dynamic"] = "dynamic"

    data_source: str

    layout: Literal["grid", "list", "inline"] = "grid"
    columns: int = 2

    button_template: ButtonTemplate

    pagination: PaginationConfig = Field(default_factory=PaginationConfig)

    header_buttons: list[list[Button]] = Field(default_factory=list)
    footer_buttons: list[list[Button]] = Field(default_factory=list)

    is_inline: bool = True


class Filter(BaseModel):
    """Фильтр для хендлера"""

    type: Literal[
        "command", "text", "regexp", "content_type", "state", "custom",
        "chat_member", "callback_data", "callback_data_regexp",
        "inline_command", "inline_prefix", "inline_data", "chattype",
    ]

    commands: list[str] | None = None
    text: str | list[str] | None = None
    pattern: str | None = None
    content_types: list[str] | None = None
    state: str | None = None
    custom_function: str | None = None
    callback_data: str | list[str] | None = None

    chat_member_filter: "ChatMemberFilter | None" = None

    prefix: str | None = None
    sep: str | None = None

    chat_types: list[str] | None = None


class ChatMemberFilter(BaseModel):
    """Фильтр для my_chat_member / chat_member event types.

    Декларативное описание ChatMemberUpdatedFilter из aiogram.
    Поддерживает пресеты для частых сценариев и кастомные переходы.

    Статусы: creator, administrator, member, restricted, left, kicked
    Модификаторы: +restricted (is_member=True), -restricted (is_member=False)
    Группы: is_member, is_admin, is_not_member
    """

    preset: Literal[
        "join",
        "leave",
        "promoted",
        "demoted",
        "any",
    ] | None = None

    old_status: list[str] | None = None
    new_status: list[str] | None = None


class ActionBase(BaseModel):
    """Базовый класс для всех action"""


class MessageActionBase(ActionBase):
    """Базовый для message-related actions (send/edit/photo/video/document)"""
    content: MessageContent | None = None
    keyboard: Keyboard | DynamicKeyboard | None = None


class GotoSceneAction(ActionBase):
    type: Literal[ActionType.GOTO_SCENE] = ActionType.GOTO_SCENE
    scene_id: str | None = None
    transition: TransitionType | None = None


class SendMessageAction(MessageActionBase):
    type: Literal[ActionType.SEND_MESSAGE] = ActionType.SEND_MESSAGE


class EditMessageAction(MessageActionBase):
    type: Literal[ActionType.EDIT_MESSAGE] = ActionType.EDIT_MESSAGE


class SendPhotoAction(MessageActionBase):
    type: Literal[ActionType.SEND_PHOTO] = ActionType.SEND_PHOTO


class SendVideoAction(MessageActionBase):
    type: Literal[ActionType.SEND_VIDEO] = ActionType.SEND_VIDEO


class SendDocumentAction(MessageActionBase):
    type: Literal[ActionType.SEND_DOCUMENT] = ActionType.SEND_DOCUMENT


class DeleteMessageAction(ActionBase):
    type: Literal[ActionType.DELETE_MESSAGE] = ActionType.DELETE_MESSAGE


class AnswerCallbackAction(ActionBase):
    type: Literal[ActionType.ANSWER_CALLBACK] = ActionType.ANSWER_CALLBACK
    callback_text: str | None = None
    show_alert: bool = False


class BackAction(ActionBase):
    type: Literal[ActionType.BACK] = ActionType.BACK
    transition: TransitionType | None = None


class SaveInputAction(ActionBase):
    type: Literal[ActionType.SAVE_INPUT] = ActionType.SAVE_INPUT
    save_as: str | None = None
    params: dict[str, Any] = Field(default_factory=dict)


class ClearDataAction(ActionBase):
    type: Literal[ActionType.CLEAR_DATA] = ActionType.CLEAR_DATA
    data_keys: list[str] | None = None


class SetVariableAction(ActionBase):
    type: Literal[ActionType.SET_VARIABLE] = ActionType.SET_VARIABLE
    variable_name: str | None = None
    value: Any = None
    operation: str = "set"
    scope: VariableScope = VariableScope.USER

    @model_validator(mode="before")
    @classmethod
    def migrate_from_params(cls, data: Any) -> Any:
        if isinstance(data, dict) and "params" in data and isinstance(data["params"], dict):
            params = data["params"]
            for field in ("variable_name", "value", "operation", "scope"):
                if field not in data and field in params:
                    data[field] = params.pop(field)
            if not params:
                del data["params"]
        return data


class ScheduleEventAction(ActionBase):
    type: Literal[ActionType.SCHEDULE_EVENT] = ActionType.SCHEDULE_EVENT
    event_name: str | None = None
    schedule_type: str = "once"
    schedule_value: str | None = None
    event_data: dict[str, Any] = Field(default_factory=dict)

    @model_validator(mode="before")
    @classmethod
    def migrate_from_params(cls, data: Any) -> Any:
        if isinstance(data, dict) and "params" in data and isinstance(data["params"], dict):
            params = data["params"]
            for field in ("event_name", "schedule_type", "schedule_value", "event_data"):
                if field not in data and field in params:
                    data[field] = params.pop(field)
            if not params:
                del data["params"]
        return data


class EmitEventAction(ActionBase):
    type: Literal[ActionType.EMIT_EVENT] = ActionType.EMIT_EVENT
    event_name: str | None = None
    event_data: dict[str, Any] = Field(default_factory=dict)

    @model_validator(mode="before")
    @classmethod
    def migrate_from_params(cls, data: Any) -> Any:
        if isinstance(data, dict) and "params" in data and isinstance(data["params"], dict):
            params = data["params"]
            for field in ("event_name", "event_data"):
                if field not in data and field in params:
                    data[field] = params.pop(field)
            if not params:
                del data["params"]
        return data


class BusinessAction(ActionBase):
    type: Literal[ActionType.BUSINESS_ACTION] = ActionType.BUSINESS_ACTION
    business_action: str | None = None
    params: dict[str, Any] = Field(default_factory=dict)


class ApproveJoinRequestAction(ActionBase):
    type: Literal[ActionType.APPROVE_JOIN_REQUEST] = ActionType.APPROVE_JOIN_REQUEST


class DeclineJoinRequestAction(ActionBase):
    type: Literal[ActionType.DECLINE_JOIN_REQUEST] = ActionType.DECLINE_JOIN_REQUEST


class InlineArticleResult(BaseModel):
    """Статический inline article result для декларативного описания."""
    id: str | None = None
    title: str | DynamicContent
    description: str | DynamicContent | None = None
    message_text: str | DynamicContent
    parse_mode: str = "HTML"
    thumbnail_url: str | None = None


class AnswerInlineQueryAction(ActionBase):
    type: Literal[ActionType.ANSWER_INLINE_QUERY] = ActionType.ANSWER_INLINE_QUERY
    results_function: str | None = None
    results: list[InlineArticleResult] = Field(default_factory=list)
    cache_time: int = 300
    is_personal: bool = False
    next_offset: str | None = None


class CustomAction(ActionBase):
    type: Literal[ActionType.CUSTOM] = ActionType.CUSTOM
    function: str | None = None
    function_params: dict[str, str] = Field(default_factory=dict)


ActionInstruction = Annotated[
    GotoSceneAction
    | SendMessageAction
    | EditMessageAction
    | SendPhotoAction
    | SendVideoAction
    | SendDocumentAction
    | DeleteMessageAction
    | AnswerCallbackAction
    | BackAction
    | SaveInputAction
    | ClearDataAction
    | SetVariableAction
    | ScheduleEventAction
    | EmitEventAction
    | BusinessAction
    | ApproveJoinRequestAction
    | DeclineJoinRequestAction
    | AnswerInlineQueryAction
    | CustomAction,
    Discriminator("type"),
]


class ThrottleScope(StrEnum):
    """Область видимости throttle"""

    USER = "user"
    CHAT = "chat"
    GLOBAL = "global"


class ThrottleConfig(BaseModel):
    """Конфигурация rate limiting для handler"""

    max_calls: int
    period_seconds: int
    scope: ThrottleScope = ThrottleScope.USER
    cooldown_action: ActionInstruction | None = None


STATELESS_EVENT_TYPES: set[EventType] = {
    EventType.MY_CHAT_MEMBER,
    EventType.CHAT_MEMBER,
    EventType.CHAT_BOOST,
    EventType.REMOVED_CHAT_BOOST,
    EventType.CHAT_JOIN_REQUEST,
    EventType.PRE_CHECKOUT_QUERY,
    EventType.INLINE_QUERY,
    EventType.CHOSEN_INLINE_RESULT,
}


class Handler(BaseModel):
    """Хендлер - набор инструкций"""

    name: str
    event_type: EventType
    filters: list[Filter] = Field(default_factory=list)

    conditions: list[str] = Field(default_factory=list)

    actions: list[ActionInstruction]

    require_state: bool | None = None

    throttle: ThrottleConfig | None = None

    @model_validator(mode="after")
    def set_default_require_state(self) -> Self:
        if self.require_state is None:
            if self.event_type in STATELESS_EVENT_TYPES:
                self.require_state = False
            else:
                self.require_state = self.event_type == EventType.MESSAGE
        return self


class Scene(BaseModel):
    """Сцена - основная единица UI"""

    id: str
    name: str
    description: str | None = None

    flags: list[Flag] = Field(default_factory=list)

    default_content: MessageContent | None = None
    default_keyboard: "Keyboard | DynamicKeyboard | None" = None

    conditional_content: list[ConditionalContent] = Field(default_factory=list)

    messages: list[MessageContent] = Field(default_factory=list)

    handlers: list[Handler] = Field(default_factory=list)

    on_enter: list[ActionInstruction] = Field(default_factory=list)
    on_exit: list[ActionInstruction] = Field(default_factory=list)

    allowed_scenes: list[str] = Field(default_factory=list)
    parent_scene: str | None = None

    expect_input: bool = False
    input_validator: str | None = None


class GlobalHandler(BaseModel):
    """Глобальный хендлер (работает вне сцен)"""

    name: str
    event_type: EventType
    filters: list[Filter]
    conditions: list[str] = Field(default_factory=list)
    actions: list[ActionInstruction]

    priority: int = 0
    interrupt_propagation: bool = False
    require_state: bool = False

    throttle: ThrottleConfig | None = None


class FallbackConfig(BaseModel):
    """Конфигурация fallback handlers для неожиданного ввода"""

    unexpected_message: list["Handler"] = Field(default_factory=list)
    unexpected_callback: list["Handler"] = Field(default_factory=list)
    unexpected_edited_message: list["Handler"] = Field(default_factory=list)
    unexpected_inline_query: list["Handler"] = Field(default_factory=list)
    stale_callback_text: str = "⚠️ Это сообщение устарело"

    @model_validator(mode="before")
    @classmethod
    def coerce_single_handler_to_list(cls, data: Any) -> Any:
        """Backward compat: single Handler dict → list."""
        if isinstance(data, dict):
            for key in (
                "unexpected_message", "unexpected_callback",
                "unexpected_edited_message", "unexpected_inline_query",
            ):
                val = data.get(key)
                if val is not None and isinstance(val, dict) and "name" in val:
                    data[key] = [val]
        return data


class CallbackDataStrategy(BaseModel):
    """Стратегия кодирования callback_data"""

    type: Literal["inline", "cache"]

    separator: str = ":"
    max_length: int = 64

    cache_prefix: str = "cbd"
    ttl: int = 3600


class NavigationConfig(BaseModel):
    """Настройки навигации"""

    enable_history: bool = True
    max_history_depth: int = 10
    store_user_input: bool = True


class UIRouter(BaseModel):
    """Главная схема UI роутера"""

    name: str
    version: str = "1.0.0"
    enable_fluent: bool = False

    global_flags: list[Flag] = Field(default_factory=list)

    variables: list[BotVariable] = Field(default_factory=list)

    conditional_flags: list[ConditionalFlag] = Field(default_factory=list)

    scenes: list[Scene]
    initial_scene: str

    global_handlers: list[GlobalHandler] = Field(default_factory=list)

    events: list[EventDefinition] = Field(default_factory=list)
    event_handlers: list[EventHandler] = Field(default_factory=list)

    callback_strategy: CallbackDataStrategy = Field(default_factory=lambda: CallbackDataStrategy(type="inline"))
    navigation: NavigationConfig = Field(default_factory=NavigationConfig)

    fallback: FallbackConfig = Field(default_factory=FallbackConfig)

    custom_functions: dict[str, str] = Field(default_factory=dict)
    business_actions: dict[str, str] = Field(default_factory=dict)

    @model_validator(mode="after")
    def validate_initial_scene(self) -> "UIRouter":
        """Проверка существования initial_scene"""
        scene_ids = [s.id for s in self.scenes]
        if self.initial_scene not in scene_ids:
            msg = f"initial_scene '{self.initial_scene}' not found in scenes"
            raise SchemaValidationError(msg)
        return self

    def get_scene(self, scene_id: str) -> Scene | None:
        """Получить сцену по ID"""
        for scene in self.scenes:
            if scene.id == scene_id:
                return scene
        return None

    def get_global_handler(self, handler_name: str) -> GlobalHandler | None:
        """Получить глобальный хендлер по имени"""
        for handler in self.global_handlers:
            if handler.name == handler_name:
                return handler
        return None
