"""Test utilities for unit testing DexFather bots without a live server.

Usage:
    import pytest
    from dexfather.testing import MockBot, TestClient
    from dexfather import CommandHandler

    @pytest.mark.asyncio
    async def test_start_command():
        bot = MockBot()

        @bot.command("start")
        async def on_start(update, bot):
            await bot.send_message(update.message.chat.id, "Hello!")

        client = TestClient(bot)
        await client.send_command("/start")

        assert len(bot.sent_messages) == 1
        assert bot.sent_messages[0]["method"] == "sendMessage"
        assert bot.sent_messages[0]["text"] == "Hello!"
"""

from __future__ import annotations

from typing import Any, Optional

from .types import (
    ApiResponse,
    CallbackQuery,
    Chat,
    InlineQuery,
    Message,
    PollAnswer,
    Update,
    User,
)


class MockBot:
    """Drop-in replacement for DexFatherBot that records API calls.

    Does NOT make any HTTP requests or create network clients.
    All calls to ``_request`` are recorded in ``sent_messages``.
    """

    def __init__(self) -> None:
        # Bypass DexFatherBot.__init__ entirely -- no HTTP client
        self.token = "mock_test_token"
        self._handlers: list = []
        self._middlewares: list = []
        self._running = False
        self._offset: int = 0
        self.sent_messages: list[dict] = []
        self._me: dict = {"id": 999, "username": "test_bot", "name": "Test Bot"}

    # ── API call recording ────────────────────────────────────────────

    async def _request(self, method: str, **kwargs: Any) -> ApiResponse:
        """Record the API call instead of making an HTTP request."""
        call = {"method": method, **{k: v for k, v in kwargs.items() if v is not None}}
        self.sent_messages.append(call)
        return ApiResponse(ok=True, result=self._make_result(method, call))

    def _make_result(self, method: str, call: dict) -> Any:
        """Return a plausible fake result for common API methods."""
        if method == "getMe":
            return self._me
        if method == "sendMessage":
            return {
                "message_id": len(self.sent_messages),
                "chat": {"id": call.get("chat_id", 1), "type": "private"},
                "text": call.get("text", ""),
                "date": 0,
            }
        if method == "sendPhoto":
            return {
                "message_id": len(self.sent_messages),
                "chat": {"id": call.get("chat_id", 1), "type": "private"},
                "text": call.get("caption", ""),
                "photo_url": call.get("photo_url", ""),
                "date": 0,
            }
        if method in ("answerCallbackQuery", "answerInlineQuery"):
            return True
        return {}

    def reset(self) -> None:
        """Clear all recorded API calls."""
        self.sent_messages.clear()

    # ── Handler registration (mirrors DexFatherBot) ───────────────────

    def add_handler(self, handler: Any) -> None:
        """Register an update handler."""
        self._handlers.append(handler)

    def add_middleware(self, middleware: Any) -> None:
        """Register a middleware."""
        self._middlewares.append(middleware)

    def command(self, cmd: Any) -> Any:
        """Decorator to register a command handler."""
        from .handlers import CommandHandler

        def decorator(func: Any) -> Any:
            self.add_handler(CommandHandler(cmd, func))
            return func

        return decorator

    def on_message(self, pattern: Optional[str] = None, *, filters: Any = None) -> Any:
        """Decorator to register a message handler."""
        from .handlers import MessageHandler

        def decorator(func: Any) -> Any:
            self.add_handler(MessageHandler(func, pattern=pattern, filters=filters))
            return func

        return decorator

    def on_callback_query(self, pattern: Optional[str] = None) -> Any:
        """Decorator to register a callback query handler."""
        from .handlers import CallbackQueryHandler

        def decorator(func: Any) -> Any:
            self.add_handler(CallbackQueryHandler(func, pattern=pattern))
            return func

        return decorator

    def on_inline_query(self, pattern: Optional[str] = None) -> Any:
        """Decorator to register an inline query handler."""
        from .handlers import InlineQueryHandler

        def decorator(func: Any) -> Any:
            self.add_handler(InlineQueryHandler(func, pattern=pattern))
            return func

        return decorator

    def on_poll_answer(self) -> Any:
        """Decorator to register a poll answer handler."""
        from .handlers import PollAnswerHandler

        def decorator(func: Any) -> Any:
            self.add_handler(PollAnswerHandler(func))
            return func

        return decorator

    def on_chat_member(self) -> Any:
        """Decorator to register a chat member handler."""
        from .handlers import ChatMemberHandler

        def decorator(func: Any) -> Any:
            self.add_handler(ChatMemberHandler(func))
            return func

        return decorator

    def on_message_reaction(self) -> Any:
        """Decorator to register a message reaction handler."""
        from .handlers import TypeHandler

        def decorator(func: Any) -> Any:
            self.add_handler(TypeHandler("message_reaction", func))
            return func

        return decorator

    # ── Update binding (mirrors DexFatherBot._bind_update) ────────────

    def _bind_update(self, update: Update) -> None:
        """Bind bot reference to models for convenience methods."""
        if update.message:
            update.message._bind(self)
        if update.edited_message:
            update.edited_message._bind(self)
        if update.callback_query:
            update.callback_query._bind(self)
            if update.callback_query.message:
                update.callback_query.message._bind(self)

    # ── Dispatch (mirrors DexFatherBot._dispatch) ─────────────────────

    async def _dispatch(self, update: Update) -> None:
        """Dispatch an update to matching handlers."""
        self._bind_update(update)
        for handler in self._handlers:
            try:
                if handler.check(update):
                    await handler.handle(update, self)
                    return  # First matching handler wins
            except Exception:
                raise

    # ── Convenience API methods (used by Message.reply, etc.) ─────────

    async def send_message(self, chat_id: int, text: str, **kwargs: Any) -> Any:
        """Record a sendMessage call."""
        return await self._request("sendMessage", chat_id=chat_id, text=text, **kwargs)

    async def send_photo(self, chat_id: int, photo_url: str, **kwargs: Any) -> Any:
        """Record a sendPhoto call."""
        return await self._request("sendPhoto", chat_id=chat_id, photo_url=photo_url, **kwargs)

    async def edit_message_text(self, chat_id: int, message_id: int, text: str, **kwargs: Any) -> Any:
        """Record an editMessageText call."""
        return await self._request("editMessageText", chat_id=chat_id, message_id=message_id, text=text, **kwargs)

    async def delete_message(self, chat_id: int, message_id: int) -> Any:
        """Record a deleteMessage call."""
        return await self._request("deleteMessage", chat_id=chat_id, message_id=message_id)

    async def answer_callback_query(self, callback_query_id: str, **kwargs: Any) -> Any:
        """Record an answerCallbackQuery call."""
        return await self._request("answerCallbackQuery", callback_query_id=callback_query_id, **kwargs)

    async def send_document(self, chat_id: int, document_url: str, **kwargs: Any) -> Any:
        """Record a sendDocument call."""
        return await self._request("sendDocument", chat_id=chat_id, document_url=document_url, **kwargs)

    async def set_message_reaction(self, chat_id: int, message_id: int, reaction: Optional[str] = None) -> Any:
        """Record a setMessageReaction call."""
        return await self._request("setMessageReaction", chat_id=chat_id, message_id=message_id, reaction=reaction)

    async def answer_inline_query(self, inline_query_id: str, results: Any, **kwargs: Any) -> Any:
        """Record an answerInlineQuery call."""
        return await self._request("answerInlineQuery", inline_query_id=inline_query_id, results=results, **kwargs)


