"""
Admin Usage API
Read-only usage rule matrix + per-account usage drill-down.
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from functools import lru_cache
from typing import Any, Dict, List, Optional

import click
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from g_gremlin import command_metadata as gremlin_command_metadata
from g_gremlin.cli import cli as gremlin_cli
from g_gremlin.credentials.entitlements import get_cli_entitlements_config

from ...auth_security import AdminContext, require_admin
from ...config.tier_limits import TIER_CONFIG as ENRICHMENT_CONFIG, get_tier_limits
from ...config.product_capabilities import CAPABILITIES_CONFIG
from ...db import get_session
from ...models import Account, AccountEntitlements, UsageLedger, UsageQuotaModel
from ...services.admin_audit import log_admin_action
from ...services.usage_gateway import UsageGateway

log = logging.getLogger(__name__)

router = APIRouter(prefix="/admin/usage", tags=["Admin Usage"])


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def _serialize_entitlements(entitlements: Optional[AccountEntitlements]) -> Dict[str, Any]:
    if not entitlements:
        return {}
    return {
        "tier": entitlements.tier,
        "addons": entitlements.addons or [],
        "monthly_row_limit": entitlements.monthly_row_limit,
        "gremlin_daily_limit": entitlements.gremlin_daily_limit,
        "ai_report_row_cap": entitlements.ai_report_row_cap,
        "byo_enabled": entitlements.byo_enabled,
        "byo_daily_limit": entitlements.byo_daily_limit,
        "source": entitlements.source,
        "synced_at": entitlements.synced_at.isoformat() if entitlements.synced_at else None,
        "expires_at": entitlements.expires_at.isoformat() if entitlements.expires_at else None,
    }


def _serialize_legacy_quota(quota: Optional[UsageQuotaModel]) -> Dict[str, Any]:
    if not quota:
        return {}
    return {
        "tier": quota.tier,
        "monthly_rows_used": quota.monthly_rows_used,
        "monthly_match_entitlement": quota.monthly_match_entitlement,
        "list_builder_daily_exports": quota.list_builder_daily_exports,
        "list_builder_monthly_exports": quota.list_builder_monthly_exports,
        "reset_at": quota.reset_at.isoformat() if quota.reset_at else None,
        "tier_synced_at": quota.tier_synced_at.isoformat() if quota.tier_synced_at else None,
        "tier_sync_source": quota.tier_sync_source,
    }


def _serialize_ledger_entry(entry: UsageLedger) -> Dict[str, Any]:
    return {
        "id": str(entry.id),
        "resource": entry.resource,
        "amount": entry.amount,
        "balance_after": entry.balance_after,
        "source": entry.source,
        "idempotency_key": entry.idempotency_key,
        "created_at": entry.created_at.isoformat() if entry.created_at else None,
    }


def _iter_click_commands(
    group: click.Group,
    path_parts: list[str] | None = None,
    parent_ctx: click.Context | None = None,
) -> List[tuple[str, click.Command]]:
    current_path = path_parts or []
    ctx = click.Context(group, parent=parent_ctx)
    resolved: List[tuple[str, click.Command]] = []

    for name in group.list_commands(ctx):
        if name in gremlin_command_metadata.HIDDEN_COMMANDS:
            continue
        cmd = group.get_command(ctx, name)
        if cmd is None:
            continue
        cmd_path = current_path + [name]
        cmd_id = gremlin_command_metadata.build_command_id(cmd_path)
        resolved.append((cmd_id, cmd))
        if isinstance(cmd, click.Group):
            resolved.extend(_iter_click_commands(cmd, cmd_path, ctx))
    return resolved


@lru_cache(maxsize=1)
def _build_cli_command_lookup() -> Dict[str, click.Command]:
    if not isinstance(gremlin_cli, click.Group):
        return {}
    pairs = _iter_click_commands(gremlin_cli)
    return {cmd_id: cmd for cmd_id, cmd in pairs}


@router.get("/rules")
async def get_usage_rules(
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    """Return the current tier matrix and add-on limits from config."""
    if not ENRICHMENT_CONFIG:
        raise HTTPException(
            status_code=500, detail="Enrichment pricing config is not available."
        )

    tiers: Dict[str, Any] = {}
    for name, tier in ENRICHMENT_CONFIG.tiers.items():
        if name.startswith("_"):
            continue
        tiers[name] = {
            "monthly_rows": tier.monthly_rows,
            "grace_percentage": tier.grace_percentage,
            "gremlin_enabled": tier.gremlin_enabled,
            "gremlin_messages_per_day": tier.gremlin_messages_per_day,
            "ai_report_row_cap": tier.ai_report_row_cap,
            "list_builder_daily_rows": tier.list_builder_daily_rows,
            "list_builder_monthly_rows": tier.list_builder_monthly_rows,
            "list_builder_preview_rows": tier.list_builder_preview_rows,
            "list_builder_max_columns": tier.list_builder_max_columns,
            "max_schedules": tier.max_schedules,
            "allowed_frequencies": list(tier.allowed_frequencies or []),
            "concurrent_jobs": tier.concurrent_jobs,
            "candidate_cap": tier.candidate_cap,
            "require_b2c": tier.require_b2c,
            "allow_advanced_algos": tier.allow_advanced_algos,
            "soql_runs_per_month": tier.soql_runs_per_month,
            "salesforce_orgs": tier.salesforce_orgs,
            "users": tier.users,
            "byo": {
                "enabled": tier.byo_enrichment_enabled,
                "bundled": tier.byo_bundled,
                "providers": list(tier.byo_providers or []),
                "max_rows_per_job": tier.byo_max_rows_per_job,
                "concurrent_enrichment_jobs": tier.byo_concurrent_enrichment_jobs,
                "daily_enrichment_rows": tier.byo_daily_enrichment_rows,
                "monthly_enrichment_rows": tier.byo_monthly_enrichment_rows,
                "scheduled_enrichment": tier.byo_scheduled_enrichment,
                "scheduled_frequencies": list(tier.byo_scheduled_frequencies or [])
                if tier.byo_scheduled_frequencies
                else [],
                "priority_queue": tier.byo_priority_queue,
            },
        }

    if "scale" in tiers and "business" not in tiers:
        tiers["business"] = {"alias_of": "scale", **tiers["scale"]}

    addons: Dict[str, Any] = {}
    for addon_key, addon in (ENRICHMENT_CONFIG.addons or {}).items():
        limits = {
            tier_name: limit.dict()
            for tier_name, limit in (addon.limits_by_tier or {}).items()
            if limit is not None
        }
        addons[addon_key] = {
            "name": addon.name,
            "description": addon.description,
            "available_tiers": list(addon.available_tiers or []),
            "providers": list(addon.providers or []),
            "limits_by_tier": limits,
        }

    await log_admin_action(
        admin_user_id=admin_ctx.user_id,
        admin_email=admin_ctx.email,
        action="view_usage_rules",
        db=db,
    )

    return {
        "tiers": tiers,
        "tier_limits": get_tier_limits(),
        "addons": addons,
        "updated_at": _now_iso(),
    }


@router.get("/product-capabilities")
async def get_product_capabilities(
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    """Return the product capabilities matrix from config."""
    if not CAPABILITIES_CONFIG:
        raise HTTPException(
            status_code=500,
            detail="Product capabilities config is not available. Check config/product_capabilities.yaml",
        )

    await log_admin_action(
        admin_user_id=admin_ctx.user_id,
        admin_email=admin_ctx.email,
        action="view_product_capabilities",
        db=db,
    )

    response = CAPABILITIES_CONFIG.to_api_response()
    response["updated_at"] = _now_iso()
    return response


@router.get("/cli-entitlements")
async def get_cli_entitlements(
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    """Return merged CLI entitlement matrix and command mapping."""
    try:
        config = get_cli_entitlements_config()
    except Exception as exc:
        raise HTTPException(
            status_code=500,
            detail=f"CLI entitlement config unavailable: {exc}",
        ) from exc

    # Enrich command safety with inferred defaults for required capability/meters/fallback.
    command_lookup = _build_cli_command_lookup()
    merged_command_safety: Dict[str, Dict[str, Any]] = {}

    # Prefer only discovered Click commands to avoid placeholder-driven fallback inference.
    # If command discovery fails entirely, fall back to registry keys so the endpoint still works.
    if command_lookup:
        all_command_ids = sorted(command_lookup.keys())
    else:
        all_command_ids = sorted(gremlin_command_metadata.COMMAND_SAFETY.keys())

    def _synthetic_command_from_registry(command_id: str) -> click.Command:
        safety = gremlin_command_metadata.COMMAND_SAFETY.get(command_id) or {}
        params: list[click.Option] = []
        if safety.get("fallback_mode") == "dry_run":
            params.append(click.Option(["--dry-run"], is_flag=True))
        return click.Command(name=command_id.split(":")[-1], params=params)

    for command_id in all_command_ids:
        root = command_id.split(":", 1)[0]
        group_name = gremlin_command_metadata.find_group_for_command(root)
        cmd = command_lookup.get(command_id) or _synthetic_command_from_registry(command_id)
        merged_command_safety[command_id] = gremlin_command_metadata.resolve_safety_for_schema(
            cmd,
            command_id,
            group_name,
            isinstance(cmd, click.Group),
        )

    await log_admin_action(
        admin_user_id=admin_ctx.user_id,
        admin_email=admin_ctx.email,
        action="view_cli_entitlements",
        db=db,
    )

    response = config.to_api_response(merged_command_safety)
    response["updated_at"] = _now_iso()
    return response


@router.get("/accounts/{account_id}")
async def get_account_usage(
    account_id: str,
    include_ledger: bool = Query(False, description="Include recent ledger entries"),
    ledger_limit: int = Query(50, ge=1, le=200),
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    """Return UsageGateway capabilities + entitlements for a specific account."""
    account_exists = await db.scalar(
        select(Account.id).where(Account.id == account_id)
    )
    if not account_exists:
        raise HTTPException(status_code=404, detail="Account not found")

    gateway = UsageGateway(db)
    capabilities = await gateway.get_capabilities(account_id)

    entitlements = await db.get(AccountEntitlements, account_id)
    quota = await db.execute(
        select(UsageQuotaModel).where(UsageQuotaModel.account_id == account_id)
    )
    legacy_quota = quota.scalar_one_or_none()

    ledger_entries: List[Dict[str, Any]] = []
    if include_ledger:
        ledger_rows = await db.execute(
            select(UsageLedger)
            .where(UsageLedger.account_id == account_id)
            .order_by(UsageLedger.created_at.desc())
            .limit(ledger_limit)
        )
        ledger_entries = [
            _serialize_ledger_entry(entry)
            for entry in ledger_rows.scalars().all()
        ]

    await log_admin_action(
        admin_user_id=admin_ctx.user_id,
        admin_email=admin_ctx.email,
        action="view_account_usage",
        db=db,
        target_account_id=account_id,
        details={"include_ledger": include_ledger, "ledger_limit": ledger_limit},
    )

    return {
        "account_id": account_id,
        "capabilities": capabilities,
        "entitlements": _serialize_entitlements(entitlements),
        "legacy_quota": _serialize_legacy_quota(legacy_quota),
        "ledger": ledger_entries,
        "updated_at": _now_iso(),
    }
