"""Shared service utilities."""

from __future__ import annotations

from typing import Any, Protocol

import aiohttp

from notify_bridge_core.providers.immich import ImmichServiceProvider
from notify_bridge_core.providers.gitea import GiteaServiceProvider
from notify_bridge_core.providers.planka import PlankaServiceProvider
from notify_bridge_core.providers.nut import NutServiceProvider
from notify_bridge_core.providers.google_photos import GooglePhotosServiceProvider

from ..database.models import ServiceProvider

# Default timeout for all outgoing HTTP requests to external services.
DEFAULT_HTTP_TIMEOUT = aiohttp.ClientTimeout(total=30)


class CollectionProvider(Protocol):
    """Protocol for providers that can list collections."""

    async def list_collections(self) -> list[dict[str, Any]]: ...


class TestableProvider(Protocol):
    """Protocol for providers that support connection testing."""

    async def test_connection(self) -> dict[str, Any]: ...


def make_immich_provider(http_session: aiohttp.ClientSession, provider: ServiceProvider) -> ImmichServiceProvider:
    """Create an ImmichServiceProvider from a DB provider model."""
    config = provider.config or {}
    return ImmichServiceProvider(
        http_session,
        config.get("url", ""),
        config.get("api_key", ""),
        config.get("external_domain"),
        provider.name,
    )


def make_gitea_provider(http_session: aiohttp.ClientSession, provider: ServiceProvider) -> GiteaServiceProvider:
    """Create a GiteaServiceProvider from a DB provider model."""
    config = provider.config or {}
    return GiteaServiceProvider(
        http_session,
        config.get("url", ""),
        config.get("api_token", ""),
        provider.name,
    )


def make_planka_provider(http_session: aiohttp.ClientSession, provider: ServiceProvider) -> PlankaServiceProvider:
    """Create a PlankaServiceProvider from a DB provider model."""
    config = provider.config or {}
    return PlankaServiceProvider(
        http_session,
        config.get("url", ""),
        config.get("api_key", ""),
        provider.name,
    )


def make_nut_provider(provider: ServiceProvider) -> NutServiceProvider:
    """Create a NutServiceProvider from a DB provider model."""
    config = provider.config or {}
    return NutServiceProvider(
        host=config.get("host", "localhost"),
        port=config.get("port", 3493),
        username=config.get("username"),
        password=config.get("password"),
        name=provider.name,
    )


def make_google_photos_provider(http_session: aiohttp.ClientSession, provider: ServiceProvider) -> GooglePhotosServiceProvider:
    """Create a GooglePhotosServiceProvider from a DB provider model."""
    config = provider.config or {}
    return GooglePhotosServiceProvider(
        http_session,
        config.get("client_id", ""),
        config.get("client_secret", ""),
        config.get("refresh_token", ""),
        provider.name,
    )


# ---------------------------------------------------------------------------
# Provider factory registry — maps provider type strings to factory callables
# that create a provider with a ``list_collections`` method. Providers that
# require an API credential skip creation when the credential is missing
# (the factory returns None in that case).
# ---------------------------------------------------------------------------

def _make_collection_provider(
    http_session: aiohttp.ClientSession,
    provider: ServiceProvider,
) -> CollectionProvider | None:
    """Create a CollectionProvider for the given DB provider, or None if unsupported."""
    ptype = provider.type
    config = provider.config or {}

    if ptype == "immich":
        return make_immich_provider(http_session, provider)
    if ptype == "gitea":
        if not config.get("api_token"):
            return None
        return make_gitea_provider(http_session, provider)
    if ptype == "planka":
        if not config.get("api_key"):
            return None
        return make_planka_provider(http_session, provider)
    if ptype == "google_photos":
        return make_google_photos_provider(http_session, provider)
    if ptype == "home_assistant":
        from notify_bridge_core.providers.home_assistant import HomeAssistantServiceProvider
        return HomeAssistantServiceProvider(
            session=http_session,
            url=config.get("url", ""),
            access_token=config.get("access_token", ""),
            verify_tls=bool(config.get("verify_tls", True)),
            event_types=config.get("event_types") or None,
            name=provider.name,
        )
    # NUT provider needs no http_session
    if ptype == "nut":
        return make_nut_provider(provider)  # type: ignore[return-value]
    return None


# Set of provider types that need an aiohttp session for collection listing.
_HTTP_COLLECTION_PROVIDERS = {"immich", "gitea", "planka", "google_photos", "home_assistant"}


async def list_provider_collections(provider: ServiceProvider) -> list[dict[str, Any]]:
    """List collections for any supported provider type.

    Returns an empty list for providers that don't support collections or
    are missing required credentials.
    """
    if provider.type in _HTTP_COLLECTION_PROVIDERS:
        from .http_session import get_http_session
        http_session = await get_http_session()
        svc = _make_collection_provider(http_session, provider)
        if svc is None:
            return []
        return await svc.list_collections()

    # Non-HTTP providers (e.g. NUT)
    svc = _make_collection_provider(None, provider)  # type: ignore[arg-type]
    if svc is None:
        return []
    return await svc.list_collections()
