"""cloud_infra domain — HTTP routes.

Two router surfaces:
1. /api/admin/cloud-infra  — ops console (requires admin auth)
2. /v1/internal/cloud-instances  — called by Cloud instances on startup (CLOUD_INTERNAL_TOKEN)
"""
from __future__ import annotations

from typing import Annotated, Optional
from uuid import UUID

from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.database import get_db
from src.domain.admin.router import require_admin
from src.domain.auth.models import User
from src.domain.cloud.dependencies import require_cloud_internal_token
from src.domain.cloud_infra.repository import (
    CFAccountRepository,
    CloudInstanceRepository,
    CloudShardMigrationStatusRepository,
    CloudShardRepository,
    ProjectCloudAssignmentRepository,
    WorkspaceCloudAssignmentRepository,
)
from src.domain.cloud_infra.schemas import (
    AssignProjectToShardRequest,
    AssignWorkspaceRequest,
    CFAccountResponse,
    CloudInstanceHeartbeatResponse,
    CloudInstanceResponse,
    CloudShardMigrationStatusResponse,
    CloudShardResponse,
    CreateCFAccountRequest,
    CreateCloudShardRequest,
    ProjectCloudAssignmentResponse,
    RegisterCloudInstanceRequest,
    ReportShardMigrationRequest,
    ShardFleetMigrationRow,
    UpdateCFAccountRequest,
    UpdateCloudInstanceRequest,
    UpdateCloudShardRequest,
    WorkspaceCloudAssignmentResponse,
)
from src.domain.cloud_infra.service import CloudInfraService


def _get_service(db: AsyncSession = Depends(get_db)) -> CloudInfraService:
    from src.integrations.cloud_control_client import CloudControlClient
    return CloudInfraService(
        cf_account_repo=CFAccountRepository(db),
        instance_repo=CloudInstanceRepository(db),
        assignment_repo=WorkspaceCloudAssignmentRepository(db),
        shard_repo=CloudShardRepository(db),
        project_shard_repo=ProjectCloudAssignmentRepository(db),
        shard_migration_repo=CloudShardMigrationStatusRepository(db),
        cloud_client=CloudControlClient(),
    )


# ---------------------------------------------------------------------------
# Admin router — ops console
# ---------------------------------------------------------------------------

admin_router = APIRouter(
    prefix="/api/admin/cloud-infra",
    tags=["admin", "cloud-infra"],
)


