from datetime import datetime from typing import Any from uuid import UUID from fastapi import APIRouter, Depends, Query from pydantic import BaseModel, Field from sqlalchemy.ext.asyncio import AsyncSession from src.api.v1.dependencies.auth import get_current_user, require_role from src.core.exceptions import ConflictException, NotFoundException from src.domain.entities import MedicationEntity, UserRole from src.infrastructure.database.repositories import MedicationCatalogRepository, MedicationRepository from src.infrastructure.database.session import get_db_session from src.services.auth.security import TokenData from src.services.medication.medication_service import MedicationService router = APIRouter(prefix="/medications", tags=["Medications"]) class MedicationCreateRequest(BaseModel): nregistro: str = Field(..., min_length=1, max_length=50) name: str = Field(..., min_length=1, max_length=500) active_ingredient: str | None = Field(None, max_length=500) dosage: str | None = Field(None, max_length=100) form: str | None = Field(None, max_length=100) atc_code: str | None = Field(None, max_length=20) prescription_required: bool = False manufacturer: str | None = Field(None, max_length=255) metadata: dict[str, Any] | None = None class MedicationUpdateRequest(BaseModel): name: str | None = Field(None, min_length=1, max_length=500) active_ingredient: str | None = Field(None, max_length=500) dosage: str | None = Field(None, max_length=100) form: str | None = Field(None, max_length=100) atc_code: str | None = Field(None, max_length=20) prescription_required: bool | None = None manufacturer: str | None = Field(None, max_length=255) metadata: dict[str, Any] | None = None class MedicationResponse(BaseModel): id: UUID nregistro: str name: str active_ingredient: str | None dosage: str | None form: str | None atc_code: str | None prescription_required: bool manufacturer: str | None metadata: dict[str, Any] | None created_at: datetime | None updated_at: datetime | None class MedicationListResponse(BaseModel): items: list[MedicationResponse] total: int offset: int limit: int def _entity_to_response(e: MedicationEntity) -> MedicationResponse: return MedicationResponse( id=e.id, nregistro=e.nregistro, name=e.name, active_ingredient=e.active_ingredient, dosage=e.dosage, form=e.form, atc_code=e.atc_code, prescription_required=e.prescription_required, manufacturer=e.manufacturer, metadata=e.metadata_, created_at=e.created_at, updated_at=e.updated_at, ) def _get_service(session: AsyncSession) -> MedicationService: return MedicationService( medication_repo=MedicationRepository(session), catalog_repo=MedicationCatalogRepository(session), ) @router.get("", response_model=MedicationListResponse) async def list_medications( q: str | None = None, offset: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=200), session: AsyncSession = Depends(get_db_session), current_user: TokenData = Depends(get_current_user), ): service = _get_service(session) if q: entities, total = await service.search_medications(q, offset=offset, limit=limit) else: from src.domain.entities import MedicationEntity all_entities, total = await service.search_medications("", offset=offset, limit=limit) entities = all_entities return MedicationListResponse(items=[_entity_to_response(e) for e in entities], total=total, offset=offset, limit=limit) @router.get("/search", response_model=MedicationListResponse) async def search_medications( q: str = Query(..., min_length=1), offset: int = Query(0, ge=0), limit: int = Query(50, ge=1, le=200), session: AsyncSession = Depends(get_db_session), current_user: TokenData = Depends(get_current_user), ): service = _get_service(session) entities, total = await service.search_medications(q, offset=offset, limit=limit) return MedicationListResponse(items=[_entity_to_response(e) for e in entities], total=total, offset=offset, limit=limit) @router.get("/nregistro/{nregistro}", response_model=MedicationResponse) async def get_medication_by_nregistro( nregistro: str, session: AsyncSession = Depends(get_db_session), current_user: TokenData = Depends(get_current_user), ): service = _get_service(session) entity = await service.get_by_nregistro(nregistro) if not entity: raise NotFoundException("Medication", nregistro) return _entity_to_response(entity) @router.get("/{medication_id}", response_model=MedicationResponse) async def get_medication( medication_id: UUID, session: AsyncSession = Depends(get_db_session), current_user: TokenData = Depends(get_current_user), ): service = _get_service(session) entity = await service.get_medication(medication_id) if not entity: raise NotFoundException("Medication", str(medication_id)) return _entity_to_response(entity) @router.post("", response_model=MedicationResponse, status_code=201) async def create_medication( body: MedicationCreateRequest, session: AsyncSession = Depends(get_db_session), current_user: TokenData = Depends(require_role(UserRole.ADMIN)), ): service = _get_service(session) entity = MedicationEntity( nregistro=body.nregistro, name=body.name, active_ingredient=body.active_ingredient, dosage=body.dosage, form=body.form, atc_code=body.atc_code, prescription_required=body.prescription_required, manufacturer=body.manufacturer, metadata_=body.metadata, ) created = await service.create_medication(entity) return _entity_to_response(created) @router.put("/{medication_id}", response_model=MedicationResponse) async def update_medication( medication_id: UUID, body: MedicationUpdateRequest, session: AsyncSession = Depends(get_db_session), current_user: TokenData = Depends(require_role(UserRole.ADMIN)), ): service = _get_service(session) entity = await service.get_medication(medication_id) if not entity: raise NotFoundException("Medication", str(medication_id)) update_data = body.model_dump(exclude_unset=True) if "metadata" in update_data: entity.metadata_ = update_data.pop("metadata") for key, value in update_data.items(): setattr(entity, key, value) updated = await service.update_medication(entity) return _entity_to_response(updated) @router.delete("/{medication_id}", status_code=204) async def delete_medication( medication_id: UUID, session: AsyncSession = Depends(get_db_session), current_user: TokenData = Depends(require_role(UserRole.ADMIN)), ): service = _get_service(session) deleted = await service.delete_medication(medication_id) if not deleted: raise NotFoundException("Medication", str(medication_id))