from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Any
from datetime import datetime, timezone

from sqlalchemy import inspect, text
from sqlalchemy.orm import Session

from app.core.config import get_settings

try:
    from pywebpush import WebPushException, webpush
except ImportError:  # pragma: no cover - defensive runtime fallback.
    WebPushException = Exception  # type: ignore[assignment]
    webpush = None  # type: ignore[assignment]


logger = logging.getLogger(__name__)


def _table_exists(db: Session, table_name: str) -> bool:
    bind = db.get_bind()
    if bind is None:
        return False
    return inspect(bind).has_table(table_name)


def _load_enabled_push_subscriptions(db: Session) -> list[dict[str, object]]:
    if not _table_exists(db, "tenant_push_subscriptions"):
        return []

    rows = db.execute(
        text(
            """
            SELECT endpoint, p256dh, auth
            FROM tenant_push_subscriptions
            WHERE enabled = 1
            ORDER BY updated_at DESC
            """
        )
    ).mappings().all()
    return [
        {
            "endpoint": str(row.get("endpoint") or ""),
            "keys": {
                "p256dh": str(row.get("p256dh") or ""),
                "auth": str(row.get("auth") or ""),
            },
        }
        for row in rows
        if row.get("endpoint") and row.get("p256dh") and row.get("auth")
    ]


def _mark_subscription_error(db: Session, endpoint: str, error_detail: str, *, disable: bool) -> None:
    if not endpoint or not _table_exists(db, "tenant_push_subscriptions"):
        return

    db.execute(
        text(
            """
            UPDATE tenant_push_subscriptions
            SET
                enabled = CASE WHEN :disable = 1 THEN 0 ELSE enabled END,
                last_error = :last_error,
                updated_at = :updated_at
            WHERE endpoint = :endpoint
            """
        ),
        {
            "disable": 1 if disable else 0,
            "last_error": error_detail[:1000],
            "updated_at": datetime.now(timezone.utc).isoformat(),
            "endpoint": endpoint,
        },
    )


def send_push_payload(db: Session, payload: dict[str, object]) -> int:
    if webpush is None:
        logger.warning("pywebpush non disponibile: notifica push non inviata")
        return 0

    private_key_path = Path(get_settings().push_vapid_private_key_file)
    if not private_key_path.exists():
        logger.warning("Chiave VAPID non trovata in %s: notifica push non inviata", private_key_path)
        return 0

    sent_count = 0
    for subscription in _load_enabled_push_subscriptions(db):
        endpoint = str(subscription.get("endpoint") or "")
        try:
            webpush(
                subscription_info=subscription,
                data=json.dumps(payload, ensure_ascii=False),
                vapid_private_key=str(private_key_path),
                vapid_claims={"sub": get_settings().push_vapid_subject},
                timeout=10,
            )
        except WebPushException as exc:
            response = getattr(exc, "response", None)
            status_code = getattr(response, "status_code", None)
            _mark_subscription_error(db, endpoint, str(exc), disable=status_code in {404, 410})
        except Exception as exc:  # pragma: no cover - keeps background scans safe.
            logger.exception("Invio push sotto-stock non riuscito")
            _mark_subscription_error(db, endpoint, str(exc), disable=False)
        else:
            sent_count += 1

    db.commit()
    return sent_count


__all__ = ["send_push_payload"]