@admin_router.post("/cf-accounts", response_model=CFAccountResponse)
async def create_cf_account(
    body: CreateCFAccountRequest,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> CFAccountResponse:
    return await svc.create_cf_account(body)


@admin_router.get("/cf-accounts", response_model=list[CFAccountResponse])
async def list_cf_accounts(
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> list[CFAccountResponse]:
    return await svc.list_cf_accounts()


@admin_router.get("/cf-accounts/{account_id}", response_model=CFAccountResponse)
async def get_cf_account(
    account_id: UUID,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> CFAccountResponse:
    return await svc.get_cf_account(account_id)


@admin_router.patch("/cf-accounts/{account_id}", response_model=CFAccountResponse)
async def update_cf_account(
    account_id: UUID,
    body: UpdateCFAccountRequest,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> CFAccountResponse:
    return await svc.update_cf_account(account_id, body)


@admin_router.delete("/cf-accounts/{account_id}", status_code=204, response_model=None)
async def delete_cf_account(
    account_id: UUID,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> None:
    await svc.delete_cf_account(account_id)


@admin_router.get("/instances", response_model=list[CloudInstanceResponse])
async def list_instances(
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> list[CloudInstanceResponse]:
    return await svc.list_instances()


@admin_router.patch("/instances/{instance_id}", response_model=CloudInstanceResponse)
async def update_instance(
    instance_id: UUID,
    body: UpdateCloudInstanceRequest,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> CloudInstanceResponse:
    return await svc.update_instance(instance_id, body)


@admin_router.post(
    "/workspaces/{workspace_id}/assign",
    response_model=WorkspaceCloudAssignmentResponse,
)
async def assign_workspace(
    workspace_id: UUID,
    body: AssignWorkspaceRequest,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> WorkspaceCloudAssignmentResponse:
    return await svc.assign_workspace(workspace_id, body)


@admin_router.get(
    "/workspaces/{workspace_id}/assignment",
    response_model=WorkspaceCloudAssignmentResponse,
)
async def get_workspace_assignment(
    workspace_id: UUID,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> WorkspaceCloudAssignmentResponse:
    from src.core.exceptions import NotFoundError

    result = await svc.get_assignment(workspace_id)
    if not result:
        raise NotFoundError("No active cloud assignment for this workspace")
    return result


# ---------------------------------------------------------------------------
# Cloud Shards
# ---------------------------------------------------------------------------


@admin_router.post("/shards", response_model=CloudShardResponse, status_code=201)
async def create_shard(
    body: CreateCloudShardRequest,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> CloudShardResponse:
    return await svc.create_shard(body)


@admin_router.get("/shards", response_model=list[CloudShardResponse])
async def list_shards(
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> list[CloudShardResponse]:
    return await svc.list_shards()


@admin_router.get("/shards/{shard_id}", response_model=CloudShardResponse)
async def get_shard(
    shard_id: UUID,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> CloudShardResponse:
    return await svc.get_shard(shard_id)


@admin_router.patch("/shards/{shard_id}", response_model=CloudShardResponse)
async def update_shard(
    shard_id: UUID,
    body: UpdateCloudShardRequest,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> CloudShardResponse:
    return await svc.update_shard(shard_id, body)


# ---------------------------------------------------------------------------
# Project Shard Assignments
# ---------------------------------------------------------------------------


@admin_router.post(
    "/projects/{project_id}/shard-assignment",
    response_model=ProjectCloudAssignmentResponse,
)
async def assign_project_to_shard(
    project_id: UUID,
    body: AssignProjectToShardRequest,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> ProjectCloudAssignmentResponse:
    return await svc.assign_project_to_shard(project_id, body)


@admin_router.get(
    "/projects/{project_id}/shard-assignment",
    response_model=ProjectCloudAssignmentResponse,
)
async def get_project_shard_assignment(
    project_id: UUID,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> ProjectCloudAssignmentResponse:
    from src.core.exceptions import NotFoundError

    result = await svc.get_project_shard_assignment(project_id)
    if not result:
        raise NotFoundError("No shard assignment found for this project")
    return result


# ---------------------------------------------------------------------------
# Shard Migration Status
# ---------------------------------------------------------------------------


@admin_router.get(
    "/shards/{shard_id}/migrations",
    response_model=list[CloudShardMigrationStatusResponse],
)
async def list_shard_migrations(
    shard_id: UUID,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> list[CloudShardMigrationStatusResponse]:
    """List all reported migration versions for a shard."""
    return await svc.list_shard_migrations(shard_id)


@admin_router.get(
    "/migrations/fleet",
    response_model=list[ShardFleetMigrationRow],
)
async def get_fleet_migration_status(
    expected_head: Optional[str] = None,
    _: User = Depends(require_admin),
    svc: CloudInfraService = Depends(_get_service),
) -> list[ShardFleetMigrationRow]:
    """Fleet-wide view: one row per shard with its latest migration version.

    Pass ?expected_head=0011_query_import to flag shards that are behind.
    """
    return await svc.get_fleet_migration_status(expected_head=expected_head)


# ---------------------------------------------------------------------------
# Internal router — called by Cloud instances (CLOUD_INTERNAL_TOKEN)
# ---------------------------------------------------------------------------

internal_router = APIRouter(
    prefix="/v1/internal/cloud-instances",
    tags=["cloud-infra-internal"],
    dependencies=[Depends(require_cloud_internal_token)],
)


@internal_router.post("/register", response_model=CloudInstanceResponse)
async def register_cloud_instance(
    body: RegisterCloudInstanceRequest,
    svc: CloudInfraService = Depends(_get_service),
) -> CloudInstanceResponse:
    """Called by a Cloud instance on startup to register or refresh its record in Core."""
    return await svc.register_instance(body)


@internal_router.post("/heartbeat/{instance_id}", response_model=CloudInstanceHeartbeatResponse)
async def cloud_instance_heartbeat(
    instance_id: str,
    svc: CloudInfraService = Depends(_get_service),
) -> CloudInstanceHeartbeatResponse:
    """Periodic heartbeat from a running Cloud instance."""
    return await svc.heartbeat(instance_id)


@internal_router.post(
    "/report-migration",
    response_model=CloudShardMigrationStatusResponse,
    status_code=201,
)
async def report_shard_migration(
    body: ReportShardMigrationRequest,
    svc: CloudInfraService = Depends(_get_service),
) -> CloudShardMigrationStatusResponse:
    """Called by a shard's gateway after wrangler d1 migrations apply completes.

    Core records the applied version and flags the shard as current or lagging.
    """
    return await svc.report_shard_migration(body)
