# @File: backend/services/uptimerobot/usage.py
# @Desc: Compute and classify global Team-plan consumption. The numbers are
#        pulled both from our internal tables (authoritative for "us") and
#        the upstream account-details endpoint (authoritative for the Team
#        plan quotas). Whichever is higher drives the warning bucket.
from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import Any, Dict, Optional, Tuple

from models.uptimerobot import (
    UptimeAlertContact,
    UptimeClientMonitor,
    UptimeMaintenanceWindow,
    UptimeStatusPage,
    UptimeUsageSnapshot,
)
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession

from services.uptimerobot import UptimeRobotError, get_client

logger = logging.getLogger(__name__)


WARNING_LABELS: Dict[str, str] = {
    "ok": "Healthy",
    "warn_70": "Usage above 70%",
    "warn_85": "Usage above 85%",
    "critical_95": "Critical: above 95%",
    "upgrade_required": "Upgrade required",
}


def _compute_state(used: int, total: Optional[int]) -> str:
    if not total or total <= 0:
        return "ok"
    if used >= total:
        return "upgrade_required"
    pct = used / total
    if pct >= 0.95:
        return "critical_95"
    if pct >= 0.85:
        return "warn_85"
    if pct >= 0.70:
        return "warn_70"
    return "ok"


def _worst_state(states: list[str]) -> str:
    priority = ["ok", "warn_70", "warn_85", "critical_95", "upgrade_required"]
    worst = "ok"
    for s in states:
        if priority.index(s) > priority.index(worst):
            worst = s
    return worst


async def _count_internal(session: AsyncSession) -> Dict[str, int]:
    monitors = await session.scalar(
        select(func.count(UptimeClientMonitor.id)).where(
            UptimeClientMonitor.archived.is_(False)
        )
    )
    contacts = await session.scalar(select(func.count(UptimeAlertContact.id)))
    windows = await session.scalar(
        select(func.count(UptimeMaintenanceWindow.id))
    )
    pages = await session.scalar(select(func.count(UptimeStatusPage.id)))
    return {
        "monitors": int(monitors or 0),
        "contacts": int(contacts or 0),
        "windows": int(windows or 0),
        "pages": int(pages or 0),
    }


def _extract_totals(account_payload: Any) -> Dict[str, Optional[int]]:
    """Pull plan quotas out of an UR /account-details response shape.

    We defensively probe multiple shapes because UR v3 has evolved the
    envelope a few times across preview releases.
    """
    if not isinstance(account_payload, dict):
        return {"monitors": None, "contacts": None, "windows": None, "pages": None, "plan": None}
    source = account_payload
    if isinstance(account_payload.get("data"), dict):
        source = account_payload["data"]
    elif isinstance(account_payload.get("account"), dict):
        source = account_payload["account"]

    def _safe_int(v: Any) -> Optional[int]:
        try:
            if v is None:
                return None
            if isinstance(v, bool):
                return None
            return int(v)
        except (TypeError, ValueError):
            return None

    plan = source.get("plan") or source.get("plan_name") or source.get("subscription")
    if isinstance(plan, dict):
        plan_name = plan.get("name") or plan.get("title")
    else:
        plan_name = plan if isinstance(plan, str) else None

    # UR v3 uses *_limit / *_used names on modern envelopes.
    return {
        "monitors": _safe_int(
            source.get("monitor_limit")
            or source.get("monitors_limit")
            or (plan.get("monitor_limit") if isinstance(plan, dict) else None)
        ),
        "contacts": _safe_int(
            source.get("alert_contact_limit")
            or source.get("alert_contacts_limit")
        ),
        "windows": _safe_int(
            source.get("mwindow_limit")
            or source.get("maintenance_window_limit")
        ),
        "pages": _safe_int(
            source.get("public_status_page_limit")
            or source.get("status_page_limit")
        ),
        "plan": plan_name,
    }


async def compute_team_usage(
    session: AsyncSession,
) -> Tuple[Dict[str, Any], Optional[str]]:
    """Return (usage_dict, error_message_or_none)."""
    internal = await _count_internal(session)

    totals: Dict[str, Optional[int]] = {
        "monitors": None,
        "contacts": None,
        "windows": None,
        "pages": None,
        "plan": None,
    }
    upstream_error: Optional[str] = None
    try:
        client = await get_client()
        account = await client.account_details()
        totals = _extract_totals(account)
    except UptimeRobotError as exc:
        upstream_error = exc.message
        logger.warning("compute_team_usage: failed to fetch account-details: %s", exc.message)
    except Exception as exc:  # noqa: BLE001
        upstream_error = str(exc)
        logger.exception("compute_team_usage: unexpected failure")

    def _meter(used: int, total: Optional[int]) -> Dict[str, Any]:
        pct: Optional[int] = None
        if total and total > 0:
            pct = int(round((used / total) * 100))
        return {
            "used": used,
            "total": total,
            "percent": pct,
            "unlimited": False,
        }

    meters = {
        "monitors": _meter(internal["monitors"], totals["monitors"]),
        "alert_contacts": _meter(internal["contacts"], totals["contacts"]),
        "maintenance_windows": _meter(internal["windows"], totals["windows"]),
        "status_pages": _meter(internal["pages"], totals["pages"]),
    }
    states = [
        _compute_state(internal["monitors"], totals["monitors"]),
        _compute_state(internal["contacts"], totals["contacts"]),
        _compute_state(internal["windows"], totals["windows"]),
        _compute_state(internal["pages"], totals["pages"]),
    ]
    overall = _worst_state(states)
    result = {
        "monitors": meters["monitors"],
        "alert_contacts": meters["alert_contacts"],
        "status_pages": meters["status_pages"],
        "maintenance_windows": meters["maintenance_windows"],
        "plan_name": totals.get("plan"),
        "warning_state": overall,
        "warning_label": WARNING_LABELS.get(overall, "Healthy"),
        "snapshot_at": datetime.now(tz=timezone.utc),
    }
    return result, upstream_error


async def persist_usage_snapshot(
    session: AsyncSession, usage: Dict[str, Any]
) -> UptimeUsageSnapshot:
    snap = UptimeUsageSnapshot(
        monitors_used=int(usage["monitors"]["used"]),
        monitors_total=usage["monitors"]["total"],
        alert_contacts_used=int(usage["alert_contacts"]["used"]),
        alert_contacts_total=usage["alert_contacts"]["total"],
        status_pages_used=int(usage["status_pages"]["used"]),
        status_pages_total=usage["status_pages"]["total"],
        maintenance_windows_used=int(usage["maintenance_windows"]["used"]),
        maintenance_windows_total=usage["maintenance_windows"]["total"],
        plan_name=usage.get("plan_name"),
        warning_state=usage.get("warning_state", "ok"),
    )
    session.add(snap)
    await session.commit()
    await session.refresh(snap)
    return snap


async def latest_snapshot(session: AsyncSession) -> Optional[UptimeUsageSnapshot]:
    result = await session.execute(
        select(UptimeUsageSnapshot).order_by(UptimeUsageSnapshot.created_at.desc()).limit(1)
    )
    return result.scalars().first()