"""Immich-specific bot command handler — main dispatch class."""

from __future__ import annotations

import logging
from typing import Any

from ...database.models import (
    CommandConfig, CommandTracker, CommandTrackerListener,
    ServiceProvider, TelegramBot,
)
from ...services import make_immich_provider
from ...services.http_session import get_http_session
from ..base import CommandResponse, ProviderCommandHandler
from ..command_utils import get_last_event_str, get_trackers_for_provider
from ..handler import _render_cmd_template

from .albums import _cmd_albums, cmd_favorites, cmd_summary
from .common import _IMMICH_COMMANDS, fetch_albums_with_links
from .events import _cmd_events, cmd_latest, cmd_memory, cmd_random
from .search import cmd_find, cmd_person, cmd_place, cmd_search

_LOGGER = logging.getLogger(__name__)


async def _cmd_status(
    provider: ServiceProvider, locale: str,
    *,
    allowed_album_ids: set[str] | None = None,
) -> dict[str, Any]:
    trackers = await get_trackers_for_provider(provider.id)

    # Count only albums visible to this chat. Without the scope filter,
    # /status in a restricted chat leaks the full album count across the
    # provider.  ``None`` = no filter; empty set = show nothing.
    total_albums = 0
    for t in trackers:
        for aid in (t.collection_ids or []):
            if allowed_album_ids is None or aid in allowed_album_ids:
                total_albums += 1

    # Last-event timestamp is already scoped — see get_last_event_str, which
    # filters EventLog by collection_id against allowed_album_ids.
    tracker_ids = [t.id for t in trackers]
    last_str = await get_last_event_str(
        tracker_ids, allowed_album_ids=allowed_album_ids,
    )

    # Tracker counts (``trackers_active`` / ``trackers_total``) are a
    # per-provider aggregate — they'd leak info about trackers this chat
    # has no visibility into once we've scoped everything else.  Omitted.
    return {
        "total_albums": total_albums, "last_event": last_str,
    }


async def _cmd_people(
    provider: ServiceProvider, locale: str,
) -> dict[str, Any]:
    all_people: dict[str, str] = {}
    http = await get_http_session()
    immich = make_immich_provider(http, provider)
    people = await immich.client.get_people()
    all_people.update(people)
    names = sorted(all_people.values())
    return {"people": names}


class ImmichCommandHandler(ProviderCommandHandler):
    """Handles all Immich-specific bot commands."""

    provider_type = "immich"

    def get_provider_commands(self) -> set[str]:
        return _IMMICH_COMMANDS

    def get_rate_categories(self) -> dict[str, str]:
        return {
            "search": "search", "find": "search", "person": "search",
            "place": "search", "favorites": "search", "people": "search",
        }

    async def handle(
        self,
        cmd: str,
        args: str,
        count: int,
        locale: str,
        response_mode: str,
        provider: ServiceProvider,
        cmd_templates: dict[str, dict[str, str]],
        bot: TelegramBot,
        tracker: CommandTracker,
        config: CommandConfig,
        *,
        listener: CommandTrackerListener | None = None,
        allowed_album_ids: set[str] | None = None,
        page: int = 1,
    ) -> CommandResponse | None:
        if cmd == "status":
            ctx = await _cmd_status(provider, locale, allowed_album_ids=allowed_album_ids)
            return CommandResponse(text=_render_cmd_template(cmd_templates, "status", locale, ctx))
        if cmd == "albums":
            ctx = await _cmd_albums(provider, locale, allowed_album_ids=allowed_album_ids)
            return CommandResponse(text=_render_cmd_template(cmd_templates, "albums", locale, ctx))
        if cmd == "events":
            ctx = await _cmd_events(provider, count, locale, allowed_album_ids=allowed_album_ids)
            return CommandResponse(text=_render_cmd_template(cmd_templates, "events", locale, ctx))
        if cmd == "people":
            ctx = await _cmd_people(provider, locale)
            return CommandResponse(text=_render_cmd_template(cmd_templates, "people", locale, ctx))
        if cmd in ("search", "find", "person", "place", "latest",
                   "random", "favorites", "summary", "memory"):
            return await _cmd_immich(
                cmd, args, count, locale, response_mode,
                provider, cmd_templates,
                allowed_album_ids=allowed_album_ids, page=page,
            )
        return None


