# retoor <retoor@molodetz.nl>

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any

from devplacepy import stealth

API_ROOT = "https://api.telegram.org"


class TelegramBackend(ABC):
    @abstractmethod
    async def get_updates(self, offset: int, timeout: int) -> dict[str, Any]: ...

    @abstractmethod
    async def send_message(
        self,
        chat_id: int,
        text: str,
        parse_mode: str | None,
        reply_markup: dict[str, Any] | None = None,
    ) -> dict[str, Any]: ...

    @abstractmethod
    async def edit_message_text(
        self,
        chat_id: int,
        message_id: int,
        text: str,
        parse_mode: str | None,
        reply_markup: dict[str, Any] | None = None,
    ) -> dict[str, Any]: ...

    @abstractmethod
    async def answer_callback_query(
        self, callback_query_id: str, text: str | None = None
    ) -> dict[str, Any]: ...

    @abstractmethod
    async def send_chat_action(self, chat_id: int, action: str) -> dict[str, Any]: ...

    @abstractmethod
    async def get_file(self, file_id: str) -> dict[str, Any]: ...

    @abstractmethod
    async def download(self, file_path: str) -> bytes: ...


class HttpTelegramBackend(TelegramBackend):
    def __init__(self, token: str, timeout: float = 60.0) -> None:
        self._token = token
        self._timeout = timeout

    def _method_url(self, method: str) -> str:
        return f"{API_ROOT}/bot{self._token}/{method}"

    async def _call(self, method: str, payload: dict[str, Any], timeout: float) -> dict[str, Any]:
        async with stealth.stealth_async_client(timeout=timeout) as client:
            response = await client.post(self._method_url(method), json=payload)
        try:
            return response.json()
        except (ValueError, TypeError):
            return {"ok": False, "description": f"non-json response ({response.status_code})"}

    async def get_updates(self, offset: int, timeout: int) -> dict[str, Any]:
        return await self._call(
            "getUpdates",
            {
                "offset": offset,
                "timeout": timeout,
                "allowed_updates": ["message", "callback_query"],
            },
            timeout=timeout + 15,
        )

    async def send_message(
        self,
        chat_id: int,
        text: str,
        parse_mode: str | None,
        reply_markup: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        payload: dict[str, Any] = {
            "chat_id": chat_id,
            "text": text,
            "disable_web_page_preview": True,
        }
        if parse_mode:
            payload["parse_mode"] = parse_mode
        if reply_markup is not None:
            payload["reply_markup"] = reply_markup
        return await self._call("sendMessage", payload, self._timeout)

    async def edit_message_text(
        self,
        chat_id: int,
        message_id: int,
        text: str,
        parse_mode: str | None,
        reply_markup: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        payload: dict[str, Any] = {
            "chat_id": chat_id,
            "message_id": message_id,
            "text": text,
            "disable_web_page_preview": True,
        }
        if parse_mode:
            payload["parse_mode"] = parse_mode
        if reply_markup is not None:
            payload["reply_markup"] = reply_markup
        return await self._call("editMessageText", payload, self._timeout)

    async def answer_callback_query(
        self, callback_query_id: str, text: str | None = None
    ) -> dict[str, Any]:
        payload: dict[str, Any] = {"callback_query_id": callback_query_id}
        if text:
            payload["text"] = text
        return await self._call("answerCallbackQuery", payload, self._timeout)

    async def send_chat_action(self, chat_id: int, action: str) -> dict[str, Any]:
        return await self._call(
            "sendChatAction", {"chat_id": chat_id, "action": action}, self._timeout
        )

    async def get_file(self, file_id: str) -> dict[str, Any]:
        return await self._call("getFile", {"file_id": file_id}, self._timeout)

    async def download(self, file_path: str) -> bytes:
        url = f"{API_ROOT}/file/bot{self._token}/{file_path}"
        async with stealth.stealth_async_client(timeout=self._timeout) as client:
            response = await client.get(url)
        if response.status_code != 200:
            return b""
        return response.content


class FakeTelegramBackend(TelegramBackend):
    def __init__(self) -> None:
        self.inbound: list[dict[str, Any]] = []
        self.sent: list[dict[str, Any]] = []
        self.edited: list[dict[str, Any]] = []
        self.actions: list[dict[str, Any]] = []
        self.files: dict[str, bytes] = {}
        self._message_id = 1000

    async def get_updates(self, offset: int, timeout: int) -> dict[str, Any]:
        ready = [u for u in self.inbound if int(u.get("update_id", 0)) >= offset]
        self.inbound = []
        return {"ok": True, "result": ready}

    async def send_message(
        self,
        chat_id: int,
        text: str,
        parse_mode: str | None,
        reply_markup: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        self._message_id += 1
        self.sent.append(
            {
                "chat_id": chat_id,
                "text": text,
                "parse_mode": parse_mode,
                "reply_markup": reply_markup,
            }
        )
        return {"ok": True, "result": {"message_id": self._message_id}}

    async def edit_message_text(
        self,
        chat_id: int,
        message_id: int,
        text: str,
        parse_mode: str | None,
        reply_markup: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        self.edited.append(
            {
                "chat_id": chat_id,
                "message_id": message_id,
                "text": text,
                "parse_mode": parse_mode,
                "reply_markup": reply_markup,
            }
        )
        return {"ok": True, "result": {"message_id": message_id}}

    async def answer_callback_query(
        self, callback_query_id: str, text: str | None = None
    ) -> dict[str, Any]:
        self.actions.append(
            {"callback_query_id": callback_query_id, "text": text, "kind": "answer"}
        )
        return {"ok": True}

    async def send_chat_action(self, chat_id: int, action: str) -> dict[str, Any]:
        self.actions.append({"chat_id": chat_id, "action": action})
        return {"ok": True}

    async def get_file(self, file_id: str) -> dict[str, Any]:
        if file_id not in self.files:
            return {"ok": False}
        return {"ok": True, "result": {"file_path": f"photos/{file_id}.jpg"}}

    async def download(self, file_path: str) -> bytes:
        key = file_path.split("/")[-1].rsplit(".", 1)[0]
        return self.files.get(key, b"")
