# @File: backend/routers/uptimerobot_client.py
# @Desc: End-user UptimeRobot endpoints. All routes require an authenticated
#        DoItEasy user JWT; ownership is enforced on every read/write path
#        by scoping queries to the current user's active subscription.
from __future__ import annotations

import json
import logging
from typing import List, Optional

from core.database import get_db
from dependencies.auth import get_current_user
from fastapi import APIRouter, Depends, HTTPException, Query, status
from models.uptimerobot import (
    UptimeAlertContact,
    UptimeClientMonitor,
    UptimeClientSubscription,
    UptimeMaintenanceWindow,
    UptimePackage,
    UptimeStatusPage,
)
from schemas.auth import UserResponse
from schemas.uptimerobot import (
    UptimeAlertContactCreate,
    UptimeAlertContactOut,
    UptimeClientDashboardOut,
    UptimeMaintenanceWindowCreate,
    UptimeMaintenanceWindowOut,
    UptimeMonitorCreate,
    UptimeMonitorOut,
    UptimeMonitorUpdate,
    UptimeNotificationPrefOut,
    UptimeNotificationPrefUpdate,
    UptimePackageOut,
    UptimeStatusPageCreate,
    UptimeStatusPageOut,
    UptimeSubscriptionOut,
    UptimeUsageMeter,
)
from services.uptimerobot import UptimeRobotError, get_client
from services.uptimerobot.monitors import (
    create_monitor,
    delete_monitor,
    fetch_monitor_logs,
    get_monitor,
    list_monitors_for_subscription,
    set_monitor_paused,
    update_monitor,
)
from services.uptimerobot.packages import assert_quota_ok, get_package, list_packages
from services.uptimerobot.subscriptions import (
    create_subscription,
    get_active_subscription_for_user,
    refresh_usage_counters,
)
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v1/uptimerobot", tags=["uptimerobot-client"])


async def _require_active_subscription(
    db: AsyncSession, user: UserResponse
) -> UptimeClientSubscription:
    sub = await get_active_subscription_for_user(db, user.id)
    if sub is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="You don't have an active UptimeRobot subscription yet.",
        )
    if sub.status != "active":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail=f"Subscription is currently {sub.status}.",
        )
    return sub


def _meter(used: int, cap: int, unlimited: bool) -> UptimeUsageMeter:
    total = None if unlimited else cap
    pct = None
    if total and total > 0:
        pct = int(round((used / total) * 100))
    return UptimeUsageMeter(used=used, total=total, percent=pct, unlimited=unlimited)


def _build_dashboard(
    sub: UptimeClientSubscription, pkg: UptimePackage
) -> UptimeClientDashboardOut:
    return UptimeClientDashboardOut(
        subscription=UptimeSubscriptionOut.model_validate(sub),
        package=UptimePackageOut.model_validate(pkg),
        quota_monitors=_meter(sub.monitors_used, pkg.max_monitors, pkg.monitors_unlimited),
        quota_alert_contacts=_meter(
            sub.alert_contacts_used, pkg.max_alert_contacts, pkg.alert_contacts_unlimited
        ),
        quota_maintenance_windows=_meter(
            sub.maintenance_windows_used,
            pkg.max_maintenance_windows,
            pkg.maintenance_windows_unlimited,
        ),
        quota_status_pages=_meter(
            sub.status_pages_used, pkg.max_status_pages, pkg.status_pages_unlimited
        ),
    )