async def _cmd_immich(
    cmd: str, args: str, count: int, locale: str,
    response_mode: str, provider: ServiceProvider,
    cmd_templates: dict[str, dict[str, str]],
    *,
    allowed_album_ids: set[str] | None = None,
    page: int = 1,
) -> CommandResponse | None:
    """Handle commands that need Immich API access and may return media."""
    notification_trackers = await get_trackers_for_provider(provider.id)

    all_album_ids: list[str] = []
    seen: set[str] = set()
    for t in notification_trackers:
        for aid in (t.collection_ids or []):
            if aid not in seen:
                seen.add(aid)
                all_album_ids.append(aid)

    # Intersect with the scope resolved by the dispatcher (from the listener
    # override if set, otherwise from the notification-routing graph for this
    # chat).  ``None`` = no filter (rare); empty set = show nothing (common
    # when the chat has no tracker routing).
    if allowed_album_ids is not None:
        before = len(all_album_ids)
        all_album_ids = [aid for aid in all_album_ids if aid in allowed_album_ids]
        if not all_album_ids:
            # A command that sees zero albums is a routing/tracker config issue
            # the operator needs to notice — otherwise the user gets
            # "no results" with no hint at why.
            _LOGGER.info(
                "Command /%s has empty album scope for provider=%d (had %d trackers, chat scope allowed %d)",
                cmd, provider.id, before, len(allowed_album_ids),
            )

    ext_domain = (provider.config.get("external_domain") or provider.config.get("url", "")).rstrip("/")

    http = await get_http_session()
    immich = make_immich_provider(http, provider)
    client = immich.client

    # Build asset_id → public_url map from tracked albums' shared links
    asset_public_urls: dict[str, str] = {}
    if ext_domain and all_album_ids and cmd in ("search", "find", "person", "place", "favorites"):
        fetched = await fetch_albums_with_links(client, all_album_ids, ext_domain, include_failed=False)
        for album_data in fetched:
            pub_url = album_data.get("public_url", "")
            album_obj = album_data.get("_album")
            if pub_url and album_obj:
                for asset_id in album_obj.assets:
                    asset_public_urls[asset_id] = f"{pub_url}/photos/{asset_id}"

    # Wrap single-provider in a map for functions that still expect it
    providers_map = {provider.id: provider}

    result: str | dict[str, Any] | None = None

    if cmd == "search":
        result = await cmd_search(client, args, all_album_ids, count, locale, response_mode, cmd_templates, asset_public_urls=asset_public_urls, page=page)
    elif cmd == "find":
        result = await cmd_find(client, args, all_album_ids, count, locale, response_mode, cmd_templates, asset_public_urls=asset_public_urls, page=page)
    elif cmd == "person":
        result = await cmd_person(client, args, count, locale, response_mode, cmd_templates, asset_public_urls=asset_public_urls)
    elif cmd == "place":
        result = await cmd_place(client, args, all_album_ids, count, locale, response_mode, cmd_templates, asset_public_urls=asset_public_urls)
    elif cmd == "favorites":
        result = await cmd_favorites(providers_map, all_album_ids, count, locale, response_mode, client, cmd_templates)
    elif cmd == "latest":
        result = await cmd_latest(client, all_album_ids, count, locale, response_mode, cmd_templates, external_domain=ext_domain)
    elif cmd == "random":
        result = await cmd_random(client, all_album_ids, count, locale, response_mode, cmd_templates, external_domain=ext_domain)
    elif cmd == "summary":
        result = await cmd_summary(client, all_album_ids, locale, cmd_templates, external_domain=ext_domain)
    elif cmd == "memory":
        result = await cmd_memory(provider.id, client, all_album_ids, count, locale, response_mode, cmd_templates)

    if result is None:
        return None
    # _format_assets returns {"text": ..., "media": [...]} for media mode
    if isinstance(result, dict):
        return CommandResponse(
            text=result.get("text"),
            media=result.get("media", []),
        )
    return CommandResponse(text=result)
