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

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

from src.domain.entities import InventoryHistoryEntity, InventoryRecordEntity
from src.domain.interfaces import IInventoryRepository
from src.infrastructure.database.models import InventoryHistoryModel, InventoryRecordModel


def _record_model_to_entity(m: InventoryRecordModel) -> InventoryRecordEntity:
    return InventoryRecordEntity(
        id=m.id,
        pharmacy_id=m.pharmacy_id,
        medication_id=m.medication_id,
        stock=m.stock,
        reserved_stock=m.reserved_stock,
        available_stock=m.available_stock,
        min_stock=m.min_stock,
        max_stock=m.max_stock,
        last_updated_at=m.last_updated_at,
        source=m.source,
        batch_id=m.batch_id,
        metadata_=m.metadata_,
        created_at=m.created_at,
        updated_at=m.updated_at,
    )


def _record_entity_to_model_data(e: InventoryRecordEntity) -> dict[str, Any]:
    return {
        "pharmacy_id": e.pharmacy_id,
        "medication_id": e.medication_id,
        "stock": e.stock,
        "reserved_stock": e.reserved_stock,
        "available_stock": e.available_stock,
        "min_stock": e.min_stock,
        "max_stock": e.max_stock,
        "last_updated_at": e.last_updated_at,
        "source": e.source,
        "batch_id": e.batch_id,
        "metadata_": e.metadata_,
    }


def _history_model_to_entity(m: InventoryHistoryModel) -> InventoryHistoryEntity:
    return InventoryHistoryEntity(
        id=m.id,
        pharmacy_id=m.pharmacy_id,
        medication_id=m.medication_id,
        previous_stock=m.previous_stock,
        new_stock=m.new_stock,
        delta=m.delta,
        reason=m.reason,
        source=m.source,
        created_at=m.created_at,
    )


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

    async def get_record(self, pharmacy_id: uuid.UUID, medication_id: uuid.UUID) -> InventoryRecordEntity | None:
        stmt = select(InventoryRecordModel).where(
            InventoryRecordModel.pharmacy_id == pharmacy_id,
            InventoryRecordModel.medication_id == medication_id,
        )
        result = await self._session.execute(stmt)
        model = result.scalar_one_or_none()
        return _record_model_to_entity(model) if model else None

    async def list_by_pharmacy(self, pharmacy_id: uuid.UUID, *, offset: int = 0, limit: int = 50) -> tuple[Sequence[InventoryRecordEntity], int]:
        count_stmt = select(func.count()).select_from(InventoryRecordModel).where(
            InventoryRecordModel.pharmacy_id == pharmacy_id
        )
        total = (await self._session.execute(count_stmt)).scalar() or 0

        stmt = (
            select(InventoryRecordModel)
            .where(InventoryRecordModel.pharmacy_id == pharmacy_id)
            .order_by(InventoryRecordModel.last_updated_at.desc().nullslast())
            .offset(offset)
            .limit(limit)
        )
        result = await self._session.execute(stmt)
        models = result.scalars().all()
        return [_record_model_to_entity(m) for m in models], total

    async def upsert(self, entity: InventoryRecordEntity) -> InventoryRecordEntity:
        existing = await self.get_record(entity.pharmacy_id, entity.medication_id)
        if existing:
            data = _record_entity_to_model_data(entity)
            stmt = (
                update(InventoryRecordModel)
                .where(
                    InventoryRecordModel.pharmacy_id == entity.pharmacy_id,
                    InventoryRecordModel.medication_id == entity.medication_id,
                )
                .values(**data)
            )
            await self._session.execute(stmt)
            await self._session.flush()
            return await self.get_record(entity.pharmacy_id, entity.medication_id)  # type: ignore[return-value]
        else:
            model = InventoryRecordModel(**_record_entity_to_model_data(entity))
            self._session.add(model)
            await self._session.flush()
            return _record_model_to_entity(model)

    async def add_history(self, entity: InventoryHistoryEntity) -> InventoryHistoryEntity:
        model = InventoryHistoryModel(
            inventory_record_id=entity.inventory_record_id,
            pharmacy_id=entity.pharmacy_id,
            medication_id=entity.medication_id,
            previous_stock=entity.previous_stock,
            new_stock=entity.new_stock,
            delta=entity.delta,
            reason=entity.reason,
            source=entity.source,
        )
        self._session.add(model)
        await self._session.flush()
        return _history_model_to_entity(model)

    async def get_history(self, pharmacy_id: uuid.UUID, medication_id: uuid.UUID, *, offset: int = 0, limit: int = 50) -> Sequence[InventoryHistoryEntity]:
        record = await self.get_record(pharmacy_id, medication_id)
        if not record:
            return []

        stmt = (
            select(InventoryHistoryModel)
            .where(InventoryHistoryModel.inventory_record_id == record.id)
            .order_by(InventoryHistoryModel.created_at.desc())
            .offset(offset)
            .limit(limit)
        )
        result = await self._session.execute(stmt)
        models = result.scalars().all()
        return [_history_model_to_entity(m) for m in models]
