from __future__ import annotations

import logging
from datetime import datetime
from typing import Any, Dict, Literal, Optional

from fastapi import APIRouter, Body, Depends, HTTPException, Query
from pydantic import BaseModel, Field
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from ...auth_security import AdminContext, require_admin
from ...db import get_session
from ...models import Account, AccountEntitlements, GremlinGatewayLicense, LicenseGrant, LicenseGrantProjection, UsageQuotaModel, User
from ...services.admin_audit import log_admin_action
from ...services.admin_surface_signals import (
    DEFAULT_PRODUCT_SURFACE,
    classify_account_identity,
    normalize_product_surface,
)
from ...services.gremlin_install_tokens import gremlin_cli_plan_for_state, issue_local_install_token
from ...services.gremlin_license_gateway import (
    canonical_gremlin_plan_to_runtime_tier,
    get_canonical_gremlin_runtime_tier,
    get_gremlin_gateway_runtime_tier,
    serialize_gremlin_gateway_license,
)
from ...services.license_manager import (
    GREMLIN_GATEWAY_KEY,
    detect_license_grant_drift,
    detect_trial_license_drift,
    projection_gateway_key,
    serialize_license_projection,
    serialize_license_grant,
    sync_trial_license_from_inventory,
)
from ...services.usage_gateway import effective_gremlin_tier
from . import admin_trials as trial_api

log = logging.getLogger(__name__)

router = APIRouter(prefix="/admin/license-manager", tags=["Admin"])


class LicenseManagerReconcilePayload(BaseModel):
    product_surface: Optional[Literal["gremlin", "sheets", "deal_desk"]] = None


class GremlinInstallTokenPayload(BaseModel):
    expires_in_hours: int = Field(default=24 * 30, ge=1, le=24 * 365)
    single_use: bool = False


def _resolve_reconcile_surface(
    *,
    trial_item: Dict[str, Any],
    requested_surface: Optional[str],
) -> str:
    active_surfaces = list(trial_item.get("active_surfaces") or [])
    if requested_surface:
        normalized_requested = normalize_product_surface(requested_surface)
        if active_surfaces and normalized_requested not in active_surfaces:
            raise HTTPException(
                400,
                f"Account is not currently attributed to the {normalized_requested} surface",
            )
        return normalized_requested

    primary_surface = normalize_product_surface(
        trial_item.get("primary_surface"),
        default=DEFAULT_PRODUCT_SURFACE,
    )
    if primary_surface in active_surfaces:
        return primary_surface
    if len(active_surfaces) == 1:
        return active_surfaces[0]
    return primary_surface


def _iso_or_none(value: Optional[datetime]) -> Optional[str]:
    if value is None:
        return None
    if value.tzinfo is None:
        return value.isoformat()
    return value.isoformat()


def _serialize_usage_quota(quota: Optional[UsageQuotaModel]) -> Optional[dict[str, Any]]:
    if quota is None:
        return None
    return {
        "tier": quota.tier,
        "subscription_status": quota.subscription_status,
        "monthly_rows_used": quota.monthly_rows_used,
        "monthly_match_entitlement": quota.monthly_match_entitlement,
        "enrichment_credits_used": quota.enrichment_credits_used,
        "monthly_enrichment_entitlement": quota.monthly_enrichment_entitlement,
        "tier_synced_at": _iso_or_none(quota.tier_synced_at),
        "tier_sync_source": quota.tier_sync_source,
    }


