"""Dropbox import test routes — enqueue Cloud jobs and list stored assets."""
from __future__ import annotations

import json
from uuid import UUID

import httpx
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from pydantic import ValidationError
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.database import get_db
from src.core.dependencies import require_active_billing_for_project_id
from src.domain.auth.models import User
from src.domain.auth.router import get_current_user
from src.domain.cloud.dependencies import get_cloud_internal_service
from src.domain.cloud.service import CloudInternalService
from src.domain.dropbox_imports.schemas import (
    DROPBOX_IMPORT_BUCKET,
    ArchiveExtractJobResponse,
    AssetAccessUrlResponse,
    DeleteStorageAssetResponse,
    DropboxBucketSettingsResponse,
    DropboxImportJobResponse,
    StartArchiveExtractRequest,
    StartDropboxImportRequest,
    StorageAssetsListResponse,
    UpdateDropboxBucketSettingsRequest,
)
from src.domain.dropbox_imports.service import DropboxImportService
from src.domain.project_storage.repository import (
    ProjectStorageBucketRepository,
    ProjectStorageUsageRepository,
)
from src.domain.project_storage.service import ProjectStorageService
from src.domain.projects.repository import ProjectRepository
from src.integrations.cloud_control_client import CloudControlClient, get_cloud_client_for_workspace

router = APIRouter(
    prefix="/api/projects/{project_id}/dropbox-imports",
    tags=["dropbox-imports"],
)


async def _get_service(
    project_id: UUID = Depends(require_active_billing_for_project_id),
    db: AsyncSession = Depends(get_db),
    cloud_service: CloudInternalService = Depends(get_cloud_internal_service),
) -> DropboxImportService:
    project = await ProjectRepository(db).get_by_id(project_id)
    cloud_client = (
        await get_cloud_client_for_workspace(project.workspace_id, db)
        if project
        else CloudControlClient()
    )
    return DropboxImportService(
        db=db,
        project_repo=ProjectRepository(db),
        cloud_service=cloud_service,
        storage_service=ProjectStorageService(
            bucket_repo=ProjectStorageBucketRepository(db),
            usage_repo=ProjectStorageUsageRepository(db),
            cloud_client=cloud_client,
        ),
        bucket_repo=ProjectStorageBucketRepository(db),
    )


async def _parse_dropbox_import_body(request: Request) -> StartDropboxImportRequest:
    raw = await request.body()
    if not raw:
        raise HTTPException(status_code=400, detail="Request body is required")
    try:
        data = json.loads(raw)
    except json.JSONDecodeError as exc:
        preview = raw[:160].decode("utf-8", errors="replace")
        raise HTTPException(
            status_code=422,
            detail={
                "error": "invalid_json",
                "message": str(exc),
                "preview": preview,
            },
        ) from exc
    if not isinstance(data, dict):
        raise HTTPException(status_code=422, detail="JSON body must be an object")
    try:
        return StartDropboxImportRequest.model_validate(data)
    except ValidationError as exc:
        raise HTTPException(status_code=422, detail=exc.errors()) from exc