class TestClient:
    """Dispatches fake updates through a MockBot's handler pipeline.

    Simulates incoming messages, commands, callback queries, inline queries,
    and poll answers without any network calls.
    """

    def __init__(self, bot: MockBot) -> None:
        self.bot = bot
        self._update_id = 0

    def _next_id(self) -> int:
        self._update_id += 1
        return self._update_id

    async def send_message(
        self,
        text: str,
        chat_id: int = 1,
        user_id: int = 1,
        chat_type: str = "private",
        **kwargs: Any,
    ) -> Update:
        """Dispatch a text message update through the bot.

        Args:
            text: Message text.
            chat_id: Chat ID (default: 1).
            user_id: User ID (default: 1).
            chat_type: Chat type (default: "private").
            **kwargs: Extra fields merged into Message constructor.

        Returns:
            The constructed Update.
        """
        uid = self._next_id()
        msg_kwargs: dict[str, Any] = {
            "message_id": uid,
            "chat": Chat(id=chat_id, type=chat_type),
            "from_user": User(id=user_id),
            "text": text,
            "date": 0,
        }
        msg_kwargs.update(kwargs)
        update = Update(
            update_id=uid,
            message=Message(**msg_kwargs),
        )
        await self.bot._dispatch(update)
        return update

    async def send_command(self, command: str, **kwargs: Any) -> Update:
        """Dispatch a /command message update through the bot.

        Args:
            command: The full command string (e.g., "/start" or "/help arg").
            **kwargs: Extra keyword arguments passed to send_message.

        Returns:
            The constructed Update.
        """
        return await self.send_message(command, **kwargs)

    async def send_callback_query(
        self,
        data: str,
        chat_id: int = 1,
        user_id: int = 1,
        message_id: int = 1,
    ) -> Update:
        """Dispatch a callback query update through the bot.

        Args:
            data: Callback data string.
            chat_id: Chat ID (default: 1).
            user_id: User ID (default: 1).
            message_id: Message ID of the originating message (default: 1).

        Returns:
            The constructed Update.
        """
        uid = self._next_id()
        update = Update(
            update_id=uid,
            callback_query=CallbackQuery(
                id=str(uid),
                from_user=User(id=user_id),
                data=data,
                message=Message(
                    message_id=message_id,
                    chat=Chat(id=chat_id, type="private"),
                    text="",
                    date=0,
                ),
            ),
        )
        await self.bot._dispatch(update)
        return update

    async def send_inline_query(
        self,
        query: str,
        user_id: int = 1,
    ) -> Update:
        """Dispatch an inline query update through the bot.

        Args:
            query: The inline query text.
            user_id: User ID (default: 1).

        Returns:
            The constructed Update.
        """
        uid = self._next_id()
        update = Update(
            update_id=uid,
            inline_query=InlineQuery(
                id=str(uid),
                from_user=User(id=user_id),
                query=query,
            ),
        )
        await self.bot._dispatch(update)
        return update

    async def send_poll_answer(
        self,
        poll_id: str,
        option_ids: list[int],
        user_id: int = 1,
    ) -> Update:
        """Dispatch a poll answer update through the bot.

        Args:
            poll_id: Poll identifier.
            option_ids: List of selected option indices.
            user_id: User ID (default: 1).

        Returns:
            The constructed Update.
        """
        uid = self._next_id()
        update = Update(
            update_id=uid,
            poll_answer=PollAnswer(
                poll_id=poll_id,
                user=User(id=user_id),
                option_ids=option_ids,
            ),
        )
        await self.bot._dispatch(update)
        return update


__all__ = ["MockBot", "TestClient"]
