# grantflow/api/routes/tenants.py
"""
Tenant provisioning and usage metering endpoints.

POST /tenants                 — register or update a tenant with quotas
GET  /tenants                 — list all registered tenants
GET  /tenants/{tenant_id}     — tenant config + today's usage metrics
GET  /tenants/{tenant_id}/usage — usage metrics only (agent-friendly)

Quotas appear in usage responses and can hard-block requests when
GRANTFLOW_TENANT_QUOTA_HARD_BLOCK=true is set. GET /tenants/{tenant_id}/usage
is the billing integration point for external metering systems.
"""

from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Request
from pydantic import BaseModel, Field

from grantflow.api.errors import raise_agent_error
from grantflow.api.job_store_service import _list_ingest_events, _list_jobs
from grantflow.api.security import require_api_key_if_configured
from grantflow.api.tenant import _filter_jobs_by_tenant
from grantflow.api.tenant_store import get_tenant, list_tenants, register_tenant

tenants_router = APIRouter()


# ---------------------------------------------------------------------------
# Schemas
# ---------------------------------------------------------------------------


class RegisterTenantRequest(BaseModel):
    tenant_id: str = Field(..., description="Normalized tenant slug (e.g. 'tenant_alpha').")
    display_name: Optional[str] = Field(None, description="Human-readable label.")
    allowed_donors: Optional[List[str]] = Field(
        None,
        description="Allowed donor_id values for this tenant. Empty or omitted = all donors allowed.",
    )
    quota_jobs_per_day: int = Field(
        0,
        ge=0,
        description="Max generate calls per UTC day. 0 = unlimited.",
    )
    quota_ingest_per_day: int = Field(
        0,
        ge=0,
        description="Max ingest calls per UTC day. 0 = unlimited.",
    )


# ---------------------------------------------------------------------------
# Usage computation
# ---------------------------------------------------------------------------


def _today_utc_prefix() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%d")


def _job_created_ts(job: Dict[str, Any]) -> str:
    """Return ISO timestamp of when the job was first recorded."""
    events = job.get("job_events")
    if isinstance(events, list) and events:
        first = events[0]
        if isinstance(first, dict):
            return str(first.get("ts") or "")
    return ""


def _compute_usage(tenant_id: str, *, tenant_config: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
    """Compute today's (UTC) usage metrics for a tenant from live stores."""
    today = _today_utc_prefix()

    all_jobs = _list_jobs()
    tenant_jobs = _filter_jobs_by_tenant(all_jobs, tenant_id)
    jobs_today = {jid: j for jid, j in tenant_jobs.items() if _job_created_ts(j).startswith(today)}

    jobs_total = len(jobs_today)
    jobs_done = sum(1 for j in jobs_today.values() if j.get("status") == "done")
    jobs_error = sum(1 for j in jobs_today.values() if j.get("status") == "error")
    jobs_pending = sum(1 for j in jobs_today.values() if j.get("status") not in {"done", "error", "canceled"})

    ingest_events = _list_ingest_events(tenant_id=tenant_id, limit=2000)
    ingest_today = [e for e in ingest_events if str(e.get("ts") or "").startswith(today)]
    ingest_total = len(ingest_today)
    chunks_ingested_total = sum(int((e.get("result") or {}).get("chunks_ingested") or 0) for e in ingest_today)

    quota_jobs = int((tenant_config or {}).get("quota_jobs_per_day") or 0)
    quota_ingest = int((tenant_config or {}).get("quota_ingest_per_day") or 0)

    quota_jobs_remaining = max(0, quota_jobs - jobs_total) if quota_jobs > 0 else None
    quota_ingest_remaining = max(0, quota_ingest - ingest_total) if quota_ingest > 0 else None

    usage: Dict[str, Any] = {
        "period": "today",
        "period_date_utc": today,
        "jobs_total": jobs_total,
        "jobs_done": jobs_done,
        "jobs_error": jobs_error,
        "jobs_pending": jobs_pending,
        "ingest_total": ingest_total,
        "chunks_ingested_total": chunks_ingested_total,
        "quota_jobs_per_day": quota_jobs,
        "quota_ingest_per_day": quota_ingest,
    }
    if quota_jobs_remaining is not None:
        usage["quota_jobs_remaining"] = quota_jobs_remaining
    if quota_ingest_remaining is not None:
        usage["quota_ingest_remaining"] = quota_ingest_remaining

    return usage


# ---------------------------------------------------------------------------
# Endpoints
# ---------------------------------------------------------------------------


@tenants_router.post("/tenants", tags=["tenants"])
def provision_tenant(req: RegisterTenantRequest, request: Request):
    """Register or update a tenant with display name, allowed donors, and daily quotas.

    Idempotent: calling again with the same tenant_id updates the record.
    Quota values of 0 mean unlimited.
    """
    require_api_key_if_configured(request)

    tenant_id = str(req.tenant_id or "").strip().lower().replace(" ", "_")
    if not tenant_id:
        raise_agent_error(
            code="missing_tenant_id",
            message="tenant_id must not be empty.",
            retryable=False,
            next_action="Provide a non-empty tenant_id slug.",
            status_code=400,
        )

    record = register_tenant(
        tenant_id,
        display_name=req.display_name,
        allowed_donors=req.allowed_donors,
        quota_jobs_per_day=req.quota_jobs_per_day,
        quota_ingest_per_day=req.quota_ingest_per_day,
    )
    return {"status": "registered", "tenant": record}


@tenants_router.get("/tenants", tags=["tenants"])
def get_tenants_list(request: Request):
    """List all registered tenants (config only, no live usage metrics)."""
    require_api_key_if_configured(request)
    tenants = list_tenants()
    return {"count": len(tenants), "tenants": tenants}


@tenants_router.get("/tenants/{tenant_id}", tags=["tenants"])
def get_tenant_detail(tenant_id: str, request: Request):
    """Return tenant config and today's live usage metrics."""
    require_api_key_if_configured(request)

    normalized = str(tenant_id or "").strip().lower()
    if not normalized:
        raise_agent_error(
            code="missing_tenant_id",
            message="tenant_id path segment must not be empty.",
            retryable=False,
            next_action="Provide a non-empty tenant_id in the URL path.",
            status_code=400,
        )

    config = get_tenant(normalized)
    if config is None:
        raise_agent_error(
            code="tenant_not_found",
            message=f"Tenant '{normalized}' is not registered.",
            retryable=False,
            next_action="Register the tenant first via POST /tenants.",
            status_code=404,
        )

    usage = _compute_usage(normalized, tenant_config=config)
    return {"tenant": config, "usage": usage}


@tenants_router.get("/tenants/{tenant_id}/usage", tags=["tenants"])
def get_tenant_usage(tenant_id: str, request: Request):
    """Return today's usage metrics for a tenant (agent-friendly, no config)."""
    require_api_key_if_configured(request)

    normalized = str(tenant_id or "").strip().lower()
    if not normalized:
        raise_agent_error(
            code="missing_tenant_id",
            message="tenant_id path segment must not be empty.",
            retryable=False,
            next_action="Provide a non-empty tenant_id in the URL path.",
            status_code=400,
        )

    config = get_tenant(normalized)
    usage = _compute_usage(normalized, tenant_config=config)
    usage["tenant_id"] = normalized
    usage["registered"] = config is not None
    return usage
