import uuid
from collections.abc import Sequence
from typing import Any

from sqlalchemy import delete, func, or_, select, update
from sqlalchemy.ext.asyncio import AsyncSession

from src.domain.entities import MedicationEntity
from src.domain.interfaces import IMedicationRepository
from src.infrastructure.database.models import MedicationModel


def _model_to_entity(m: MedicationModel) -> MedicationEntity:
    return MedicationEntity(
        id=m.id,
        nregistro=m.nregistro,
        name=m.name,
        active_ingredient=m.active_ingredient,
        dosage=m.dosage,
        form=m.form,
        atc_code=m.atc_code,
        prescription_required=m.prescription_required,
        manufacturer=m.manufacturer,
        metadata_=m.metadata_,
        created_at=m.created_at,
        updated_at=m.updated_at,
    )


def _entity_to_model_data(e: MedicationEntity) -> dict[str, Any]:
    return {
        "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_,
    }


class MedicationRepository(IMedicationRepository):
    def __init__(self, session: AsyncSession) -> None:
        self._session = session

    async def get_by_id(self, medication_id: uuid.UUID) -> MedicationEntity | None:
        stmt = select(MedicationModel).where(MedicationModel.id == medication_id)
        result = await self._session.execute(stmt)
        model = result.scalar_one_or_none()
        return _model_to_entity(model) if model else None

    async def get_by_nregistro(self, nregistro: str) -> MedicationEntity | None:
        stmt = select(MedicationModel).where(MedicationModel.nregistro == nregistro)
        result = await self._session.execute(stmt)
        model = result.scalar_one_or_none()
        return _model_to_entity(model) if model else None

    async def search(self, query: str, *, offset: int = 0, limit: int = 50) -> tuple[Sequence[MedicationEntity], int]:
        pattern = f"%{query}%"
        filters = [
            or_(
                MedicationModel.name.ilike(pattern),
                MedicationModel.active_ingredient.ilike(pattern),
                MedicationModel.nregistro.ilike(pattern),
                MedicationModel.atc_code.ilike(pattern),
            )
        ]

        count_stmt = select(func.count()).select_from(MedicationModel).where(*filters)
        total = (await self._session.execute(count_stmt)).scalar() or 0

        stmt = (
            select(MedicationModel)
            .where(*filters)
            .order_by(MedicationModel.name)
            .offset(offset)
            .limit(limit)
        )
        result = await self._session.execute(stmt)
        models = result.scalars().all()
        return [_model_to_entity(m) for m in models], total

    async def create(self, entity: MedicationEntity) -> MedicationEntity:
        model = MedicationModel(**_entity_to_model_data(entity))
        self._session.add(model)
        await self._session.flush()
        return _model_to_entity(model)

    async def update(self, entity: MedicationEntity) -> MedicationEntity:
        data = _entity_to_model_data(entity)
        stmt = update(MedicationModel).where(MedicationModel.id == entity.id).values(**data)
        await self._session.execute(stmt)
        await self._session.flush()
        return await self.get_by_id(entity.id)  # type: ignore[return-value]

    async def delete(self, medication_id: uuid.UUID) -> bool:
        stmt = delete(MedicationModel).where(MedicationModel.id == medication_id)
        result = await self._session.execute(stmt)
        return result.rowcount > 0
