"""Service scheduling — business logic."""
from __future__ import annotations

import logging
from datetime import date, datetime, timedelta, timezone
from typing import List, Optional, Tuple
from uuid import UUID

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

from src.apps.crew_members.models.crew_member import CrewMember
from src.apps.scheduling.models.service_crew_assignment import ServiceCrewAssignment
from src.apps.scheduling.models.service_event import ServiceEvent
from src.apps.scheduling.schemas.requests import ServiceCreate, ServiceUpdate
from src.core.exceptions import NotFoundError, ValidationError

logger = logging.getLogger(__name__)


class ServiceService:

    async def _validate_crew_ids(
        self,
        db: AsyncSession,
        tenant_id: UUID | str,
        crew_ids: List[UUID],
    ) -> None:
        """Reject any crew_member_id that isn't a live crew member of this tenant.

        Mirrors SchedulingService._validate_crew_ids so the legacy /services
        path can't associate cross-tenant crew (foreign/unknown -> 422)."""
        if not crew_ids:
            return
        unique_ids = list(set(crew_ids))
        rows = (
            await db.execute(
                select(CrewMember.id).where(
                    CrewMember.id.in_(unique_ids),
                    CrewMember.tenant_id == tenant_id,
                    CrewMember.deleted_at.is_(None),
                )
            )
        ).scalars().all()
        found = {str(r) for r in rows}
        missing = [str(cid) for cid in unique_ids if str(cid) not in found]
        if missing:
            raise ValidationError(
                message="One or more crew members are invalid for this tenant.",
                errors=[{"reason": "invalid_crew_member", "ids": missing}],
            )
    # ── Week calendar view ────────────────────────────────────────────────────

    async def get_week(
        self,
        db: AsyncSession,
        tenant_id: UUID | str,
        week_start: date,
        event_type: Optional[str] = None,
    ) -> List[ServiceEvent]:
        """Return all non-deleted services in a 7-day window from week_start."""
        week_end = week_start + timedelta(days=7)
        stmt = select(ServiceEvent).where(
            ServiceEvent.tenant_id == tenant_id,
            ServiceEvent.deleted_at.is_(None),
            ServiceEvent.scheduled_date >= week_start,
            ServiceEvent.scheduled_date < week_end,
        )
        if event_type and event_type != "all":
            stmt = stmt.where(ServiceEvent.service_type == event_type)
        stmt = stmt.order_by(ServiceEvent.scheduled_date, ServiceEvent.scheduled_time)
        result = await db.execute(stmt)
        return list(result.scalars().all())

    # ── Paginated summary table ───────────────────────────────────────────────

    async def get_summary_table(
        self,
        db: AsyncSession,
        tenant_id: UUID | str,
        week_start: date,
        page: int = 1,
        page_size: int = 20,
    ) -> Tuple[List[ServiceEvent], int]:
        """Paginated service list for the given week."""
        week_end = week_start + timedelta(days=7)
        base_filters = [
            ServiceEvent.tenant_id == tenant_id,
            ServiceEvent.deleted_at.is_(None),
            ServiceEvent.scheduled_date >= week_start,
            ServiceEvent.scheduled_date < week_end,
        ]

        count_stmt = (
            select(func.count())
            .select_from(ServiceEvent)
            .where(*base_filters)
        )
        total: int = (await db.execute(count_stmt)).scalar_one()

        offset = (page - 1) * page_size
        stmt = (
            select(ServiceEvent)
            .where(*base_filters)
            .order_by(ServiceEvent.scheduled_date, ServiceEvent.scheduled_time)
            .offset(offset)
            .limit(page_size)
        )
        result = await db.execute(stmt)
        return list(result.scalars().all()), total

    # ── Create ────────────────────────────────────────────────────────────────

    async def create(
        self,
        db: AsyncSession,
        tenant_id: UUID | str,
        data: ServiceCreate,
        current_user,
    ) -> ServiceEvent:
        """Create a service event and persist any crew assignments."""
        crew_ids = data.assigned_crew_ids or []
        await self._validate_crew_ids(db, tenant_id, crew_ids)
        service_data = data.model_dump(exclude={"assigned_crew_ids"})

        event = ServiceEvent(
            tenant_id=tenant_id,
            created_by=current_user.id,
            **service_data,
        )
        db.add(event)
        await db.flush()  # generate event.id

        for crew_id in crew_ids:
            assignment = ServiceCrewAssignment(
                tenant_id=tenant_id,
                service_id=event.id,
                crew_member_id=crew_id,
            )
            db.add(assignment)

        await db.flush()
        logger.info("[scheduling] Created service %s for tenant %s", event.id, tenant_id)
        return event

    # ── Get by ID ─────────────────────────────────────────────────────────────

    async def get_by_id(
        self,
        db: AsyncSession,
        tenant_id: UUID | str,
        service_id: UUID,
    ) -> ServiceEvent:
        """Fetch a single non-deleted service event, raise NotFoundError if missing."""
        stmt = select(ServiceEvent).where(
            ServiceEvent.id == service_id,
            ServiceEvent.tenant_id == tenant_id,
            ServiceEvent.deleted_at.is_(None),
        )
        event = (await db.execute(stmt)).scalar_one_or_none()
        if not event:
            raise NotFoundError("Service not found")
        return event

    # ── Get staff assignments ─────────────────────────────────────────────────

    async def get_assignments(
        self,
        db: AsyncSession,
        service_id: UUID,
    ) -> List[ServiceCrewAssignment]:
        """Return all crew assignments for a service."""
        stmt = select(ServiceCrewAssignment).where(
            ServiceCrewAssignment.service_id == service_id
        )
        result = await db.execute(stmt)
        return list(result.scalars().all())

    # ── Update ────────────────────────────────────────────────────────────────

    async def update(
        self,
        db: AsyncSession,
        tenant_id: UUID | str,
        service_id: UUID,
        data: ServiceUpdate,
        current_user,
    ) -> ServiceEvent:
        """Update a service event and optionally replace crew assignments."""
        event = await self.get_by_id(db, tenant_id, service_id)

        if data.assigned_crew_ids is not None:
            await self._validate_crew_ids(db, tenant_id, data.assigned_crew_ids)

        update_data = data.model_dump(exclude_unset=True, exclude={"assigned_crew_ids"})
        for key, value in update_data.items():
            setattr(event, key, value)

        if data.assigned_crew_ids is not None:
            # Replace all crew assignments
            await db.execute(
                delete(ServiceCrewAssignment).where(
                    ServiceCrewAssignment.service_id == event.id
                )
            )
            for crew_id in data.assigned_crew_ids:
                db.add(
                    ServiceCrewAssignment(
                        tenant_id=tenant_id,
                        service_id=event.id,
                        crew_member_id=crew_id,
                    )
                )

        await db.flush()
        logger.info("[scheduling] Updated service %s", service_id)
        return event

    # ── Soft delete ───────────────────────────────────────────────────────────

    async def soft_delete(
        self,
        db: AsyncSession,
        tenant_id: UUID | str,
        service_id: UUID,
        current_user,
    ) -> None:
        """Soft-delete a service event."""
        event = await self.get_by_id(db, tenant_id, service_id)
        event.deleted_at = datetime.now(timezone.utc)
        await db.flush()
        logger.info("[scheduling] Soft-deleted service %s", service_id)
