"""
Repository for Lab Schedule Config and Lab Holiday records.
"""

from datetime import UTC, date, datetime, timedelta
from uuid import UUID
from zoneinfo import ZoneInfo

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.appointment import AppointmentSlot, LabHoliday, LabScheduleConfig
from app.schemas.schedule import LabHolidayCreate, LabScheduleConfigUpdate

LAB_TZ = ZoneInfo("Asia/Kathmandu")


class ScheduleRepository:
    """Data access layer for lab operating hours and holiday closures."""

    async def get_or_create_config(self, db: AsyncSession, lab_id: UUID) -> LabScheduleConfig:
        """Fetch the schedule config for lab, creating default if not existing."""
        stmt = select(LabScheduleConfig).where(LabScheduleConfig.lab_id == lab_id)
        result = await db.execute(stmt)
        config = result.scalar_one_or_none()
        if not config:
            config = LabScheduleConfig(
                lab_id=lab_id,
                open_time="07:00",
                close_time="19:00",
                slot_duration_minutes=30,
                slot_capacity=2,
                operating_days="0,1,2,3,4,5,6",
                is_active=True,
            )
            db.add(config)
            await db.flush()
            await db.refresh(config)
        return config

    async def update_config(
        self, db: AsyncSession, config: LabScheduleConfig, data: LabScheduleConfigUpdate
    ) -> LabScheduleConfig:
        """Update existing lab schedule configuration."""
        for field, val in data.model_dump(exclude_unset=True).items():
            setattr(config, field, val)
        await db.flush()
        await db.refresh(config)
        return config

    async def list_holidays(
        self,
        db: AsyncSession,
        lab_id: UUID,
        from_date: date | None = None,
        to_date: date | None = None,
    ) -> list[LabHoliday]:
        """List active holidays for lab, ordered chronologically."""
        stmt = select(LabHoliday).where(
            LabHoliday.lab_id == lab_id,
            LabHoliday.deleted_at.is_(None),
        )
        if from_date:
            stmt = stmt.where(LabHoliday.end_date >= from_date)
        if to_date:
            stmt = stmt.where(LabHoliday.start_date <= to_date)
        stmt = stmt.order_by(LabHoliday.start_date.asc())
        result = await db.execute(stmt)
        return list(result.scalars().all())

    async def get_holiday_by_id(
        self, db: AsyncSession, holiday_id: UUID, lab_id: UUID | None = None
    ) -> LabHoliday | None:
        """Fetch holiday by ID."""
        stmt = select(LabHoliday).where(
            LabHoliday.id == holiday_id,
            LabHoliday.deleted_at.is_(None),
        )
        if lab_id is not None:
            stmt = stmt.where(LabHoliday.lab_id == lab_id)
        result = await db.execute(stmt)
        return result.scalar_one_or_none()

    async def find_holiday_for_date(
        self, db: AsyncSession, lab_id: UUID, target_date: date
    ) -> LabHoliday | None:
        """Find active holiday covering target date if any."""
        stmt = select(LabHoliday).where(
            LabHoliday.lab_id == lab_id,
            LabHoliday.deleted_at.is_(None),
            LabHoliday.start_date <= target_date,
            LabHoliday.end_date >= target_date,
        )
        result = await db.execute(stmt)
        return result.scalar_one_or_none()

    async def create_holiday(self, db: AsyncSession, lab_id: UUID, data: LabHolidayCreate) -> LabHoliday:
        """Create new holiday / closure period."""
        holiday = LabHoliday(
            lab_id=lab_id,
            start_date=data.start_date,
            end_date=data.end_date,
            title=data.title,
            notes=data.notes,
            is_closed=data.is_closed,
        )
        db.add(holiday)
        await db.flush()
        await db.refresh(holiday)

        if data.is_closed:
            # Deactivate in-range slots using lab-local day bounds (stored slots are UTC).
            range_start = datetime.combine(data.start_date, datetime.min.time(), tzinfo=LAB_TZ).astimezone(
                UTC
            )
            range_end = datetime.combine(
                data.end_date + timedelta(days=1), datetime.min.time(), tzinfo=LAB_TZ
            ).astimezone(UTC)
            slot_stmt = select(AppointmentSlot).where(
                AppointmentSlot.lab_id == lab_id,
                AppointmentSlot.deleted_at.is_(None),
                AppointmentSlot.start_at >= range_start,
                AppointmentSlot.start_at <= range_end,
            )
            slots_res = await db.execute(slot_stmt)
            for s in slots_res.scalars().all():
                s.is_active = False
            await db.flush()
        return holiday

    async def delete_holiday(self, db: AsyncSession, holiday: LabHoliday) -> None:
        """Soft delete a holiday."""
        holiday.deleted_at = datetime.now(UTC)
        await db.flush()
