from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from typing import Optional, List
from datetime import datetime, timedelta
from . import repository
from .schema import (
    ReservationCreate,
    ReservationUpdate,
    ReservationResponse,
    ReservationDetailResponse,
)
from user_management.models import User
from schedule_management.models import Schedule, Seat
from schedule_management import repository as schedule_repo


def _get_or_raise(db: Session, entity_id: str, repo_module, entity_name: str):
    entity = repo_module.get_by_id(db, entity_id) if hasattr(repo_module, "get_by_id") else repo_module.get_reservation_by_id(db, entity_id)
    if not entity:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"{entity_name} with id {entity_id} not found",
        )
    return entity


def create_reservation(db: Session, data: ReservationCreate) -> ReservationResponse:
    user = db.query(User).filter(User.id == data.user_id).first()
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"User with id {data.user_id} not found",
        )

    schedule = db.query(Schedule).filter(Schedule.id == data.schedule_id).first()
    if not schedule:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Schedule with id {data.schedule_id} not found",
        )

    if schedule.status != "scheduled":
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Schedule is not available for reservation",
        )

    if schedule.departure_datetime <= datetime.utcnow():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot reserve seats for past or ongoing schedules",
        )

    existing_active = repository.get_active_reservation_by_user_schedule(
        db, data.user_id, data.schedule_id
    )
    if existing_active:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="User already has an active reservation for this schedule",
        )

    seat_ids = data.seat_ids
    seats = db.query(Seat).filter(Seat.id.in_(seat_ids)).all()
    if len(seats) != len(seat_ids):
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="One or more seat IDs are invalid",
        )

    for seat in seats:
        if seat.schedule_id != data.schedule_id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Seat {seat.seat_number} does not belong to the specified schedule",
            )
        if seat.status != "available":
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Seat {seat.seat_number} is not available",
            )

    total_amount = sum(schedule.base_price * (1 + seat.price_modifier) for seat in seats)

    reservation_data = {
        "user_id": data.user_id,
        "schedule_id": data.schedule_id,
        "reservation_datetime": data.reservation_datetime,
        "expiry_datetime": data.expiry_datetime,
        "status": data.status,
        "total_amount": total_amount,
    }

    reservation = repository.create_reservation(db, reservation_data)

    for seat in seats:
        repository.create_reservation_seat(
            db, {"reservation_id": reservation.id, "seat_id": seat.id}
        )
        seat.status = "reserved"

    schedule.available_seats -= len(seats)

    db.commit()
    db.refresh(reservation)

    return ReservationResponse.model_validate(reservation)


def get_reservation(db: Session, reservation_id: str) -> ReservationResponse:
    reservation = _get_or_raise(db, reservation_id, repository, "Reservation")
    return ReservationResponse.model_validate(reservation)


def get_reservation_details(db: Session, reservation_id: str) -> ReservationDetailResponse:
    reservation = repository.get_reservation_with_details(db, reservation_id)
    if not reservation:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Reservation with id {reservation_id} not found",
        )
    return ReservationDetailResponse.model_validate(reservation)


def list_reservations(
    db: Session,
    limit: int,
    offset: int,
    user_id: Optional[str] = None,
    schedule_id: Optional[str] = None,
    status: Optional[str] = None,
) -> List[ReservationResponse]:
    reservations = repository.list_reservations(
        db, limit, offset, user_id, schedule_id, status
    )
    return [ReservationResponse.model_validate(r) for r in reservations]


def update_reservation(
    db: Session, reservation_id: str, data: ReservationUpdate
) -> ReservationResponse:
    reservation = _get_or_raise(db, reservation_id, repository, "Reservation")

    update_data = data.model_dump(exclude_unset=True)

    if "user_id" in update_data:
        user = db.query(User).filter(User.id == update_data["user_id"]).first()
        if not user:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"User with id {update_data['user_id']} not found",
            )

    if "schedule_id" in update_data:
        schedule = db.query(Schedule).filter(Schedule.id == update_data["schedule_id"]).first()
        if not schedule:
            raise HTTPException(
                status_code=status.HTTP_404_NOT_FOUND,
                detail=f"Schedule with id {update_data['schedule_id']} not found",
            )

    updated_reservation = repository.update_reservation(db, reservation_id, update_data)
    db.commit()
    db.refresh(updated_reservation)

    return ReservationResponse.model_validate(updated_reservation)


