"""Single entry point for all Telegram send operations.

Both the notification dispatcher (event-driven) and the bot command
handlers (user-driven) funnel their Telegram API calls through this
module. Keeping construction in one place means:

* The shared aiohttp session is always reused (one TCP pool for the
  whole process).
* The Telegram file_id caches (URL cache + asset cache) are always
  wired in, so repeated sends — whether from a scheduled tracker or
  a ``/latest`` command — reuse cached file_ids instead of re-uploading
  the same bytes.
* Future cross-cutting concerns (rate limiting, telemetry, retries)
  have exactly one place to live.

The actual Telegram API routine is still ``TelegramClient`` in core —
this module just guarantees every caller gets a properly-wired client.
"""

from __future__ import annotations

import contextlib
from typing import Any, AsyncIterator, Callable

import aiohttp

from notify_bridge_core.notifications.telegram.client import (
    NotificationResult,
    TelegramClient,
)

from .http_session import get_http_session
from .watcher import _get_telegram_caches


async def get_telegram_client(
    bot_token: str,
    *,
    session: aiohttp.ClientSession | None = None,
    thumbhash_resolver: Callable[[str], str | None] | None = None,
) -> TelegramClient:
    """Return a ``TelegramClient`` wired to shared session + shared caches.

    Every Telegram send in the process should acquire its client from
    here — constructing ``TelegramClient`` directly skips the caches and
    silently halves cache hit rate.

    Args:
        bot_token: The bot's API token.
        session: Optional explicit aiohttp session. Defaults to the
            process-wide shared session.
        thumbhash_resolver: Optional asset-id → thumbhash lookup. The
            notification dispatcher passes one so asset-cache entries
            invalidate on visual change; the command path doesn't need it
            (commands always ask for a fresh result).
    """
    if session is None:
        session = await get_http_session()
    url_cache, asset_cache = await _get_telegram_caches()
    return TelegramClient(
        session, bot_token,
        url_cache=url_cache,
        asset_cache=asset_cache,
        thumbhash_resolver=thumbhash_resolver,
    )


async def send_telegram_message(
    bot_token: str,
    chat_id: str,
    text: str,
    *,
    reply_to_message_id: int | None = None,
    disable_web_page_preview: bool = True,
    parse_mode: str = "HTML",
) -> NotificationResult:
    """Send a plain-text Telegram message with caches wired in."""
    client = await get_telegram_client(bot_token)
    return await client.send_message(
        chat_id, text,
        reply_to_message_id=reply_to_message_id,
        disable_web_page_preview=disable_web_page_preview,
        parse_mode=parse_mode,
    )


async def send_telegram_media(
    bot_token: str,
    chat_id: str,
    assets: list[dict[str, Any]],
    *,
    caption: str | None = None,
    reply_to_message_id: int | None = None,
    max_group_size: int = 10,
    chunk_delay: int = 0,
    max_asset_data_size: int | None = None,
    send_large_photos_as_documents: bool = False,
    chat_action: str | None = "typing",
    thumbhash_resolver: Callable[[str], str | None] | None = None,
) -> NotificationResult:
    """Send a Telegram media group (or single asset) with caches wired in.

    ``assets`` must be in ``TelegramClient`` format — see
    ``notify_bridge_core.notifications.telegram.media.build_telegram_asset_entry``
    for the canonical builder.
    """
    client = await get_telegram_client(
        bot_token, thumbhash_resolver=thumbhash_resolver,
    )
    return await client.send_notification(
        chat_id,
        assets=assets,
        caption=caption,
        reply_to_message_id=reply_to_message_id,
        max_group_size=max_group_size,
        chunk_delay=chunk_delay,
        max_asset_data_size=max_asset_data_size,
        send_large_photos_as_documents=send_large_photos_as_documents,
        chat_action=chat_action,
    )


@contextlib.asynccontextmanager
async def telegram_chat_action(
    bot_token: str,
    chat_id: str,
    action: str | None,
) -> AsyncIterator[None]:
    """Hold a Telegram chat action (e.g. ``upload_photo``) for the block's duration.

    Used by the command path to show ``typing`` / ``uploading photo`` while
    the bot fetches assets from the service (Immich, etc.) AND uploads them
    to Telegram — i.e. for the whole user-visible wait, not just the upload.

    A ``None`` action makes this a no-op so callers don't have to branch.
    """
    if not action:
        yield
        return

    client = await get_telegram_client(bot_token)
    task = client.start_chat_action_keepalive(chat_id, action)
    try:
        yield
    finally:
        await client.stop_keepalive(task)