@router.post("", response_model=DropboxImportJobResponse, status_code=202)
async def start_dropbox_import(
    request: Request,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    svc: DropboxImportService = Depends(_get_service),
    db: AsyncSession = Depends(get_db),
) -> DropboxImportJobResponse:
    body = await _parse_dropbox_import_body(request)
    project = await ProjectRepository(db).get_by_id(project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    try:
        return await svc.start_import(project, body)
    except ValueError as exc:
        raise HTTPException(status_code=403, detail=str(exc)) from exc
    except RuntimeError as exc:
        raise HTTPException(status_code=502, detail=str(exc)) from exc
    except httpx.HTTPStatusError as exc:
        raise HTTPException(
            status_code=502,
            detail=f"Cloud control API error ({exc.response.status_code})",
        ) from exc


@router.get("/assets", response_model=StorageAssetsListResponse)
async def list_dropbox_assets(
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    svc: DropboxImportService = Depends(_get_service),
    db: AsyncSession = Depends(get_db),
    bucket_name: str = Query(default=DROPBOX_IMPORT_BUCKET),
    limit: int = Query(default=100, ge=1, le=100),
    cursor: str | None = Query(default=None),
) -> StorageAssetsListResponse:
    project = await ProjectRepository(db).get_by_id(project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    return await svc.list_assets(project, bucket_name=bucket_name, limit=limit, cursor=cursor)


@router.get("/bucket-settings", response_model=DropboxBucketSettingsResponse)
async def get_dropbox_bucket_settings(
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    svc: DropboxImportService = Depends(_get_service),
    db: AsyncSession = Depends(get_db),
    bucket_name: str = Query(default=DROPBOX_IMPORT_BUCKET),
) -> DropboxBucketSettingsResponse:
    project = await ProjectRepository(db).get_by_id(project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    return await svc.get_bucket_settings(project, bucket_name=bucket_name)


@router.patch("/bucket-settings", response_model=DropboxBucketSettingsResponse)
async def update_dropbox_bucket_settings(
    body: UpdateDropboxBucketSettingsRequest,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    svc: DropboxImportService = Depends(_get_service),
    db: AsyncSession = Depends(get_db),
) -> DropboxBucketSettingsResponse:
    project = await ProjectRepository(db).get_by_id(project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    try:
        return await svc.update_bucket_settings(project, body)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc


@router.get("/files/{file_id}/url", response_model=AssetAccessUrlResponse)
async def get_dropbox_asset_url(
    file_id: str,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    svc: DropboxImportService = Depends(_get_service),
    db: AsyncSession = Depends(get_db),
    bucket_name: str = Query(default=DROPBOX_IMPORT_BUCKET),
    expires_in: int = Query(default=86400, ge=60, le=86400),
) -> AssetAccessUrlResponse:
    project = await ProjectRepository(db).get_by_id(project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    try:
        return await svc.get_asset_access_url(
            project,
            file_id,
            bucket_name=bucket_name,
            expires_in=expires_in,
        )
    except ValueError as exc:
        if str(exc) == "file_not_found":
            raise HTTPException(status_code=404, detail="file_not_found") from exc
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except RuntimeError as exc:
        raise HTTPException(status_code=502, detail=str(exc)) from exc
    except httpx.HTTPStatusError as exc:
        raise HTTPException(
            status_code=502,
            detail=f"Cloud control API error ({exc.response.status_code})",
        ) from exc


@router.post(
    "/files/{file_id}/extract",
    response_model=ArchiveExtractJobResponse,
    status_code=202,
)
async def start_archive_extract(
    file_id: str,
    request: Request,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    svc: DropboxImportService = Depends(_get_service),
    db: AsyncSession = Depends(get_db),
) -> ArchiveExtractJobResponse:
    project = await ProjectRepository(db).get_by_id(project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    raw = await request.body()
    body = StartArchiveExtractRequest()
    if raw:
        try:
            data = json.loads(raw)
            if isinstance(data, dict):
                body = StartArchiveExtractRequest.model_validate(data)
        except (json.JSONDecodeError, ValidationError) as exc:
            raise HTTPException(status_code=422, detail=str(exc)) from exc
    try:
        return await svc.start_archive_extract(project, file_id, body)
    except ValueError as exc:
        code = str(exc)
        status = 404 if code == "file_not_found" else 400
        raise HTTPException(status_code=status, detail=code) from exc
    except RuntimeError as exc:
        raise HTTPException(status_code=502, detail=str(exc)) from exc
    except httpx.HTTPStatusError as exc:
        raise HTTPException(
            status_code=502,
            detail=f"Cloud control API error ({exc.response.status_code})",
        ) from exc


@router.delete("/files/{file_id}", response_model=DeleteStorageAssetResponse)
async def delete_dropbox_asset(
    file_id: str,
    project_id: UUID = Depends(require_active_billing_for_project_id),
    _: User = Depends(get_current_user),
    svc: DropboxImportService = Depends(_get_service),
    db: AsyncSession = Depends(get_db),
    bucket_name: str = Query(default=DROPBOX_IMPORT_BUCKET),
) -> DeleteStorageAssetResponse:
    project = await ProjectRepository(db).get_by_id(project_id)
    if not project:
        raise HTTPException(status_code=404, detail="Project not found")
    try:
        return await svc.delete_asset(project, file_id, bucket_name=bucket_name)
    except ValueError as exc:
        if str(exc) == "file_not_found":
            raise HTTPException(status_code=404, detail="file_not_found") from exc
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except RuntimeError as exc:
        raise HTTPException(status_code=502, detail=str(exc)) from exc
    except httpx.HTTPStatusError as exc:
        raise HTTPException(
            status_code=502,
            detail=f"Cloud control API error ({exc.response.status_code})",
        ) from exc
