"""Instance management routes."""

import json
import time
from collections.abc import Awaitable, Callable
from datetime import UTC, datetime, timedelta
from typing import Annotated, Any

from backend.config import logger
from backend.deps import ensure_supabase, limiter, verify_user
from backend.entitlements import assert_instance_entitlement
from backend.k8s import check_deployment_exists, instance_deployment_ref, run_kubectl
from backend.models import ActionResult, InstancesResponse, ProvisionResponse
from backend.services import instances_data, provisioner_service
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request

router = APIRouter()

# Track instances being synced to prevent duplicates
_syncing_instances: set[str] = set()
InstanceAction = Callable[[int], Awaitable[dict[str, Any]]]


async def _background_sync_instance_status(instance_id: str) -> None:
    """Background task to sync a single instance's Kubernetes status."""
    if instance_id in _syncing_instances:
        return  # Already syncing

    _syncing_instances.add(instance_id)
    start = time.perf_counter()

    try:
        sb = ensure_supabase()

        # Get current status from DB
        instance_row = instances_data.get_instance(sb, instance_id, columns="status")
        if instance_row is None:
            logger.warning("Background sync: instance %s not found in database, skipping sync", instance_id)
            return
        current_status = instance_row.get("status")

        # Check if deployment exists
        k8s_start = time.perf_counter()
        exists = await check_deployment_exists(instance_id)

        if exists:
            # Inspect deployment status to understand readiness
            code, out, err = await run_kubectl(
                ["get", instance_deployment_ref(instance_id), "-o=json"], namespace="mindroom-instances"
            )
            if code == 0 and out:
                try:
                    deployment = json.loads(out)
                except json.JSONDecodeError:
                    logger.warning("Failed to parse deployment JSON for instance %s: %s", instance_id, out[:120])
                    deployment = out.strip()

                if isinstance(deployment, (int, float)) or (isinstance(deployment, str) and deployment.isdigit()):
                    replicas = int(deployment)
                    actual_status = "stopped" if replicas == 0 else "running"
                elif not deployment:
                    actual_status = "provisioning"
                else:
                    spec = deployment.get("spec", {}) or {}
                    status = deployment.get("status", {}) or {}
                    desired_replicas = int(spec.get("replicas") or 0)
                    ready_replicas = int(status.get("readyReplicas") or 0)
                    available_replicas = int(status.get("availableReplicas") or 0)
                    updated_replicas = int(status.get("updatedReplicas") or 0)

                    if desired_replicas == 0:
                        actual_status = "stopped"
                    elif min(ready_replicas, available_replicas, updated_replicas) >= desired_replicas:
                        actual_status = "running"
                    else:
                        actual_status = "provisioning"
            else:
                logger.warning(
                    "kubectl get deployment failed for instance %s: %s",
                    instance_id,
                    err.strip() if err else out.strip(),
                )
                # Fall back to assuming deployment exists but is still provisioning
                actual_status = "provisioning"
        elif current_status in ["deprovisioned", "provisioning"]:
            # Keep current status if it makes sense
            actual_status = current_status
        else:
            actual_status = "error"

        # Update database
        now = datetime.now(UTC).isoformat()
        instances_data.update_instance(
            sb, instance_id, {"status": actual_status, "kubernetes_synced_at": now, "updated_at": now}
        )

        total_time = (time.perf_counter() - start) * 1000
        k8s_time = (time.perf_counter() - k8s_start) * 1000
        logger.info(
            "Background K8s sync for instance %s: status=%s, K8s calls %.2fms, total %.2fms",
            instance_id,
            actual_status,
            k8s_time,
            total_time,
        )
    finally:
        _syncing_instances.discard(instance_id)


@router.get("/my/instances", response_model=InstancesResponse)
@limiter.limit("30/minute")  # Reading is less sensitive
async def list_user_instances(
    request: Request, user: Annotated[dict, Depends(verify_user)], background_tasks: BackgroundTasks
) -> dict[str, Any]:
    """List instances for current user with background status refresh."""
    start = time.perf_counter()
    sb = ensure_supabase()
    account_id = user["account_id"]

    db_start = time.perf_counter()
    instances = instances_data.get_instances_for_account(sb, account_id)
    db_time = (time.perf_counter() - db_start) * 1000

    enhanced_instances: list[dict[str, Any]] = []
    for instance in instances:
        status = instance.get("status")
        hint: str | None = None

        if status == "provisioning":
            hint = (
                "Provisioning in progress. First boot can take several minutes while "
                "containers pull images and TLS certificates issue."
            )
        elif status == "restarting":
            hint = "Restarting MindRoom pods; the workspace will be available again shortly."
        elif status == "running" and not instance.get("kubernetes_synced_at"):
            hint = "Running (awaiting initial Kubernetes health check)."

        enhanced_instances.append({**instance, "status_hint": hint})

    # Check if any instance needs a background sync (older than 30 seconds)
    stale_threshold = datetime.now(UTC) - timedelta(seconds=30)
    for instance in enhanced_instances:
        instance_id = instance.get("instance_id")
        if not instance_id:
            continue

        # Skip if already being synced
        if str(instance_id) in _syncing_instances:
            continue

        # Check if kubernetes_synced_at is missing or stale
        synced_at = instance.get("kubernetes_synced_at")
        if not synced_at:
            needs_sync = True
        else:
            # Parse ISO timestamp (handle both Z and +00:00 formats)
            if synced_at.endswith("Z"):
                synced_at = synced_at[:-1] + "+00:00"
            synced_time = datetime.fromisoformat(synced_at)
            needs_sync = synced_time < stale_threshold

        if needs_sync:
            logger.info("Instance %s has stale K8s status, scheduling background sync", instance_id)
            background_tasks.add_task(_background_sync_instance_status, str(instance_id))

    # Log cache effectiveness
    total_time = (time.perf_counter() - start) * 1000
    logger.info("Instances endpoint: DB query %.2fms, total %.2fms (cached K8s status)", db_time, total_time)

    # Return cached data immediately
    return {"instances": enhanced_instances}


