"""Reservation service layer for handling reservation database operations."""

import hashlib
from datetime import UTC, datetime
from typing import Optional

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

from .db import AckedRequest, Customer, Reservation
from .schemas import ReservationData


class ReservationService:
    """Service for managing reservations and related operations.

    Handles all database operations for reservations including creation,
    retrieval, and acknowledgement tracking.
    """

    def __init__(self, session: AsyncSession):
        self.session = session

    def _convert_reservation_data_to_db(
        self, reservation_model: ReservationData, customer_id: int
    ) -> Reservation:
        """Convert ReservationData to Reservation model.

        Args:
            reservation_model: ReservationData instance
            customer_id: Customer ID to link to

        Returns:
            Reservation instance ready for database insertion
        """
        data = reservation_model.model_dump(exclude_none=True)

        # Convert children_ages list to CSV string
        children_list = data.pop("children_ages", [])
        children_csv = (
            ",".join(str(int(a)) for a in children_list) if children_list else ""
        )
        data["children_ages"] = children_csv

        # Inject foreign key
        data["customer_id"] = customer_id

        return Reservation(**data)

    async def create_reservation(
        self, reservation_data: ReservationData, customer_id: int
    ) -> Reservation:
        """Create a new reservation.

        Args:
            reservation_data: ReservationData containing reservation details
            customer_id: ID of the customer making the reservation

        Returns:
            Created Reservation instance
        """
        reservation = self._convert_reservation_data_to_db(
            reservation_data, customer_id
        )
        self.session.add(reservation)
        await self.session.commit()
        await self.session.refresh(reservation)
        return reservation

    async def get_reservation_by_unique_id(
        self, unique_id: str
    ) -> Optional[Reservation]:
        """Get a reservation by unique_id.

        Args:
            unique_id: The unique_id to search for

        Returns:
            Reservation instance if found, None otherwise
        """
        result = await self.session.execute(
            select(Reservation).where(Reservation.unique_id == unique_id)
        )
        return result.scalar_one_or_none()

    async def get_reservation_by_md5_unique_id(
        self, md5_unique_id: str
    ) -> Optional[Reservation]:
        """Get a reservation by md5_unique_id.

        Args:
            md5_unique_id: The MD5 hash of unique_id

        Returns:
            Reservation instance if found, None otherwise
        """
        result = await self.session.execute(
            select(Reservation).where(
                Reservation.md5_unique_id == md5_unique_id
            )
        )
        return result.scalar_one_or_none()

    async def check_duplicate_reservation(
        self, unique_id: str, md5_unique_id: str
    ) -> bool:
        """Check if a reservation already exists.

        Args:
            unique_id: The unique_id to check
            md5_unique_id: The MD5 hash to check

        Returns:
            True if reservation exists, False otherwise
        """
        existing = await self.get_reservation_by_unique_id(unique_id)
        if existing:
            return True

        existing_md5 = await self.get_reservation_by_md5_unique_id(md5_unique_id)
        return existing_md5 is not None

    async def get_reservations_for_customer(
        self, customer_id: int
    ) -> list[Reservation]:
        """Get all reservations for a customer.

        Args:
            customer_id: The customer ID

        Returns:
            List of Reservation instances
        """
        result = await self.session.execute(
            select(Reservation).where(Reservation.customer_id == customer_id)
        )
        return list(result.scalars().all())

    async def get_reservations_with_filters(
        self,
        start_date: Optional[datetime] = None,
        end_date: Optional[datetime] = None,
        hotel_code: Optional[str] = None,
    ) -> list[tuple[Reservation, Customer]]:
        """Get reservations with optional filters, joined with customers.

        Args:
            start_date: Filter by created_at >= this value
            end_date: Filter by created_at <= this value
            hotel_code: Filter by hotel code

        Returns:
            List of (Reservation, Customer) tuples
        """
        query = select(Reservation, Customer).join(
            Customer, Reservation.customer_id == Customer.id
        )

        filters = []
        if start_date:
            filters.append(Reservation.created_at >= start_date)
        if end_date:
            filters.append(Reservation.created_at <= end_date)
        if hotel_code:
            filters.append(Reservation.hotel_code == hotel_code)

        if filters:
            query = query.where(and_(*filters))

        result = await self.session.execute(query)
        return list(result.all())

    async def get_unacknowledged_reservations(
        self,
        start_date: Optional[datetime] = None,
        end_date: Optional[datetime] = None,
        hotel_code: Optional[str] = None,
        username: Optional[str] = None,
        client_id: Optional[str] = None,
    ) -> list[tuple[Reservation, Customer]]:
        """Get reservations that haven't been acknowledged by a client.

        Prioritizes checking by username if provided, falls back to client_id for backward compatibility.

        Args:
            start_date: Filter by start date >= this value
            end_date: Filter by end date <= this value
            hotel_code: Filter by hotel code
            username: The username of the client (preferred for lookup)
            client_id: The client ID (fallback for backward compatibility)

        Returns:
            List of (Reservation, Customer) tuples that are unacknowledged
        """
        # Get all acknowledged unique_ids for this client/username
        if username:
            acked_result = await self.session.execute(
                select(AckedRequest.unique_id).where(
                    AckedRequest.username == username
                )
            )
        else:
            acked_result = await self.session.execute(
                select(AckedRequest.unique_id).where(
                    AckedRequest.client_id == client_id
                )
            )
        acked_md5_ids = {row[0] for row in acked_result.all()}

        # Get all reservations with filters
        all_reservations = await self.get_reservations_with_filters(
            start_date, end_date, hotel_code
        )

        # Filter out acknowledged ones (comparing md5_unique_id)
        return [
            (res, cust)
            for res, cust in all_reservations
            if res.md5_unique_id not in acked_md5_ids
        ]

    async def record_acknowledgement(
        self, client_id: str, unique_id: str, username: Optional[str] = None
    ) -> AckedRequest:
        """Record that a client has acknowledged a reservation.

        Args:
            client_id: The client ID
            unique_id: The unique_id of the reservation (md5_unique_id)
            username: The username of the client making the request (optional)

        Returns:
            Created AckedRequest instance
        """
        acked = AckedRequest(
            client_id=client_id,
            username=username,
            unique_id=unique_id,
            timestamp=datetime.now(UTC),
        )
        self.session.add(acked)
        await self.session.commit()
        await self.session.refresh(acked)
        return acked

    async def is_acknowledged(self, unique_id: str, username: Optional[str] = None, client_id: Optional[str] = None) -> bool:
        """Check if a reservation has been acknowledged by a client.

        Prioritizes checking by username if provided, falls back to client_id for backward compatibility.

        Args:
            unique_id: The reservation unique_id
            username: The username of the client (preferred for lookup)
            client_id: The client ID (fallback for backward compatibility)

        Returns:
            True if acknowledged, False otherwise
        """
        if username:
            result = await self.session.execute(
                select(AckedRequest).where(
                    and_(
                        AckedRequest.username == username,
                        AckedRequest.unique_id == unique_id,
                    )
                )
            )
        else:
            result = await self.session.execute(
                select(AckedRequest).where(
                    and_(
                        AckedRequest.client_id == client_id,
                        AckedRequest.unique_id == unique_id,
                    )
                )
            )
        return result.scalar_one_or_none() is not None

    @staticmethod
    def generate_md5_unique_id(unique_id: str) -> str:
        """Generate MD5 hash of unique_id.

        Args:
            unique_id: The unique_id to hash

        Returns:
            MD5 hash as hex string
        """
        return hashlib.md5(unique_id.encode("utf-8")).hexdigest()