def delete_reservation(db: Session, reservation_id: str) -> dict:
    reservation = _get_or_raise(db, reservation_id, repository, "Reservation")

    if reservation.status == "confirmed":
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot delete a confirmed reservation",
        )

    reservation_seats = repository.get_reservation_seats_by_reservation_id(db, reservation_id)
    seat_ids = [rs.seat_id for rs in reservation_seats]

    if seat_ids:
        seats = db.query(Seat).filter(Seat.id.in_(seat_ids)).all()
        for seat in seats:
            if seat.status == "reserved":
                seat.status = "available"

        schedule = db.query(Schedule).filter(Schedule.id == reservation.schedule_id).first()
        if schedule:
            schedule.available_seats += len(seats)

    success = repository.delete_reservation(db, reservation_id)
    if not success:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Reservation with id {reservation_id} not found",
        )

    db.commit()
    return {"message": "Reservation deleted successfully"}


def get_active_reservations(db: Session, limit: int, offset: int) -> List[ReservationResponse]:
    reservations = repository.list_reservations(
        db, limit, offset, status="active"
    )
    return [ReservationResponse.model_validate(r) for r in reservations]


def expire_reservations_batch(db: Session) -> dict:
    current_time = datetime.utcnow()
    expired_reservations = repository.get_expired_reservations(db, current_time)

    if not expired_reservations:
        return {"expired_count": 0, "message": "No reservations to expire"}

    expired_ids = [r.id for r in expired_reservations]

    reservation_seats = (
        db.query(repository.Reservationseat)
        .filter(repository.Reservationseat.reservation_id.in_(expired_ids))
        .all()
    )

    seat_ids = [rs.seat_id for rs in reservation_seats]

    if seat_ids:
        seats = db.query(Seat).filter(Seat.id.in_(seat_ids)).all()
        seats_by_id = {s.id: s for s in seats}

        schedule_ids = list({s.schedule_id for s in seats})
        schedules = db.query(Schedule).filter(Schedule.id.in_(schedule_ids)).all()
        schedules_by_id = {sch.id: sch for sch in schedules}

        from collections import defaultdict
        seats_by_schedule = defaultdict(int)

        for seat in seats:
            if seat.status == "reserved":
                seat.status = "available"
                seats_by_schedule[seat.schedule_id] += 1

        for schedule_id, count in seats_by_schedule.items():
            schedule = schedules_by_id.get(schedule_id)
            if schedule:
                schedule.available_seats += count

    for reservation in expired_reservations:
        reservation.status = "expired"

    db.commit()

    return {
        "expired_count": len(expired_reservations),
        "message": f"Successfully expired {len(expired_reservations)} reservations",
    }


def release_reservation_manually(db: Session, reservation_id: str) -> dict:
    reservation = _get_or_raise(db, reservation_id, repository, "Reservation")

    if reservation.status != "active":
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Only active reservations can be manually released",
        )

    reservation_seats = repository.get_reservation_seats_by_reservation_id(db, reservation_id)
    seat_ids = [rs.seat_id for rs in reservation_seats]

    if seat_ids:
        seats = db.query(Seat).filter(Seat.id.in_(seat_ids)).all()
        for seat in seats:
            if seat.status == "reserved":
                seat.status = "available"

        schedule = db.query(Schedule).filter(Schedule.id == reservation.schedule_id).first()
        if schedule:
            schedule.available_seats += len(seats)

    reservation.status = "cancelled"

    db.commit()
    db.refresh(reservation)

    return {
        "message": "Reservation released successfully",
        "reservation_id": reservation_id,
    }