from __future__ import annotations

import json
import logging
from typing import Any

import aiosqlite
from pywebpush import WebPushException, webpush

from server.db import utc_now_iso
from server.settings import settings

logger = logging.getLogger(__name__)


async def save_subscription(db: aiosqlite.Connection, sub: dict[str, Any]) -> None:
    keys = sub.get("keys", {})
    await db.execute(
        """
        INSERT INTO push_subscription (endpoint, p256dh, auth, created_at)
        VALUES (?, ?, ?, ?)
        ON CONFLICT(endpoint) DO UPDATE SET p256dh=excluded.p256dh, auth=excluded.auth
        """,
        (sub["endpoint"], keys.get("p256dh", ""), keys.get("auth", ""), utc_now_iso()),
    )
    await db.commit()


async def get_subscriptions(db: aiosqlite.Connection) -> list[dict[str, str]]:
    cur = await db.execute("SELECT endpoint, p256dh, auth FROM push_subscription")
    rows = await cur.fetchall()
    await cur.close()
    return [{"endpoint": r[0], "p256dh": r[1], "auth": r[2]} for r in rows]


def _vapid_claims() -> dict[str, str]:
    return {"sub": settings.vapid_claims_email}


def _endpoint_label(endpoint: str) -> str:
    return endpoint[:60] + ("..." if len(endpoint) > 60 else "")


async def send_push(
    db: aiosqlite.Connection,
    payload: dict[str, Any],
) -> int:
    tag = payload.get("tag", "?")
    slot_id = payload.get("slot_id", "?")

    if not settings.vapid_private_key or not settings.vapid_public_key:
        logger.warning("Push skipped slot=%s tag=%s reason=vapid_not_configured", slot_id, tag)
        return 0

    subs = await get_subscriptions(db)
    if not subs:
        logger.warning("Push skipped slot=%s tag=%s reason=no_subscriptions", slot_id, tag)
        return 0

    sent = 0
    failed = 0
    dead: list[str] = []

    logger.info(
        "Push start slot=%s tag=%s subscriptions=%d title=%r",
        slot_id,
        tag,
        len(subs),
        payload.get("title"),
    )

    for sub in subs:
        endpoint = sub["endpoint"]
        subscription = {
            "endpoint": endpoint,
            "keys": {"p256dh": sub["p256dh"], "auth": sub["auth"]},
        }
        try:
            webpush(
                subscription_info=subscription,
                data=json.dumps(payload),
                vapid_private_key=settings.vapid_private_key,
                vapid_claims=_vapid_claims(),
            )
            sent += 1
            logger.info("Push ok slot=%s endpoint=%s", slot_id, _endpoint_label(endpoint))
        except WebPushException as exc:
            status = exc.response.status_code if exc.response else None
            if status in (404, 410):
                dead.append(endpoint)
                logger.info(
                    "Push dead slot=%s endpoint=%s status=%s",
                    slot_id,
                    _endpoint_label(endpoint),
                    status,
                )
            else:
                failed += 1
                logger.warning(
                    "Push failed slot=%s endpoint=%s status=%s error=%s",
                    slot_id,
                    _endpoint_label(endpoint),
                    status,
                    exc,
                )

    for endpoint in dead:
        await db.execute("DELETE FROM push_subscription WHERE endpoint = ?", (endpoint,))
    if dead:
        await db.commit()
        logger.info("Push removed %d dead subscription(s)", len(dead))

    logger.info(
        "Push done slot=%s tag=%s sent=%d failed=%d dead=%d total=%d",
        slot_id,
        tag,
        sent,
        failed,
        len(dead),
        len(subs),
    )
    return sent


async def send_slot_reminder(
    db: aiosqlite.Connection,
    slot_id: str,
    title: str,
    body: str,
    *,
    silent: bool = True,
) -> int:
    payload = {
        "title": title,
        "body": body,
        "slot_id": slot_id,
        "silent": silent,
        "tag": f"med-{slot_id}",
    }
    return await send_push(db, payload)
