# retoor <retoor@molodetz.nl>

import logging
from typing import Any

from devplacepy.database import get_int_setting
from devplacepy.push import providers, store
from devplacepy.push.providers.base import Delivery

logger = logging.getLogger(__name__)

TIMEOUT_KEY = "push_delivery_timeout_seconds"
DEFAULT_TIMEOUT_SECONDS = 10
MIN_TIMEOUT_SECONDS = 1
MAX_TIMEOUT_SECONDS = 120


def timeout_seconds() -> float:
    seconds = get_int_setting(TIMEOUT_KEY, DEFAULT_TIMEOUT_SECONDS)
    return float(min(max(seconds, MIN_TIMEOUT_SECONDS), MAX_TIMEOUT_SECONDS))


def group_by_provider(
    registrations: list[dict[str, Any]],
) -> dict[str, list[dict[str, Any]]]:
    grouped: dict[str, list[dict[str, Any]]] = {}
    for registration in registrations:
        grouped.setdefault(store.provider_of(registration), []).append(registration)
    return grouped


def _open_client(provider, timeout: float):
    return provider.delivery_client(timeout)


async def notify_user(user_uid: str, payload: dict[str, Any]) -> None:
    registrations = store.active_for_user(user_uid)
    if not registrations:
        logger.debug("No active push subscriptions for user %s", user_uid)
        return

    grouped = group_by_provider(registrations)
    timeout = timeout_seconds()
    for name, rows in grouped.items():
        provider = providers.PROVIDERS.get(name)
        if provider is None:
            logger.warning(
                "Unknown push provider %s on %s subscriptions of user %s",
                name,
                len(rows),
                user_uid,
            )
            continue
        if not providers.is_active(provider):
            logger.debug(
                "Push provider %s is not active; skipping %s subscriptions",
                name,
                len(rows),
            )
            continue
        try:
            prepared = provider.prepare(payload)
        except Exception as exc:
            logger.error("Push provider %s could not build a payload: %s", name, exc)
            continue
        try:
            client = _open_client(provider, timeout)
        except Exception as exc:
            logger.error("Push provider %s could not open a client: %s", name, exc)
            continue
        try:
            for registration in rows:
                await _deliver_one(provider, client, registration, prepared, user_uid)
        finally:
            if provider.closes_delivery_client():
                await client.aclose()


async def notify_registration(
    registration: dict[str, Any], payload: dict[str, Any]
) -> Delivery:
    name = store.provider_of(registration)
    provider = providers.PROVIDERS.get(name)
    if provider is None:
        return Delivery(providers.REJECTED, f"unknown provider {name}")
    if not providers.is_active(provider):
        return Delivery(providers.REJECTED, f"provider {name} is not active")
    try:
        prepared = provider.prepare(payload)
    except Exception as exc:
        return Delivery(providers.REJECTED, str(exc))
    user_uid = registration.get("user_uid") or ""
    try:
        client = _open_client(provider, timeout_seconds())
    except Exception as exc:
        return Delivery(providers.REJECTED, str(exc))
    try:
        return await _deliver_one(provider, client, registration, prepared, user_uid)
    finally:
        if provider.closes_delivery_client():
            await client.aclose()


async def _deliver_one(provider, client, registration, prepared, user_uid) -> Delivery:
    try:
        outcome = await provider.deliver(client, registration, prepared)
    except Exception as exc:
        logger.error("Push provider %s raised for %s: %s", provider.name, user_uid, exc)
        return Delivery(providers.REJECTED, str(exc))
    if outcome.status == providers.ACCEPTED:
        logger.debug("Push delivered to %s via %s", user_uid, provider.name)
        return outcome
    if outcome.status == providers.DEAD:
        logger.warning(
            "Push dead via %s for %s: %s", provider.name, user_uid, outcome.detail
        )
        try:
            store.mark_dead(registration["id"], outcome.dead_before)
        except Exception as exc:
            logger.error("Could not soft-delete push subscription: %s", exc)
        return outcome
    logger.warning(
        "Push rejected by %s for %s: %s", provider.name, user_uid, outcome.detail
    )
    return outcome
