"""complyform-overage-enforcement — suspend excess engagements Cloud Function.

Trigger: Daily Cloud Scheduler (05:00 UTC — after data cleanup at 04:00).

Suspends excess projects for Agency/Enterprise customers with unpaid
overage >30 days.  Base 5 engagements remain fully operational.
Enterprise Unlimited is exempt.

Contract: per-customer error isolation.
"""

from __future__ import annotations

import logging
from datetime import UTC, datetime, timedelta
from typing import Any

from backend.ops._shared import cloud_function_wrapper, log_action
from backend.shared.firestore import get_db
from backend.shared.tier_config import TIER_CONFIG

_log = logging.getLogger(__name__)

_OVERAGE_GRACE_DAYS = 30

# Project limits per tier — derived from TIER_CONFIG.
PROJECT_LIMITS: dict[str, int | None] = {
    tier: (
        cfg.get("project_limit")  # type: ignore[misc]
        if isinstance(cfg.get("project_limit"), int)
        else None
    )
    for tier, cfg in TIER_CONFIG.items()
}

_OVERAGE_TIERS: frozenset[str] = frozenset(["agency", "enterprise"])


@cloud_function_wrapper("complyform-overage-enforcement")
async def overage_enforcement(request: Any) -> dict[str, Any]:
    """Suspend excess engagements for Agency/Enterprise with unpaid overage >30 days."""
    db = get_db()
    now = datetime.now(UTC)
    cutoff = (now - timedelta(days=_OVERAGE_GRACE_DAYS)).isoformat()
    stats: dict[str, int] = {"customers_enforced": 0, "projects_suspended": 0}

    # Query customers with overdue overage
    query = db.collection("customers").where("overage_past_due_since", "<", cutoff)
    async for doc in query.stream():
        customer_id = doc.id
        try:
            data = doc.to_dict() or {}
            # Skip cancelled/purged customers
            if data.get("status") in ("cancelled", "purged"):
                continue
            tier = data.get("tier", "")
            if tier not in _OVERAGE_TIERS:
                continue

            limit = PROJECT_LIMITS.get(tier)
            if limit is None:
                continue

            suspended = await _enforce_customer(db, customer_id, limit)
            if suspended > 0:
                # Set customer status to past_due
                await (
                    db.collection("customers")
                    .document(customer_id)
                    .update({"status": "past_due"})
                )
                stats["customers_enforced"] += 1
                stats["projects_suspended"] += suspended
                log_action(
                    customer_id,
                    "overage_enforcement",
                    record_count=suspended,
                )
        except Exception as exc:
            log_action(customer_id, "overage_enforcement", error=str(exc))
            _log.exception("Enforcement error for %s", customer_id)

    return stats


async def _enforce_customer(db: Any, customer_id: str, limit: int) -> int:
    """Suspend projects beyond the limit. Returns count of suspended projects.

    Sorts active projects by last_scan ASC (least recently scanned first)
    and suspends those beyond the project_limit.
    """
    projects_ref = (
        db.collection("customers")
        .document(customer_id)
        .collection("projects")
        .where("status", "==", "active")
    )
    projects: list[tuple[Any, dict[str, Any]]] = []
    async for proj_doc in projects_ref.stream():
        projects.append((proj_doc.reference, proj_doc.to_dict() or {}))

    if len(projects) <= limit:
        return 0

    # Sort by last_scan ascending — least recently scanned first
    projects.sort(key=lambda p: p[1].get("last_scan", ""))

    # Keep the most recently scanned (last N), suspend the rest
    to_suspend = projects[: len(projects) - limit]
    count = 0
    for ref, _ in to_suspend:
        await ref.update({"status": "suspended"})
        count += 1

    return count


entry_point = overage_enforcement
