from __future__ import annotations import uuid from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi.responses import FileResponse from sqlalchemy.orm import Session from app.core.database import get_db from app.core.dependencies import CurrentUser from app.core.logging_config import get_logger from app.repositories.document_repository import DocumentRepository, TemplateMatchRepository from app.repositories.template_repository import TemplateRepository from app.schemas.common import PaginatedResponse, SuccessResponse from app.schemas.document import TemplateMatchRequest, TemplateMatchResponse from app.schemas.template import ( TemplateListResponse, TemplateRenderRequest, TemplateRenderResponse, TemplateResponse, ) logger = get_logger(__name__) router = APIRouter(prefix="/templates", tags=["Templates"]) @router.get( "", response_model=PaginatedResponse[TemplateListResponse], summary="List Templates", description="List all active templates with pagination.", ) def list_templates( page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), current_user: CurrentUser = None, db: Session = Depends(get_db), ) -> PaginatedResponse[TemplateListResponse]: """List all active templates.""" template_repo = TemplateRepository(db) offset = (page - 1) * page_size templates = template_repo.get_active_templates(offset=offset, limit=page_size) total = template_repo.count_active() items = [TemplateListResponse.model_validate(t) for t in templates] return PaginatedResponse.create( items=items, total=total, page=page, page_size=page_size, ) @router.get( "/{template_id}", response_model=TemplateResponse, summary="Get Template", description="Retrieve a template by ID with all its components.", ) def get_template( template_id: uuid.UUID, current_user: CurrentUser = None, db: Session = Depends(get_db), ) -> TemplateResponse: """Get a template by ID.""" template_repo = TemplateRepository(db) template = template_repo.get_by_id(template_id) if not template: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Template '{template_id}' not found", ) return TemplateResponse.model_validate(template) @router.delete( "/{template_id}", response_model=SuccessResponse, summary="Delete Template", description="Soft-delete a template by deactivating it.", ) def delete_template( template_id: uuid.UUID, current_user: CurrentUser = None, db: Session = Depends(get_db), ) -> SuccessResponse: """Soft-delete a template.""" template_repo = TemplateRepository(db) template = template_repo.get_by_id(template_id) if not template: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Template '{template_id}' not found", ) template_repo.deactivate_template(template_id) db.commit() return SuccessResponse(message=f"Template '{template_id}' deactivated successfully") @router.post( "/match", response_model=list[TemplateMatchResponse], summary="Match Document to Templates", description="Match a document against existing templates and return ranked results.", ) def match_template( payload: TemplateMatchRequest, current_user: CurrentUser = None, db: Session = Depends(get_db), ) -> list[TemplateMatchResponse]: """Match a document against existing templates.""" doc_repo = DocumentRepository(db) document = doc_repo.get_by_id(payload.document_id) if not document: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Document '{payload.document_id}' not found", ) if document.status != "completed": raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Document must be in 'completed' status. Current status: '{document.status}'", ) # Perform template matching from app.services.matching_service import MatchingService matching_service = MatchingService(db) matches = matching_service.match_document( document_id=payload.document_id, min_confidence=payload.min_confidence, max_results=payload.max_results, ) db.commit() results = [] for match in matches: resp = TemplateMatchResponse( id=match.id, document_id=match.document_id, format_id=match.format_id, confidence_score=match.confidence_score, match_details=match.match_details, selected=match.selected, template_name=match.template.name if match.template else None, created_at=match.created_at, ) results.append(resp) return results @router.post( "/render", response_model=TemplateRenderResponse, summary="Render Template to PDF", description="Generate a PDF from a stored template with supplied data.", ) def render_template( payload: TemplateRenderRequest, current_user: CurrentUser = None, db: Session = Depends(get_db), ) -> TemplateRenderResponse: """Render a template to PDF.""" template_repo = TemplateRepository(db) template = template_repo.get_by_id(payload.template_id) if not template: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Template '{payload.template_id}' not found", ) if not template.is_active: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Template is deactivated", ) from app.services.reconstruction_service import ReconstructionService reconstruction_service = ReconstructionService(db) result = reconstruction_service.render_template( template=template, data=payload.data, output_filename=payload.output_filename, images=payload.images, ) return result @router.get( "/{template_id}/download", summary="Download Rendered PDF", description="Download a previously rendered PDF.", ) def download_rendered_pdf( template_id: uuid.UUID, filename: str = Query(..., description="Filename of the rendered PDF"), current_user: CurrentUser = None, db: Session = Depends(get_db), ) -> FileResponse: """Download a rendered PDF.""" from app.storage.provider import get_storage_provider storage = get_storage_provider() storage_path = f"rendered/{filename}" if not storage.file_exists(storage_path): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Rendered PDF '{filename}' not found", ) absolute_path = storage.get_absolute_path(storage_path) return FileResponse( path=absolute_path, media_type="application/pdf", filename=filename, )