"""Telegram channel adapter."""

from __future__ import annotations

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

from beaver.foundation.events import InboundMessage, OutboundMessage
from beaver.interfaces.channels.base import ChannelInboundSink

from .base import (
    build_inbound_message,
    chunk_text,
    compact_media_summary,
    config_bool,
    config_list,
    mark_unclaimed,
    outbound_target,
)

EventRecorder = Callable[..., None]


class TelegramAdapter:
    """Telegram Bot API adapter with injectable client support."""

    KIND = "telegram"

    def __init__(
        self,
        *,
        channel_id: str,
        kind: str,
        mode: str,
        account_id: str,
        display_name: str | None,
        inbound_sink: ChannelInboundSink,
        secrets: dict[str, Any] | None = None,
        config: dict[str, Any] | None = None,
        event_recorder: EventRecorder | None = None,
        client: Any | None = None,
        application_factory: Callable[[], Any] | None = None,
    ) -> None:
        self.channel_id = channel_id
        self.kind = kind
        self.mode = mode
        self.account_id = account_id
        self.display_name = display_name
        self.inbound_sink = inbound_sink
        self.secrets = secrets or {}
        self.config = config or {}
        self.event_recorder = event_recorder
        self._client = client
        self._application_factory = application_factory
        self._application: Any | None = None
        self.max_message_chars = int(self.config.get("maxMessageChars") or 4096)

    async def start(self) -> None:
        if self._client is not None:
            return
        if self.mode == "polling":
            self._application = self._build_application()
            await self._application.initialize()
            await self._application.start()
            if getattr(self._application, "updater", None) is not None:
                await self._application.updater.start_polling()
            self._client = self._application.bot
            return
        if self.mode == "webhook":
            self._client = self._build_bot()
            return
        raise ValueError(f"Unsupported telegram mode: {self.mode}")

    async def stop(self) -> None:
        if self._application is None:
            return
        updater = getattr(self._application, "updater", None)
        if updater is not None:
            await updater.stop()
        await self._application.stop()
        await self._application.shutdown()
        self._application = None

    async def handle_update_payload(self, payload: dict[str, Any]) -> None:
        message = self._normalize_payload(payload)
        if message is None:
            return
        await self.inbound_sink.accept_inbound(message)

    async def send(self, message: OutboundMessage) -> None:
        target = outbound_target(message)
        if not target.peer_id:
            mark_unclaimed(message)
            return
        client = self._require_client()
        kwargs: dict[str, Any] = {"chat_id": target.peer_id}
        if target.thread_id:
            kwargs["message_thread_id"] = int(target.thread_id) if str(target.thread_id).isdigit() else target.thread_id
        for chunk in chunk_text(message.content, max_chars=self.max_message_chars):
            await client.send_message(**kwargs, text=chunk)

    def _normalize_payload(self, payload: dict[str, Any]) -> InboundMessage | None:
        data = payload.get("message") or payload.get("edited_message")
        if not isinstance(data, dict):
            return None

        chat = data.get("chat") if isinstance(data.get("chat"), dict) else {}
        sender = data.get("from") if isinstance(data.get("from"), dict) else {}
        peer_id = _string_or_none(chat.get("id"))
        if not peer_id:
            return None

        chat_type = str(chat.get("type") or "unknown")
        peer_type = self._peer_type(chat_type)
        user_id = _string_or_none(sender.get("id"))
        message_id = _string_or_none(data.get("message_id"))
        thread_id = _string_or_none(data.get("message_thread_id"))

        content = str(data.get("text") or data.get("caption") or "").strip()
        media_entries = self._media_entries(data)
        if media_entries:
            content = "\n".join([part for part in [content, *media_entries] if part]).strip()
        if not content:
            return None

        if peer_type in {"group", "channel"} and not self._group_allowed(peer_id, user_id):
            return None
        if peer_type == "dm" and not self._dm_allowed(user_id or peer_id):
            return None

        if peer_type in {"group", "channel"} and config_bool(self.config, "requireMentionInGroups", default=False):
            gated = self._strip_required_mention(content)
            if gated is None:
                return None
            content = gated

        metadata = {
            "chat_id": peer_id,
            "message_id": message_id,
            "chat_type": chat_type,
        }
        if media_entries:
            metadata["media"] = media_entries

        return build_inbound_message(
            channel_id=self.channel_id,
            kind=self.kind,
            account_id=self.account_id,
            peer_id=peer_id,
            thread_id=thread_id,
            peer_type=peer_type,
            user_id=user_id,
            message_id=message_id,
            content=content,
            metadata=metadata,
        )

    def _media_entries(self, data: dict[str, Any]) -> list[str]:
        entries: list[str] = []
        if data.get("photo"):
            entries.append(compact_media_summary("photo"))
        for media_type in ("document", "audio", "video"):
            value = data.get(media_type)
            if isinstance(value, dict):
                entries.append(compact_media_summary(media_type, file_name=_string_or_none(value.get("file_name"))))
        return entries

    def _strip_required_mention(self, content: str) -> str | None:
        username = str(self.config.get("botUsername") or "").strip().lstrip("@")
        if not username:
            return None
        mention = f"@{username}"
        if mention not in content:
            return None
        return content.replace(mention, "", 1).strip()

    def _dm_allowed(self, identifier: str | None) -> bool:
        allowlist = config_list(self.config, "allowFrom")
        if not allowlist:
            return True
        return bool(identifier and identifier in allowlist)

    def _group_allowed(self, peer_id: str | None, user_id: str | None) -> bool:
        allowlist = config_list(self.config, "groupAllowFrom")
        if not allowlist:
            return True
        return bool((peer_id and peer_id in allowlist) or (user_id and user_id in allowlist))

    def _peer_type(self, chat_type: str) -> str:
        if chat_type == "private":
            return "dm"
        if chat_type in {"group", "supergroup"}:
            return "group"
        if chat_type == "channel":
            return "channel"
        return chat_type or "unknown"

    def _require_client(self) -> Any:
        if self._client is None:
            self._client = self._build_bot()
        return self._client

    def _build_bot(self) -> Any:
        token = self._require_secret("botToken")
        try:
            from telegram import Bot
        except ImportError as exc:  # pragma: no cover - optional live dependency
            raise RuntimeError("Install beaver-backend[telegram] to enable TelegramAdapter") from exc
        return Bot(token=token)

    def _build_application(self) -> Any:
        if self._application_factory is not None:
            return self._application_factory()
        token = self._require_secret("botToken")
        try:
            from telegram.ext import Application
        except ImportError as exc:  # pragma: no cover - optional live dependency
            raise RuntimeError("Install beaver-backend[telegram] to enable TelegramAdapter") from exc

        async def handle(update: Any, context: Any) -> None:
            if hasattr(update, "to_dict"):
                await self.handle_update_payload(update.to_dict())

        application = Application.builder().token(token).build()
        try:
            from telegram.ext import MessageHandler, filters

            application.add_handler(MessageHandler(filters.ALL, handle))
        except Exception:
            pass
        return application

    def _require_secret(self, key: str) -> str:
        value = self.secrets.get(key)
        if not value:
            raise ValueError(f"{key} is required")
        return str(value)


def _string_or_none(value: Any) -> str | None:
    if value is None:
        return None
    text = str(value).strip()
    return text or None
