"""Appointment repositories — Phase 13.1."""
from __future__ import annotations

from datetime import datetime
from uuid import UUID

from sqlalchemy import and_, func, or_, select

from app.models.appointments import (
    Appointment,
    AppointmentType,
    AvailabilityWindow,
    Schedule,
    WaitingListEntry,
)
from app.models.types import AppointmentStatus
from app.repositories.base import TenantBaseRepository
from app.specifications.appointments import AppointmentListSpec


class ScheduleRepository(TenantBaseRepository[Schedule]):
    model = Schedule

    async def get_by_code(self, tenant_id: UUID, code: str) -> Schedule | None:
        stmt = select(Schedule).where(
            Schedule.tenant_id == tenant_id,
            Schedule.code == code,
            Schedule.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()


class AvailabilityWindowRepository(TenantBaseRepository[AvailabilityWindow]):
    model = AvailabilityWindow

    async def get_by_code(self, tenant_id: UUID, code: str) -> AvailabilityWindow | None:
        stmt = select(AvailabilityWindow).where(
            AvailabilityWindow.tenant_id == tenant_id,
            AvailabilityWindow.code == code,
            AvailabilityWindow.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()


class AppointmentTypeRepository(TenantBaseRepository[AppointmentType]):
    model = AppointmentType

    async def get_by_code(self, tenant_id: UUID, code: str) -> AppointmentType | None:
        stmt = select(AppointmentType).where(
            AppointmentType.tenant_id == tenant_id,
            AppointmentType.code == code,
            AppointmentType.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()


class AppointmentRepository(TenantBaseRepository[Appointment]):
    model = Appointment

    async def get_by_code(self, tenant_id: UUID, code: str) -> Appointment | None:
        stmt = select(Appointment).where(
            Appointment.tenant_id == tenant_id,
            Appointment.code == code,
            Appointment.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()

    async def find_conflicts(
        self,
        tenant_id: UUID,
        *,
        doctor_id: UUID,
        scheduled_start: datetime,
        scheduled_end: datetime,
        exclude_id: UUID | None = None,
    ) -> list[Appointment]:
        clauses = [
            Appointment.tenant_id == tenant_id,
            Appointment.doctor_id == doctor_id,
            Appointment.is_deleted.is_(False),
            Appointment.status.not_in(
                [AppointmentStatus.CANCELLED, AppointmentStatus.NO_SHOW]
            ),
            Appointment.scheduled_start < scheduled_end,
            Appointment.scheduled_end > scheduled_start,
        ]
        if exclude_id is not None:
            clauses.append(Appointment.id != exclude_id)
        stmt = select(Appointment).where(and_(*clauses))
        result = await self.session.execute(stmt)
        return list(result.scalars().all())

    def _filter_clauses(self, tenant_id: UUID, spec: AppointmentListSpec | None):
        clauses = [
            Appointment.tenant_id == tenant_id,
            Appointment.is_deleted.is_(False),
        ]
        if spec is None:
            return clauses
        if spec.status is not None:
            clauses.append(Appointment.status == spec.status)
        if spec.doctor_id is not None:
            clauses.append(Appointment.doctor_id == spec.doctor_id)
        if spec.clinic_id is not None:
            clauses.append(Appointment.clinic_id == spec.clinic_id)
        if spec.patient_id is not None:
            clauses.append(Appointment.patient_id == spec.patient_id)
        if spec.date_from is not None:
            clauses.append(Appointment.scheduled_start >= spec.date_from)
        if spec.date_to is not None:
            clauses.append(Appointment.scheduled_start <= spec.date_to)
        if spec.q:
            pattern = f"%{spec.q}%"
            clauses.append(or_(Appointment.code.ilike(pattern), Appointment.notes.ilike(pattern)))
        return clauses

    async def list_filtered(
        self,
        tenant_id: UUID,
        *,
        offset: int = 0,
        limit: int = 20,
        spec: AppointmentListSpec | None = None,
    ):
        clauses = self._filter_clauses(tenant_id, spec)
        sort_col = getattr(Appointment, spec.sort_by if spec else "scheduled_start", Appointment.scheduled_start)
        order = sort_col.asc() if spec and spec.sort_dir == "asc" else sort_col.desc()
        stmt = select(Appointment).where(and_(*clauses)).order_by(order).offset(offset).limit(limit)
        result = await self.session.execute(stmt)
        return result.scalars().all()

    async def count_filtered(self, tenant_id: UUID, *, spec: AppointmentListSpec | None = None) -> int:
        clauses = self._filter_clauses(tenant_id, spec)
        stmt = select(func.count()).select_from(Appointment).where(and_(*clauses))
        result = await self.session.execute(stmt)
        return int(result.scalar_one())


class WaitingListEntryRepository(TenantBaseRepository[WaitingListEntry]):
    model = WaitingListEntry

    async def get_by_code(self, tenant_id: UUID, code: str) -> WaitingListEntry | None:
        stmt = select(WaitingListEntry).where(
            WaitingListEntry.tenant_id == tenant_id,
            WaitingListEntry.code == code,
            WaitingListEntry.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()
