from __future__ import annotations

from datetime import datetime, timezone
from typing import Any
from uuid import UUID

from src.core.exceptions import ConflictException, NotFoundException, ValidationException
from src.core.logging import get_logger
from src.domain.entities import MedicationCatalogEntryEntity, MedicationEntity
from src.domain.interfaces import IMedicationCatalogRepository, IMedicationRepository
from src.infrastructure.messaging.rabbitmq import rabbitmq_client

logger = get_logger("medication_service")


class MedicationService:
    def __init__(
        self,
        medication_repo: IMedicationRepository,
        catalog_repo: IMedicationCatalogRepository,
    ) -> None:
        self._medication_repo = medication_repo
        self._catalog_repo = catalog_repo

    async def create_medication(self, entity: MedicationEntity) -> MedicationEntity:
        existing = await self._medication_repo.get_by_nregistro(entity.nregistro)
        if existing:
            raise ConflictException(f"Medication with nregistro '{entity.nregistro}' already exists")
        created = await self._medication_repo.create(entity)
        await self._publish_event("medication.created", created.id, {"nregistro": created.nregistro, "name": created.name})
        return created

    async def get_medication(self, medication_id: UUID) -> MedicationEntity | None:
        return await self._medication_repo.get_by_id(medication_id)

    async def get_by_nregistro(self, nregistro: str) -> MedicationEntity | None:
        return await self._medication_repo.get_by_nregistro(nregistro)

    async def search_medications(self, query: str, *, offset: int = 0, limit: int = 50) -> tuple[list[MedicationEntity], int]:
        return await self._medication_repo.search(query, offset=offset, limit=limit)

    async def update_medication(self, entity: MedicationEntity) -> MedicationEntity:
        existing = await self._medication_repo.get_by_id(entity.id)  # type: ignore[arg-type]
        if not existing:
            raise NotFoundException("Medication", str(entity.id))
        updated = await self._medication_repo.update(entity)
        await self._publish_event("medication.updated", updated.id, {"nregistro": updated.nregistro})
        return updated

    async def delete_medication(self, medication_id: UUID) -> bool:
        deleted = await self._medication_repo.delete(medication_id)
        if deleted:
            await self._publish_event("medication.deleted", medication_id, {})
        return deleted

    async def get_catalog_entry(self, pharmacy_id: UUID, medication_id: UUID) -> MedicationCatalogEntryEntity | None:
        return await self._catalog_repo.get_entry(pharmacy_id, medication_id)

    async def list_catalog(self, pharmacy_id: UUID, *, offset: int = 0, limit: int = 50) -> tuple[list[MedicationCatalogEntryEntity], int]:
        return await self._catalog_repo.list_by_pharmacy(pharmacy_id, offset=offset, limit=limit)

    async def upsert_catalog_entry(self, entity: MedicationCatalogEntryEntity) -> MedicationCatalogEntryEntity:
        upserted = await self._catalog_repo.upsert(entity)
        await self._publish_event("catalog.upserted", upserted.id, {
            "pharmacy_id": str(entity.pharmacy_id),
            "medication_id": str(entity.medication_id),
            "is_available": entity.is_available,
            "stock": entity.stock,
        })
        return upserted

    async def _publish_event(self, event_type: str, resource_id: UUID, payload: dict[str, Any]) -> None:
        try:
            routing_key = event_type.replace(".", "_")
            full_payload = {
                "event_type": event_type,
                "resource_id": str(resource_id),
                "timestamp": datetime.now(timezone.utc).isoformat(),
                **payload,
            }
            await rabbitmq_client.publish(routing_key, full_payload)
        except Exception as exc:
            logger.warning("medication_event_publish_failed", extra={"event_type": event_type, "error": str(exc)})