@router.post("/my/instances/provision", response_model=ProvisionResponse)
@limiter.limit("5/minute")  # Creating instances is expensive
async def provision_user_instance(
    request: Request,  # noqa: ARG001
    user: Annotated[dict, Depends(verify_user)],
    background_tasks: BackgroundTasks,
) -> dict[str, Any]:
    """Provision an instance for the current user."""
    sb = ensure_supabase()

    account_id = user["account_id"]
    sub_result = sb.table("subscriptions").select("*").eq("account_id", account_id).execute()
    if not sub_result.data:
        raise HTTPException(status_code=404, detail="No subscription found")
    subscription = sub_result.data[0]
    assert_instance_entitlement(subscription, "provision")

    inst_result = (
        sb.table("instances")
        .select("*")
        .eq("subscription_id", subscription["id"])  # one instance per subscription
        .limit(1)
        .execute()
    )
    if inst_result.data:
        existing = inst_result.data[0]

        # If instance is deprovisioned, reprovision it
        if existing.get("status") == "deprovisioned":
            logger.info(
                "Reprovisioning %s instance %s for user %s", existing.get("status"), existing["instance_id"], account_id
            )
            return await provisioner_service.provision_instance(
                sb,
                data={
                    "subscription_id": subscription["id"],
                    "account_id": account_id,
                    "tier": subscription["tier"],
                    "instance_id": existing["instance_id"],  # Reuse the same instance ID
                },
                background_tasks=background_tasks,
            )

        # Otherwise return existing instance metadata
        status = existing.get("status", "unknown")
        message = "Instance is already provisioning" if status == "provisioning" else "Instance already exists"
        logger.info(
            "Instance already exists for user %s with status %s, returning existing metadata", account_id, status
        )
        return {
            "success": True,
            "message": message,
            "customer_id": existing.get("instance_id") or existing.get("subdomain") or "",
            "frontend_url": existing.get("frontend_url") or existing.get("instance_url"),
            "api_url": existing.get("backend_url") or existing.get("api_url"),
            "matrix_url": existing.get("matrix_server_url") or existing.get("matrix_url"),
        }

    return await provisioner_service.provision_instance(
        sb,
        data={"subscription_id": subscription["id"], "account_id": account_id, "tier": subscription["tier"]},
        background_tasks=background_tasks,
    )


# Helper function for user instance actions
async def _verify_instance_ownership_and_run(
    instance_id: int,
    user: dict,
    instance_action: InstanceAction,
    *,
    require_active_subscription: bool,
) -> dict[str, Any]:
    """Verify user owns instance and run the provisioner service action."""
    sb = ensure_supabase()

    instance = instances_data.get_owned_instance(sb, instance_id, user["account_id"])
    if instance is None:
        raise HTTPException(status_code=404, detail="Instance not found or access denied")

    if require_active_subscription:
        sub_result = sb.table("subscriptions").select("*").eq("id", instance["subscription_id"]).limit(1).execute()
        if not sub_result.data:
            raise HTTPException(status_code=404, detail="Subscription not found")
        assert_instance_entitlement(sub_result.data[0], "run")

    return await instance_action(instance_id)


@router.post("/my/instances/{instance_id}/start", response_model=ActionResult)
@limiter.limit("10/minute")  # Control actions moderate rate
async def start_user_instance(
    request: Request,  # noqa: ARG001
    instance_id: int,
    user: Annotated[dict, Depends(verify_user)],
) -> dict[str, Any]:
    """Start user's instance."""
    return await _verify_instance_ownership_and_run(
        instance_id, user, provisioner_service.start_instance, require_active_subscription=True
    )


@router.post("/my/instances/{instance_id}/stop", response_model=ActionResult)
@limiter.limit("10/minute")  # Control actions moderate rate
async def stop_user_instance(
    request: Request,  # noqa: ARG001
    instance_id: int,
    user: Annotated[dict, Depends(verify_user)],
) -> dict[str, Any]:
    """Stop user's instance."""
    return await _verify_instance_ownership_and_run(
        instance_id, user, provisioner_service.stop_instance, require_active_subscription=False
    )


@router.post("/my/instances/{instance_id}/restart", response_model=ActionResult)
@limiter.limit("10/minute")  # Control actions moderate rate
async def restart_user_instance(
    request: Request,  # noqa: ARG001
    instance_id: int,
    user: Annotated[dict, Depends(verify_user)],
) -> dict[str, Any]:
    """Restart user's instance."""
    return await _verify_instance_ownership_and_run(
        instance_id, user, provisioner_service.restart_instance, require_active_subscription=True
    )
