"""
Repository layer for Notification audit records and queries.
"""

from datetime import datetime
from uuid import UUID

from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.models.notification import Notification, NotificationChannel, NotificationStatus
from app.models.patient import Patient


class NotificationRepository:
    """Handles database persistence, queries, and status updates for notifications."""

    async def create(self, db: AsyncSession, notification: Notification) -> Notification:
        """Persist a new notification audit record."""
        db.add(notification)
        await db.flush()
        await db.refresh(notification)
        return notification

    async def get_by_id(self, db: AsyncSession, notification_id: UUID) -> Notification | None:
        """Fetch notification by ID with patient and order relations loaded."""
        stmt = (
            select(Notification)
            .options(
                selectinload(Notification.patient),
                selectinload(Notification.order),
                selectinload(Notification.report),
            )
            .where(Notification.id == notification_id)
        )
        result = await db.execute(stmt)
        return result.scalar_one_or_none()

    async def list(
        self,
        db: AsyncSession,
        limit: int = 50,
        offset: int = 0,
        lab_id: UUID | None = None,
        patient_id: UUID | None = None,
        channel: NotificationChannel | None = None,
        status: NotificationStatus | None = None,
    ) -> tuple[list[Notification], int]:
        """
        List notifications with optional filters and total count.
        When lab_id is provided, joins Patient to enforce branch scoping.
        """
        query = select(Notification).join(Notification.patient)
        count_query = select(func.count(Notification.id)).join(Notification.patient)

        if lab_id is not None:
            query = query.where(Patient.lab_id == lab_id)
            count_query = count_query.where(Patient.lab_id == lab_id)

        if patient_id is not None:
            query = query.where(Notification.patient_id == patient_id)
            count_query = count_query.where(Notification.patient_id == patient_id)

        if channel is not None:
            query = query.where(Notification.channel == channel)
            count_query = count_query.where(Notification.channel == channel)

        if status is not None:
            query = query.where(Notification.status == status)
            count_query = count_query.where(Notification.status == status)

        total_res = await db.execute(count_query)
        total = total_res.scalar() or 0

        query = (
            query.options(
                selectinload(Notification.patient),
                selectinload(Notification.order),
                selectinload(Notification.report),
            )
            .order_by(Notification.created_at.desc())
            .offset(offset)
            .limit(limit)
        )
        result = await db.execute(query)
        items = list(result.scalars().all())

        return items, total

    async def update_status(
        self,
        db: AsyncSession,
        notification: Notification,
        status: NotificationStatus,
        sent_at: datetime | None = None,
        error_message: str | None = None,
    ) -> Notification:
        """Update notification dispatch status, timestamp, and optional failure reason."""
        notification.status = status
        if sent_at is not None:
            notification.sent_at = sent_at
        if error_message is not None:
            notification.error_message = error_message
        await db.flush()
        await db.refresh(notification)
        return notification
