from __future__ import annotations from io import BytesIO from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status from fastapi import Request from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates from dependencies import get_import_ingestion_service, require_roles from models import User from schemas.imports import ( ImportCommitRequest, ProjectImportCommitResponse, ProjectImportPreviewResponse, ScenarioImportCommitResponse, ScenarioImportPreviewResponse, ) from services.importers import ImportIngestionService, UnsupportedImportFormat router = APIRouter(prefix="/imports", tags=["Imports"]) templates = Jinja2Templates(directory="templates") MANAGE_ROLES = ("project_manager", "admin") @router.get( "/ui", response_class=HTMLResponse, include_in_schema=False, name="imports.ui", ) def import_dashboard( request: Request, _: User = Depends(require_roles(*MANAGE_ROLES)), ) -> HTMLResponse: return templates.TemplateResponse( request, "imports/ui.html", { "title": "Imports", }, ) async def _read_upload_file(upload: UploadFile) -> BytesIO: content = await upload.read() if not content: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Uploaded file is empty.", ) return BytesIO(content) @router.post( "/projects/preview", response_model=ProjectImportPreviewResponse, status_code=status.HTTP_200_OK, ) async def preview_project_import( file: UploadFile = File(..., description="Project import file (CSV or Excel)"), _: User = Depends(require_roles(*MANAGE_ROLES)), ingestion_service: ImportIngestionService = Depends( get_import_ingestion_service), ) -> ProjectImportPreviewResponse: if not file.filename: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Filename is required for import.", ) stream = await _read_upload_file(file) try: preview = ingestion_service.preview_projects(stream, file.filename) except UnsupportedImportFormat as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc), ) from exc return ProjectImportPreviewResponse.model_validate(preview) @router.post( "/scenarios/preview", response_model=ScenarioImportPreviewResponse, status_code=status.HTTP_200_OK, ) async def preview_scenario_import( file: UploadFile = File(..., description="Scenario import file (CSV or Excel)"), _: User = Depends(require_roles(*MANAGE_ROLES)), ingestion_service: ImportIngestionService = Depends( get_import_ingestion_service), ) -> ScenarioImportPreviewResponse: if not file.filename: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Filename is required for import.", ) stream = await _read_upload_file(file) try: preview = ingestion_service.preview_scenarios(stream, file.filename) except UnsupportedImportFormat as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc), ) from exc return ScenarioImportPreviewResponse.model_validate(preview) def _value_error_status(exc: ValueError) -> int: detail = str(exc) if detail.lower().startswith("unknown"): return status.HTTP_404_NOT_FOUND return status.HTTP_400_BAD_REQUEST @router.post( "/projects/commit", response_model=ProjectImportCommitResponse, status_code=status.HTTP_200_OK, ) async def commit_project_import_endpoint( payload: ImportCommitRequest, _: User = Depends(require_roles(*MANAGE_ROLES)), ingestion_service: ImportIngestionService = Depends( get_import_ingestion_service), ) -> ProjectImportCommitResponse: try: result = ingestion_service.commit_project_import(payload.token) except ValueError as exc: raise HTTPException( status_code=_value_error_status(exc), detail=str(exc), ) from exc return ProjectImportCommitResponse.model_validate(result) @router.post( "/scenarios/commit", response_model=ScenarioImportCommitResponse, status_code=status.HTTP_200_OK, ) async def commit_scenario_import_endpoint( payload: ImportCommitRequest, _: User = Depends(require_roles(*MANAGE_ROLES)), ingestion_service: ImportIngestionService = Depends( get_import_ingestion_service), ) -> ScenarioImportCommitResponse: try: result = ingestion_service.commit_scenario_import(payload.token) except ValueError as exc: raise HTTPException( status_code=_value_error_status(exc), detail=str(exc), ) from exc return ScenarioImportCommitResponse.model_validate(result)