def _serialize_account_entitlements(
    entitlements: Optional[AccountEntitlements],
) -> Optional[dict[str, Any]]:
    if entitlements is None:
        return None
    return {
        "tier": entitlements.tier,
        "addons": list(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,
        "expires_at": _iso_or_none(entitlements.expires_at),
        "synced_at": _iso_or_none(entitlements.synced_at),
    }


async def _load_account_identity(
    db: AsyncSession,
    *,
    account_id: str,
) -> tuple[Account, list[User], dict[str, Any], Optional[str], Optional[str]]:
    account = await db.get(Account, account_id)
    if account is None:
        raise HTTPException(404, "Account not found")

    user_result = await db.execute(
        select(User)
        .where(User.account_id == account_id)
        .order_by(User.created_at.asc(), User.id.asc())
    )
    users = list(user_result.scalars().all())
    primary_user = users[0] if users else None
    primary_email = (primary_user.email if primary_user else None) or None
    primary_clerk_user_id = (primary_user.clerk_user_id if primary_user else None) or None
    identity = classify_account_identity(
        account_id=account.id,
        account_name=account.name,
        clerk_org_id=account.clerk_org_id,
        primary_user_id=primary_user.id if primary_user else None,
        primary_clerk_user_id=primary_clerk_user_id,
        user_count=len(users),
    )
    return account, users, identity, primary_email, primary_clerk_user_id


async def _load_gremlin_grant_bundle(
    db: AsyncSession,
    *,
    account_id: str,
) -> tuple[Optional[LicenseGrant], Optional[LicenseGrantProjection]]:
    grant_result = await db.execute(
        select(LicenseGrant).where(
            LicenseGrant.account_id == account_id,
            LicenseGrant.product_surface == "gremlin",
        )
    )
    grant = grant_result.scalar_one_or_none()
    if grant is None:
        return None, None

    projection_result = await db.execute(
        select(LicenseGrantProjection).where(
            LicenseGrantProjection.grant_id == grant.id,
            LicenseGrantProjection.gateway_key == projection_gateway_key("gremlin"),
        )
    )
    return grant, projection_result.scalar_one_or_none()


@router.get("/grants")
async def list_license_grants(
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
    account_id: Optional[str] = Query(default=None),
    product_surface: Optional[str] = Query(default=None),
    primary_email: Optional[str] = Query(default=None),
    status: Optional[str] = Query(default=None),
    limit: int = Query(default=100, ge=1, le=500),
) -> Dict[str, Any]:
    try:
        stmt = select(LicenseGrant).order_by(LicenseGrant.updated_at.desc()).limit(limit)
        if account_id:
            stmt = stmt.where(LicenseGrant.account_id == account_id)
        if product_surface:
            stmt = stmt.where(
                LicenseGrant.product_surface == normalize_product_surface(product_surface)
            )
        if primary_email:
            stmt = stmt.where(LicenseGrant.primary_email == primary_email.strip().lower())
        if status:
            stmt = stmt.where(LicenseGrant.status == status.strip().lower())

        grants = list((await db.execute(stmt)).scalars().all())
        projections_by_grant: dict[str, list[LicenseGrantProjection]] = {}
        if grants:
            grant_ids = [grant.id for grant in grants]
            projection_rows = list(
                (
                    await db.execute(
                        select(LicenseGrantProjection).where(
                            LicenseGrantProjection.grant_id.in_(grant_ids)
                        )
                    )
                )
                .scalars()
                .all()
            )
            for projection in projection_rows:
                projections_by_grant.setdefault(projection.grant_id, []).append(projection)

        await log_admin_action(
            admin_user_id=admin_ctx.user_id,
            admin_email=admin_ctx.email,
            action="list_license_grants",
            db=db,
            details={
                "account_id": account_id,
                "product_surface": product_surface,
                "primary_email": primary_email,
                "status": status,
                "limit": limit,
            },
        )
        return {
            "ok": True,
            "grants": [
                serialize_license_grant(
                    grant,
                    projections=projections_by_grant.get(grant.id, []),
                )
                for grant in grants
            ],
            "total": len(grants),
        }
    except HTTPException:
        raise
    except Exception as exc:
        log.error("Failed to list canonical license grants: %s", exc, exc_info=True)
        raise HTTPException(500, "Failed to list canonical license grants")


@router.post("/accounts/{account_id}/reconcile-trial")
async def reconcile_trial_license_grant(
    account_id: str,
    payload: LicenseManagerReconcilePayload = Body(
        default=LicenseManagerReconcilePayload()
    ),
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    try:
        trial_item = await trial_api._load_trial_inventory_item(db, account_id=account_id)
        product_surface = _resolve_reconcile_surface(
            trial_item=trial_item,
            requested_surface=payload.product_surface,
        )
        drift_before = await detect_trial_license_drift(
            db,
            trial_item=trial_item,
            product_surface=product_surface,
            local_tier=trial_item.get("tier"),
            local_status=trial_item.get("status"),
            local_source=trial_item.get("sync_source"),
            trial_started_at=trial_item.get("trial_started_at"),
            trial_ends_at=trial_item.get("trial_ends_at"),
            action="reconcile",
        )
        grant, projection = await sync_trial_license_from_inventory(
            db,
            trial_item=trial_item,
            product_surface=product_surface,
            local_tier=trial_item.get("tier"),
            local_status=trial_item.get("status"),
            local_source=trial_item.get("sync_source"),
            trial_started_at=trial_item.get("trial_started_at"),
            trial_ends_at=trial_item.get("trial_ends_at"),
            action="reconcile",
        )
        drift_after = await detect_license_grant_drift(
            db,
            grant=grant,
            projection=projection,
        )

        await log_admin_action(
            admin_user_id=admin_ctx.user_id,
            admin_email=admin_ctx.email,
            action="reconcile_trial_license_grant",
            db=db,
            target_account_id=account_id,
            details={
                "product_surface": product_surface,
                "projection_status": projection.sync_status,
            },
        )

        return {
            "ok": True,
            "account_id": account_id,
            "product_surface": product_surface,
            "grant": serialize_license_grant(grant, projections=[projection]),
            "drift_before": drift_before,
            "drift_after": drift_after,
        }
    except HTTPException:
        raise
    except Exception as exc:
        log.error(
            "Failed to reconcile license grant for account %s: %s",
            account_id,
            exc,
            exc_info=True,
        )
        raise HTTPException(500, "Failed to reconcile license grant")


@router.get("/accounts/{account_id}/gremlin-debug")
async def get_gremlin_license_debug(
    account_id: str,
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    try:
        account, users, identity, primary_email, primary_clerk_user_id = await _load_account_identity(
            db,
            account_id=account_id,
        )
        grant, projection = await _load_gremlin_grant_bundle(db, account_id=account_id)
        gateway = await db.get(GremlinGatewayLicense, account_id)
        quota = await db.get(UsageQuotaModel, account_id)
        entitlements = await db.get(AccountEntitlements, account_id)

        drift = None
        if grant is not None:
            drift = await detect_license_grant_drift(db, grant=grant, projection=projection)

        canonical_runtime_tier = await get_canonical_gremlin_runtime_tier(
            db,
            account_id=account_id,
        )
        gateway_runtime_tier = await get_gremlin_gateway_runtime_tier(
            db,
            account_id=account_id,
        )
        effective_quota_tier = effective_gremlin_tier(
            quota.tier if quota else None,
            entitlements.gremlin_daily_limit if entitlements else None,
        )

        await log_admin_action(
            admin_user_id=admin_ctx.user_id,
            admin_email=admin_ctx.email,
            action="get_gremlin_license_debug",
            db=db,
            target_account_id=account_id,
            details={"gateway_key": GREMLIN_GATEWAY_KEY},
        )

        return {
            "ok": True,
            "data": {
                "account": {
                    "id": account.id,
                    "name": account.name,
                    "clerk_org_id": account.clerk_org_id,
                    "stripe_customer_id": account.stripe_customer_id,
                    "stripe_subscription_id": account.stripe_subscription_id,
                },
                "identity": {
                    **identity,
                    "primary_email": primary_email,
                },
                "users": [
                    {
                        "id": user.id,
                        "email": user.email,
                        "clerk_user_id": user.clerk_user_id,
                        "created_at": _iso_or_none(user.created_at),
                        "last_login": _iso_or_none(user.last_login),
                    }
                    for user in users
                ],
                "canonical_grant": serialize_license_grant(
                    grant,
                    projections=[projection] if projection else [],
                )
                if grant
                else None,
                "gremlin_projection": serialize_license_projection(projection)
                if projection
                else None,
                "gremlin_gateway": serialize_gremlin_gateway_license(gateway)
                if gateway
                else None,
                "usage_quota": _serialize_usage_quota(quota),
                "account_entitlements": _serialize_account_entitlements(entitlements),
                "runtime": {
                    "canonical_tier": canonical_runtime_tier,
                    "gateway_tier": gateway_runtime_tier,
                    "quota_tier": quota.tier if quota else None,
                    "effective_quota_tier": effective_quota_tier,
                    "subscription_status": quota.subscription_status if quota else None,
                    "gremlin_daily_limit": entitlements.gremlin_daily_limit if entitlements else None,
                    "gateway_key": GREMLIN_GATEWAY_KEY,
                },
                "drift": drift,
                "lookup_keys": {
                    "account_id": account_id,
                    "primary_email": primary_email,
                    "primary_clerk_user_id": primary_clerk_user_id,
                },
            },
        }
    except HTTPException:
        raise
    except Exception as exc:
        log.error(
            "Failed to load Gremlin license debug for %s: %s",
            account_id,
            exc,
            exc_info=True,
        )
        raise HTTPException(500, "Failed to load Gremlin license debug")


@router.post("/accounts/{account_id}/gremlin/install-token")
async def issue_gremlin_install_token(
    account_id: str,
    payload: GremlinInstallTokenPayload = Body(default=GremlinInstallTokenPayload()),
    admin_ctx: AdminContext = Depends(require_admin),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    try:
        await _load_account_identity(db, account_id=account_id)
        grant, _projection = await _load_gremlin_grant_bundle(db, account_id=account_id)
        if grant is None:
            raise HTTPException(404, "Canonical Gremlin grant not found")

        token_payload = issue_local_install_token(
            account_id=grant.account_id,
            clerk_user_id=grant.clerk_user_id,
            primary_email=grant.primary_email,
            product_surface="gremlin",
            expires_in_hours=payload.expires_in_hours,
            single_use=payload.single_use,
        )

        await log_admin_action(
            admin_user_id=admin_ctx.user_id,
            admin_email=admin_ctx.email,
            action="issue_gremlin_install_token",
            db=db,
            target_account_id=account_id,
            details={
                "expires_in_hours": payload.expires_in_hours,
                "single_use": payload.single_use,
                "plan_key": grant.plan_key,
            },
        )

        return {
            "ok": True,
            "data": {
                **token_payload,
                "account_id": grant.account_id,
                "clerk_user_id": grant.clerk_user_id,
                "primary_email": grant.primary_email,
                "product_surface": "gremlin",
                "plan_key": grant.plan_key,
                "plan": gremlin_cli_plan_for_state(grant.plan_key),
                "runtime_tier": canonical_gremlin_plan_to_runtime_tier(
                    grant.plan_key,
                    status=grant.status,
                ),
                "trial": grant.grant_type in {"trial", "beta"},
                "status": grant.status,
                "license_expires_at": _iso_or_none(grant.ends_at),
            },
        }
    except HTTPException:
        raise
    except Exception as exc:
        log.error(
            "Failed to issue Gremlin install token for %s: %s",
            account_id,
            exc,
            exc_info=True,
        )
        raise HTTPException(500, "Failed to issue Gremlin install token")
