"""Admin domain - FastAPI router.

All admin endpoints require the requesting user to exist in the `admins` table.
These endpoints manage platform-level configuration like subscription plans.

Admin Endpoints:
  POST   /api/admin/plans          Create subscription plan (syncs to Stripe)
  GET    /api/admin/plans          List all plans
  GET    /api/admin/plans/{id}     Get plan details
  PATCH  /api/admin/plans/{id}     Update plan (syncs price changes to Stripe)
"""
from __future__ import annotations
from typing import Optional
from uuid import UUID

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

from src.config import get_settings
from src.core.database import get_db
from src.core.exceptions import ForbiddenError
from src.domain.admin.repository import AdminRepository, PlanPriceVersionRepository, SubscriptionPlanRepository
from src.domain.admin.schemas import (
    SubscriptionPlanCreate,
    SubscriptionPlanResponse,
    SubscriptionPlanUpdate,
)
from src.domain.admin.service import AdminPlanService
from src.domain.auth.models import User
from src.domain.auth.router import get_current_user
from src.integrations.stripe_client import StripeClientImpl

router = APIRouter(prefix="/api/admin", tags=["admin"])


async def require_admin(
    user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
) -> User:
    admin_repo = AdminRepository(db)
    if not await admin_repo.is_admin(user.id):
        raise ForbiddenError("Admin access required")
    return user


def _get_stripe_client() -> Optional[StripeClientImpl]:
    settings = get_settings()
    if settings.stripe_secret_key:
        return StripeClientImpl()
    return None


async def get_admin_plan_service(db: AsyncSession = Depends(get_db)) -> AdminPlanService:
    return AdminPlanService(
        plan_repo=SubscriptionPlanRepository(db),
        price_version_repo=PlanPriceVersionRepository(db),
        stripe_client=_get_stripe_client(),
    )


@router.post("/plans", response_model=SubscriptionPlanResponse)
async def create_plan(
    body: SubscriptionPlanCreate,
    admin: User = Depends(require_admin),
    service: AdminPlanService = Depends(get_admin_plan_service),
):
    plan = await service.create_plan(body.model_dump())
    return SubscriptionPlanResponse.model_validate(plan)


@router.get("/plans", response_model=list[SubscriptionPlanResponse])
async def list_plans(
    active_only: bool = Query(False),
    admin: User = Depends(require_admin),
    service: AdminPlanService = Depends(get_admin_plan_service),
):
    plans = await service.list_plans(active_only=active_only)
    return [SubscriptionPlanResponse.model_validate(p) for p in plans]


@router.get("/plans/{plan_id}", response_model=SubscriptionPlanResponse)
async def get_plan(
    plan_id: UUID = Path(...),
    admin: User = Depends(require_admin),
    service: AdminPlanService = Depends(get_admin_plan_service),
):
    plan = await service.get_plan(plan_id)
    return SubscriptionPlanResponse.model_validate(plan)


@router.patch("/plans/{plan_id}", response_model=SubscriptionPlanResponse)
async def update_plan(
    body: SubscriptionPlanUpdate,
    plan_id: UUID = Path(...),
    admin: User = Depends(require_admin),
    service: AdminPlanService = Depends(get_admin_plan_service),
):
    data = body.model_dump(exclude_unset=True)
    plan = await service.update_plan(plan_id, data)
    return SubscriptionPlanResponse.model_validate(plan)


# ── Platform file sync ─────────────────────────────────────────────────────────

class PlatformFilesSyncRequest(BaseModel):
    project_id: Optional[UUID] = None
    template_id: Optional[UUID] = None


class PlatformFilesSyncResponse(BaseModel):
    queued: bool
    current_version: str
    project_id: Optional[str] = None
    template_id: Optional[str] = None
    scope: str


@router.post("/platform-files/sync", response_model=PlatformFilesSyncResponse)
async def trigger_platform_files_sync(
    body: PlatformFilesSyncRequest,
    _admin: User = Depends(require_admin),
):
    """Enqueue a sync of platform-managed files (p2hs.platform.json,
    .cursor/instructions.md, p2hs/cloud-access.md) to project/template repos.

    - project_id given  → sync that project only
    - template_id given → sync that template only
    - neither given     → bulk: all stale projects + templates (up to 50 each)

    A repo is considered stale when its platform_files_version is NULL or
    behind the current PLATFORM_FILES_VERSION constant.
    """
    from src.workers.enqueue import enqueue_sync_platform_files
    from src.domain.projects.platform_files_sync import PLATFORM_FILES_VERSION

    pid = str(body.project_id) if body.project_id else None
    tid = str(body.template_id) if body.template_id else None

    queued = await enqueue_sync_platform_files(project_id=pid, template_id=tid)

    if pid:
        scope = "project"
    elif tid:
        scope = "template"
    else:
        scope = "bulk"

    return PlatformFilesSyncResponse(
        queued=queued,
        current_version=PLATFORM_FILES_VERSION,
        project_id=pid,
        template_id=tid,
        scope=scope,
    )


class RuntimePrimitivesMigrateRequest(BaseModel):
    project_id: Optional[UUID] = None


class RuntimePrimitivesMigrateResponse(BaseModel):
    queued: bool
    project_id: Optional[str] = None
    scope: str


@router.post("/runtime-primitives/migrate", response_model=RuntimePrimitivesMigrateResponse)
async def trigger_runtime_primitives_migrate(
    body: RuntimePrimitivesMigrateRequest = Body(default_factory=RuntimePrimitivesMigrateRequest),
    _admin: User = Depends(require_admin),
):
    """Enqueue one-time leftover-auth-path migration onto platform consume/ports.

    - project_id given → that project only
    - omitted → up to 100 active projects
    """
    from src.workers.enqueue import enqueue_migrate_runtime_primitives

    pid = str(body.project_id) if body.project_id else None
    queued = await enqueue_migrate_runtime_primitives(project_id=pid)
    return RuntimePrimitivesMigrateResponse(
        queued=queued,
        project_id=pid,
        scope="project" if pid else "bulk",
    )


# ── Deploy force-cancel ────────────────────────────────────────────────────────

class DeployForceCancelResponse(BaseModel):
    deploy_id: str
    previous_status: str
    new_status: str


@router.post("/deploys/{deploy_id}/force-cancel", response_model=DeployForceCancelResponse)
async def force_cancel_deploy(
    deploy_id: UUID = Path(...),
    _admin: User = Depends(require_admin),
    db: AsyncSession = Depends(get_db),
):
    """Force a stuck deploy to 'failed' so the project is no longer blocked.

    Only acts on deploys in 'pending' or 'deploying' state. Use this when a
    deploy worker crashed or timed out and left a deploy permanently in-progress.
    For deploys already in a terminal state (failed, live, rolled_back) this
    is a no-op — the project will unblock automatically after the blocker-logic
    fix distinguishes 'deploy_failed' from 'deploy_in_progress'.
    """
    from src.domain.deployments.repository import DeployRepository

    repo = DeployRepository(db)
    deploy = await repo.get_by_id(deploy_id)
    if not deploy:
        raise HTTPException(status_code=404, detail="Deploy not found.")

    previous = deploy.status
    if previous not in ("pending", "deploying"):
        raise HTTPException(
            status_code=422,
            detail=f"Deploy is already in terminal state '{previous}'. No action needed.",
        )

    await repo.update_status(deploy_id, "failed")
    await db.commit()

    return DeployForceCancelResponse(
        deploy_id=str(deploy_id),
        previous_status=previous,
        new_status="failed",
    )