# ---------- Subscription / dashboard ----------
@router.get("/subscription", response_model=UptimeClientDashboardOut)
async def get_subscription_dashboard(
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> UptimeClientDashboardOut:
    sub = await get_active_subscription_for_user(db, current_user.id)
    if sub is None:
        # Return a structured empty dashboard so the frontend can render
        # a friendly "no subscription" state without extra round trips.
        empty_pkg = UptimePackage(
            id=0,
            slug="none",
            name="No active plan",
            description=None,
            allowed_monitor_types="",
            terminate_action="archive",
        )
        empty_sub = UptimeClientSubscription(
            id=0,
            user_id=current_user.id,
            user_email=current_user.email,
            package_id=0,
            status="none",
        )
        return UptimeClientDashboardOut(
            subscription=None,
            package=None,
            quota_monitors=_meter(0, 0, False),
            quota_alert_contacts=_meter(0, 0, False),
            quota_maintenance_windows=_meter(0, 0, False),
            quota_status_pages=_meter(0, 0, False),
        )
    pkg = await get_package(db, sub.package_id)
    if pkg is None:
        raise HTTPException(status_code=500, detail="Package missing for subscription.")
    await refresh_usage_counters(db, sub)
    await db.commit()
    return _build_dashboard(sub, pkg)


# ---------- Self-service signup ----------
@router.get("/packages", response_model=List[UptimePackageOut])
async def client_list_available_packages(
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> List[UptimePackageOut]:
    """Return all active packages clients can self-activate.

    Auth is required so signed-out visitors can't probe package pricing.
    Inactive packages are filtered out on purpose — admins use a separate
    admin endpoint when they need to see the full catalog.
    """
    pkgs = await list_packages(db, include_inactive=False)
    active = [p for p in pkgs if p.is_active]
    return [UptimePackageOut.model_validate(p) for p in active]


@router.post(
    "/subscription/activate",
    response_model=UptimeClientDashboardOut,
    status_code=status.HTTP_201_CREATED,
)
async def client_activate_subscription(
    payload: dict,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> UptimeClientDashboardOut:
    """Self-service: activate a subscription on the chosen package.

    Rejects the call if the user already has a non-terminated subscription
    so clients can't accidentally double-subscribe. WHMCS fields are left
    NULL on purpose — this path is fully decoupled from WHMCS billing.
    """
    package_id = payload.get("package_id") if isinstance(payload, dict) else None
    try:
        package_id_int = int(package_id)
    except (TypeError, ValueError):
        raise HTTPException(status_code=400, detail="package_id is required.")

    existing = await get_active_subscription_for_user(db, current_user.id)
    if existing is not None and existing.status in ("active", "suspended"):
        raise HTTPException(
            status_code=409,
            detail="You already have an active or suspended subscription.",
        )

    pkg = await get_package(db, package_id_int)
    if pkg is None or not pkg.is_active:
        raise HTTPException(
            status_code=404, detail="Package not found or inactive."
        )

    try:
        sub = await create_subscription(
            db,
            user_id=current_user.id,
            package_id=package_id_int,
            user_email=current_user.email,
        )
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc))

    logger.info(
        "Uptime subscription self-activated: user_id=%s package_id=%s",
        current_user.id,
        package_id_int,
    )
    # Ensure counters are fresh before returning the dashboard.
    await refresh_usage_counters(db, sub)
    await db.commit()
    return _build_dashboard(sub, pkg)


# ---------- Notification preferences ----------
@router.get("/notification-preferences", response_model=UptimeNotificationPrefOut)
async def client_get_notification_preferences(
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> UptimeNotificationPrefOut:
    """Return the email-on-status-change toggle for the current user.

    Returns a sensible disabled default when the user has no active
    subscription yet so the frontend can still render the control.
    """
    sub = await get_active_subscription_for_user(db, current_user.id)
    if sub is None:
        return UptimeNotificationPrefOut(
            email_notifications_enabled=False,
            notify_email=current_user.email,
        )
    return UptimeNotificationPrefOut(
        email_notifications_enabled=bool(sub.email_notifications_enabled),
        notify_email=sub.user_email or current_user.email,
    )


@router.patch("/notification-preferences", response_model=UptimeNotificationPrefOut)
async def client_update_notification_preferences(
    payload: UptimeNotificationPrefUpdate,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> UptimeNotificationPrefOut:
    """Enable or disable email alerts on monitor status changes."""
    sub = await _require_active_subscription(db, current_user)
    sub.email_notifications_enabled = bool(payload.email_notifications_enabled)
    # Make sure we always have a deliverable address on file; fall back to
    # the JWT email if user_email was never populated.
    if not sub.user_email and current_user.email:
        sub.user_email = current_user.email
    await db.commit()
    await db.refresh(sub)
    logger.info(
        "Uptime email notifications %s for user_id=%s",
        "enabled" if sub.email_notifications_enabled else "disabled",
        current_user.id,
    )
    return UptimeNotificationPrefOut(
        email_notifications_enabled=bool(sub.email_notifications_enabled),
        notify_email=sub.user_email or current_user.email,
    )


# ---------- Monitors ----------
@router.get("/monitors", response_model=List[UptimeMonitorOut])
async def client_list_monitors(
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> List[UptimeMonitorOut]:
    sub = await _require_active_subscription(db, current_user)
    monitors = await list_monitors_for_subscription(db, sub.id)
    return [UptimeMonitorOut.model_validate(m) for m in monitors]


@router.post(
    "/monitors", response_model=UptimeMonitorOut, status_code=status.HTTP_201_CREATED
)
async def client_create_monitor(
    payload: UptimeMonitorCreate,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> UptimeMonitorOut:
    sub = await _require_active_subscription(db, current_user)
    try:
        monitor = await create_monitor(
            db,
            sub,
            friendly_name=payload.friendly_name,
            monitor_type=payload.monitor_type,
            url=payload.url,
            keyword=payload.keyword,
            port=payload.port,
            interval_seconds=payload.interval_seconds,
        )
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc))
    except UptimeRobotError as exc:
        raise HTTPException(status_code=502, detail=f"UptimeRobot: {exc.message}")
    return UptimeMonitorOut.model_validate(monitor)


@router.patch("/monitors/{monitor_id}", response_model=UptimeMonitorOut)
async def client_update_monitor(
    monitor_id: int,
    payload: UptimeMonitorUpdate,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> UptimeMonitorOut:
    monitor = await get_monitor(db, monitor_id, user_id=current_user.id)
    if monitor is None:
        raise HTTPException(status_code=404, detail="Monitor not found")
    try:
        updated = await update_monitor(
            db,
            monitor,
            friendly_name=payload.friendly_name,
            url=payload.url,
            keyword=payload.keyword,
            port=payload.port,
            interval_seconds=payload.interval_seconds,
        )
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc))
    except UptimeRobotError as exc:
        raise HTTPException(status_code=502, detail=f"UptimeRobot: {exc.message}")
    return UptimeMonitorOut.model_validate(updated)


@router.post("/monitors/{monitor_id}/pause", response_model=UptimeMonitorOut)
async def client_pause_monitor(
    monitor_id: int,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> UptimeMonitorOut:
    monitor = await get_monitor(db, monitor_id, user_id=current_user.id)
    if monitor is None:
        raise HTTPException(status_code=404, detail="Monitor not found")
    try:
        await set_monitor_paused(db, monitor, True)
    except UptimeRobotError as exc:
        raise HTTPException(status_code=502, detail=f"UptimeRobot: {exc.message}")
    return UptimeMonitorOut.model_validate(monitor)


@router.post("/monitors/{monitor_id}/resume", response_model=UptimeMonitorOut)
async def client_resume_monitor(
    monitor_id: int,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> UptimeMonitorOut:
    monitor = await get_monitor(db, monitor_id, user_id=current_user.id)
    if monitor is None:
        raise HTTPException(status_code=404, detail="Monitor not found")
    try:
        await set_monitor_paused(db, monitor, False)
    except UptimeRobotError as exc:
        raise HTTPException(status_code=502, detail=f"UptimeRobot: {exc.message}")
    return UptimeMonitorOut.model_validate(monitor)


@router.delete("/monitors/{monitor_id}", status_code=status.HTTP_204_NO_CONTENT)
async def client_delete_monitor(
    monitor_id: int,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> None:
    monitor = await get_monitor(db, monitor_id, user_id=current_user.id)
    if monitor is None:
        return
    await delete_monitor(db, monitor)


@router.get("/monitors/{monitor_id}/logs")
async def client_monitor_logs(
    monitor_id: int,
    limit: int = Query(20, ge=1, le=100),
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    monitor = await get_monitor(db, monitor_id, user_id=current_user.id)
    if monitor is None:
        raise HTTPException(status_code=404, detail="Monitor not found")
    try:
        return await fetch_monitor_logs(monitor, limit=limit)
    except UptimeRobotError as exc:
        raise HTTPException(status_code=502, detail=f"UptimeRobot: {exc.message}")


# ---------- Alert contacts ----------
@router.get("/alert-contacts", response_model=List[UptimeAlertContactOut])
async def client_list_alert_contacts(
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> List[UptimeAlertContactOut]:
    sub = await _require_active_subscription(db, current_user)
    result = await db.execute(
        select(UptimeAlertContact)
        .where(UptimeAlertContact.subscription_id == sub.id)
        .order_by(UptimeAlertContact.created_at.desc())
    )
    return [UptimeAlertContactOut.model_validate(r) for r in result.scalars().all()]


@router.post(
    "/alert-contacts",
    response_model=UptimeAlertContactOut,
    status_code=status.HTTP_201_CREATED,
)
async def client_add_alert_contact(
    payload: UptimeAlertContactCreate,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> UptimeAlertContactOut:
    sub = await _require_active_subscription(db, current_user)
    pkg = await get_package(db, sub.package_id)
    if pkg is None:
        raise HTTPException(status_code=500, detail="Package missing for subscription.")
    await refresh_usage_counters(db, sub)
    try:
        assert_quota_ok(pkg, sub.alert_contacts_used, limit_field="max_alert_contacts")
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc))

    client = await get_client(user_email=current_user.email)
    try:
        ur_resp = await client.create_alert_contact(
            {
                "type": payload.contact_type,
                "value": payload.value,
                "friendly_name": payload.friendly_name or payload.value,
            }
        )
    except UptimeRobotError as exc:
        raise HTTPException(status_code=502, detail=f"UptimeRobot: {exc.message}")
    ur_id = None
    if isinstance(ur_resp, dict):
        ur_id = (
            ur_resp.get("id")
            or ur_resp.get("contact_id")
            or (ur_resp.get("data") or {}).get("id")
        )
    if not ur_id:
        raise HTTPException(
            status_code=502, detail="UptimeRobot did not return a contact id"
        )
    row = UptimeAlertContact(
        subscription_id=sub.id,
        user_id=current_user.id,
        ur_contact_id=str(ur_id),
        contact_type=payload.contact_type,
        friendly_name=payload.friendly_name,
        value=payload.value,
        verified=False,
    )
    db.add(row)
    await refresh_usage_counters(db, sub)
    await db.commit()
    await db.refresh(row)
    return UptimeAlertContactOut.model_validate(row)


@router.delete("/alert-contacts/{contact_id}", status_code=status.HTTP_204_NO_CONTENT)
async def client_delete_alert_contact(
    contact_id: int,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> None:
    row = await db.get(UptimeAlertContact, contact_id)
    if row is None or row.user_id != current_user.id:
        return
    client = await get_client(user_email=current_user.email)
    try:
        await client.delete_alert_contact(row.ur_contact_id)
    except UptimeRobotError as exc:
        logger.warning(
            "Upstream delete of alert contact %s failed (continuing): %s",
            row.ur_contact_id,
            exc.message,
        )
    sub = await db.get(UptimeClientSubscription, row.subscription_id)
    await db.delete(row)
    if sub is not None:
        await refresh_usage_counters(db, sub)
    await db.commit()


# ---------- Maintenance windows ----------
@router.get("/maintenance-windows", response_model=List[UptimeMaintenanceWindowOut])
async def client_list_windows(
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> List[UptimeMaintenanceWindowOut]:
    sub = await _require_active_subscription(db, current_user)
    result = await db.execute(
        select(UptimeMaintenanceWindow)
        .where(UptimeMaintenanceWindow.subscription_id == sub.id)
        .order_by(UptimeMaintenanceWindow.created_at.desc())
    )
    return [UptimeMaintenanceWindowOut.model_validate(r) for r in result.scalars().all()]


@router.post(
    "/maintenance-windows",
    response_model=UptimeMaintenanceWindowOut,
    status_code=status.HTTP_201_CREATED,
)
async def client_add_window(
    payload: UptimeMaintenanceWindowCreate,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> UptimeMaintenanceWindowOut:
    sub = await _require_active_subscription(db, current_user)
    pkg = await get_package(db, sub.package_id)
    if pkg is None:
        raise HTTPException(status_code=500, detail="Package missing for subscription.")
    await refresh_usage_counters(db, sub)
    try:
        assert_quota_ok(
            pkg, sub.maintenance_windows_used, limit_field="max_maintenance_windows"
        )
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc))

    client = await get_client(user_email=current_user.email)
    try:
        ur_resp = await client.create_maintenance_window(
            {
                "friendly_name": payload.friendly_name,
                "type": payload.window_type,
                "start_time": payload.start_time,
                "duration": payload.duration_minutes,
            }
        )
    except UptimeRobotError as exc:
        raise HTTPException(status_code=502, detail=f"UptimeRobot: {exc.message}")
    ur_id = None
    if isinstance(ur_resp, dict):
        ur_id = (
            ur_resp.get("id")
            or ur_resp.get("window_id")
            or (ur_resp.get("data") or {}).get("id")
        )
    if not ur_id:
        raise HTTPException(
            status_code=502, detail="UptimeRobot did not return a maintenance window id"
        )
    row = UptimeMaintenanceWindow(
        subscription_id=sub.id,
        user_id=current_user.id,
        ur_window_id=str(ur_id),
        friendly_name=payload.friendly_name,
        window_type=payload.window_type,
        start_time=payload.start_time,
        duration_minutes=payload.duration_minutes,
        status="active",
    )
    db.add(row)
    await refresh_usage_counters(db, sub)
    await db.commit()
    await db.refresh(row)
    return UptimeMaintenanceWindowOut.model_validate(row)


@router.delete("/maintenance-windows/{window_id}", status_code=status.HTTP_204_NO_CONTENT)
async def client_delete_window(
    window_id: int,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> None:
    row = await db.get(UptimeMaintenanceWindow, window_id)
    if row is None or row.user_id != current_user.id:
        return
    client = await get_client(user_email=current_user.email)
    try:
        await client.delete_maintenance_window(row.ur_window_id)
    except UptimeRobotError as exc:
        logger.warning(
            "Upstream delete of maintenance window %s failed (continuing): %s",
            row.ur_window_id,
            exc.message,
        )
    sub = await db.get(UptimeClientSubscription, row.subscription_id)
    await db.delete(row)
    if sub is not None:
        await refresh_usage_counters(db, sub)
    await db.commit()


# ---------- Status pages ----------
@router.get("/status-pages", response_model=List[UptimeStatusPageOut])
async def client_list_pages(
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> List[UptimeStatusPageOut]:
    sub = await _require_active_subscription(db, current_user)
    result = await db.execute(
        select(UptimeStatusPage)
        .where(UptimeStatusPage.subscription_id == sub.id)
        .order_by(UptimeStatusPage.created_at.desc())
    )
    out: List[UptimeStatusPageOut] = []
    for row in result.scalars().all():
        monitor_ids: List[str] = []
        if row.monitor_ids_json:
            try:
                parsed = json.loads(row.monitor_ids_json)
                if isinstance(parsed, list):
                    monitor_ids = [str(x) for x in parsed]
            except (TypeError, ValueError):
                pass
        data = UptimeStatusPageOut.model_validate(row)
        data.monitor_ids = monitor_ids
        out.append(data)
    return out


@router.post(
    "/status-pages",
    response_model=UptimeStatusPageOut,
    status_code=status.HTTP_201_CREATED,
)
async def client_add_page(
    payload: UptimeStatusPageCreate,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> UptimeStatusPageOut:
    sub = await _require_active_subscription(db, current_user)
    pkg = await get_package(db, sub.package_id)
    if pkg is None:
        raise HTTPException(status_code=500, detail="Package missing for subscription.")
    if not pkg.public_status_page_allowed:
        raise HTTPException(
            status_code=403,
            detail="Your package does not include public status pages.",
        )
    await refresh_usage_counters(db, sub)
    try:
        assert_quota_ok(pkg, sub.status_pages_used, limit_field="max_status_pages")
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc))

    # Enforce that the requested monitor ids belong to this user.
    monitor_ids = payload.monitor_ids or []
    if monitor_ids:
        own_result = await db.execute(
            select(UptimeClientMonitor.ur_monitor_id).where(
                UptimeClientMonitor.user_id == current_user.id,
                UptimeClientMonitor.ur_monitor_id.in_(monitor_ids),
            )
        )
        owned_ids = {str(v) for v in own_result.scalars().all()}
        missing = [mid for mid in monitor_ids if str(mid) not in owned_ids]
        if missing:
            raise HTTPException(
                status_code=400,
                detail=f"Monitor ids not owned by you: {missing}",
            )

    client = await get_client(user_email=current_user.email)
    try:
        ur_resp = await client.create_status_page(
            {
                "friendly_name": payload.friendly_name,
                "custom_url": payload.custom_url,
                "monitors": monitor_ids,
            }
        )
    except UptimeRobotError as exc:
        raise HTTPException(status_code=502, detail=f"UptimeRobot: {exc.message}")
    ur_id = None
    standard_url = None
    if isinstance(ur_resp, dict):
        src = ur_resp.get("data") or ur_resp
        if isinstance(src, dict):
            ur_id = src.get("id") or src.get("page_id")
            standard_url = src.get("standard_url") or src.get("url")
    if not ur_id:
        raise HTTPException(
            status_code=502, detail="UptimeRobot did not return a status page id"
        )
    row = UptimeStatusPage(
        subscription_id=sub.id,
        user_id=current_user.id,
        ur_page_id=str(ur_id),
        friendly_name=payload.friendly_name,
        standard_url=standard_url,
        custom_url=payload.custom_url,
        monitor_ids_json=json.dumps(monitor_ids) if monitor_ids else None,
    )
    db.add(row)
    await refresh_usage_counters(db, sub)
    await db.commit()
    await db.refresh(row)
    out = UptimeStatusPageOut.model_validate(row)
    out.monitor_ids = monitor_ids
    return out


@router.delete("/status-pages/{page_id}", status_code=status.HTTP_204_NO_CONTENT)
async def client_delete_page(
    page_id: int,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> None:
    row = await db.get(UptimeStatusPage, page_id)
    if row is None or row.user_id != current_user.id:
        return
    client = await get_client(user_email=current_user.email)
    try:
        await client.delete_status_page(row.ur_page_id)
    except UptimeRobotError as exc:
        logger.warning(
            "Upstream delete of status page %s failed (continuing): %s",
            row.ur_page_id,
            exc.message,
        )
    sub = await db.get(UptimeClientSubscription, row.subscription_id)
    await db.delete(row)
    if sub is not None:
        await refresh_usage_counters(db, sub)
    await db.commit()