from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from typing import Optional, List
from datetime import datetime
import random
import string
from . import repository
from .schema import (
    PassengerCreate, PassengerUpdate, PassengerResponse,
    BookingCreate, BookingUpdate, BookingResponse,
    BookingDetailResponse, PassengerDetailResponse,
    BookingListItemResponse
)
from .models import Passenger, Booking
from flight_management.models import Flight
from flight_management import repository as flight_repo
from aircraft_management.models import Seat
from aircraft_management import repository as aircraft_repo


def _get_or_raise(db: Session, entity_id: str, repo_module, model_class, entity_name: str):
    entity = repo_module.get_by_id(db, entity_id, model_class)
    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 _generate_booking_reference() -> str:
    return ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))


# Passenger handlers
def create_passenger(db: Session, data: PassengerCreate) -> PassengerResponse:
    existing = repository.get_passenger_by_email(db, data.email)
    if existing:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Passenger with email {data.email} already exists"
        )
    
    try:
        passenger = repository.create(db, data.model_dump(), Passenger)
        db.commit()
        db.refresh(passenger)
        return PassengerResponse.model_validate(passenger)
    except Exception:
        db.rollback()
        raise


def get_passenger(db: Session, passenger_id: str) -> PassengerResponse:
    passenger = _get_or_raise(db, passenger_id, repository, Passenger, "Passenger")
    return PassengerResponse.model_validate(passenger)


def list_passengers(
    db: Session,
    limit: int = 20,
    offset: int = 0,
    search: Optional[str] = None
) -> List[PassengerResponse]:
    passengers = repository.list_all(
        db, Passenger, limit=limit, offset=offset, search=search
    )
    return [PassengerResponse.model_validate(p) for p in passengers]


def update_passenger(db: Session, passenger_id: str, data: PassengerUpdate) -> PassengerResponse:
    passenger = _get_or_raise(db, passenger_id, repository, Passenger, "Passenger")
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "email" in update_data and update_data["email"] != passenger.email:
        existing = repository.get_passenger_by_email(db, update_data["email"])
        if existing:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail=f"Passenger with email {update_data['email']} already exists"
            )
    
    try:
        updated = repository.update(db, passenger_id, update_data, Passenger)
        db.commit()
        db.refresh(updated)
        return PassengerResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


def delete_passenger(db: Session, passenger_id: str) -> dict:
    passenger = _get_or_raise(db, passenger_id, repository, Passenger, "Passenger")
    
    bookings = repository.list_all(db, Booking, limit=1, offset=0, passenger_id=passenger_id)
    if bookings:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot delete passenger with existing bookings"
        )
    
    try:
        repository.delete(db, passenger_id, Passenger)
        db.commit()
        return {"message": "Passenger deleted successfully"}
    except Exception:
        db.rollback()
        raise


def get_passenger_details(db: Session, passenger_id: str) -> PassengerDetailResponse:
    passenger = repository.get_passenger_with_details(db, passenger_id)
    if not passenger:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Passenger with id {passenger_id} not found"
        )
    
    booking_items = []
    for booking in passenger.bookings:
        booking_items.append(BookingListItemResponse(
            booking_reference=booking.booking_reference,
            booking_status=booking.booking_status,
            total_price=booking.total_price,
            flight_number=booking.flight.flight_number,
            scheduled_departure=booking.flight.scheduled_departure,
            origin_code=booking.flight.route.origin_airport.code,
            destination_code=booking.flight.route.destination_airport.code
        ))
    
    return PassengerDetailResponse(
        id=passenger.id,
        first_name=passenger.first_name,
        last_name=passenger.last_name,
        email=passenger.email,
        phone=passenger.phone,
        date_of_birth=passenger.date_of_birth,
        passport_number=passenger.passport_number,
        nationality=passenger.nationality,
        bookings=booking_items,
        created_at=passenger.created_at,
        updated_at=passenger.updated_at
    )


# Booking handlers
def create_booking(db: Session, data: BookingCreate) -> BookingResponse:
    _get_or_raise(db, data.passenger_id, repository, Passenger, "Passenger")
    
    flight = _get_or_raise(db, data.flight_id, flight_repo, Flight, "Flight")
    if flight.status != "Scheduled":
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Bookings can only be made for flights with status Scheduled"
        )
    
    if flight.scheduled_departure < datetime.utcnow():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Bookings cannot be made for flights in the past"
        )
    
    from flight_management.models import Fareclass
    from flight_management import repository as fareclass_repo
    _get_or_raise(db, data.fare_class_id, fareclass_repo, Fareclass, "Fare class")
    
    if data.seat_id:
        seat = _get_or_raise(db, data.seat_id, aircraft_repo, Seat, "Seat")
        if seat.aircraft_id != flight.aircraft_id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Seat does not belong to the flight's aircraft"
            )
        
        existing_booking = repository.get_booking_by_seat_and_flight(db, data.seat_id, data.flight_id)
        if existing_booking:
            raise HTTPException(
                status_code=status.HTTP_409_CONFLICT,
                detail="Seat is already booked for this flight"
            )
    
    booking_count = len(repository.get_bookings_by_flight_id(db, data.flight_id))
    from aircraft_management.models import Aircraft
    aircraft = db.query(Aircraft).filter(Aircraft.id == flight.aircraft_id).first()
    if booking_count >= aircraft.total_seats:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Flight is fully booked"
        )
    
    booking_reference = _generate_booking_reference()
    while repository.get_booking_by_reference(db, booking_reference):
        booking_reference = _generate_booking_reference()
    
    try:
        booking_data = data.model_dump()
        booking_data["booking_reference"] = booking_reference
        booking = repository.create(db, booking_data, Booking)
        db.commit()
        db.refresh(booking)
        return BookingResponse.model_validate(booking)
    except Exception:
        db.rollback()
        raise


def get_booking(db: Session, booking_id: str) -> BookingResponse:
    booking = _get_or_raise(db, booking_id, repository, Booking, "Booking")
    return BookingResponse.model_validate(booking)


def list_bookings(
    db: Session,
    limit: int = 20,
    offset: int = 0,
    search: Optional[str] = None,
    booking_status: Optional[str] = None,
    payment_status: Optional[str] = None,
    passenger_id: Optional[str] = None,
    flight_id: Optional[str] = None
) -> List[BookingResponse]:
    bookings = repository.list_all(
        db, Booking, limit=limit, offset=offset,
        search=search, booking_status=booking_status,
        payment_status=payment_status, passenger_id=passenger_id,
        flight_id=flight_id
    )
    return [BookingResponse.model_validate(b) for b in bookings]


def update_booking(db: Session, booking_id: str, data: BookingUpdate) -> BookingResponse:
    booking = _get_or_raise(db, booking_id, repository, Booking, "Booking")
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "passenger_id" in update_data:
        _get_or_raise(db, update_data["passenger_id"], repository, Passenger, "Passenger")
    
    if "flight_id" in update_data:
        flight = _get_or_raise(db, update_data["flight_id"], flight_repo, Flight, "Flight")
        if flight.status != "Scheduled":
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Can only change to flights with status Scheduled"
            )
    
    if "fare_class_id" in update_data:
        from flight_management.models import Fareclass
        from flight_management import repository as fareclass_repo
        _get_or_raise(db, update_data["fare_class_id"], fareclass_repo, Fareclass, "Fare class")
    
    if "seat_id" in update_data and update_data["seat_id"] is not None:
        flight_id = update_data.get("flight_id", booking.flight_id)
        flight = db.query(Flight).filter(Flight.id == flight_id).first()
        
        seat = _get_or_raise(db, update_data["seat_id"], aircraft_repo, Seat, "Seat")
        if seat.aircraft_id != flight.aircraft_id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Seat does not belong to the flight's aircraft"
            )
        
        if update_data["seat_id"] != booking.seat_id:
            existing_booking = repository.get_booking_by_seat_and_flight(db, update_data["seat_id"], flight_id)
            if existing_booking and existing_booking.id != booking_id:
                raise HTTPException(
                    status_code=status.HTTP_409_CONFLICT,
                    detail="Seat is already booked for this flight"
                )
    
    try:
        updated = repository.update(db, booking_id, update_data, Booking)
        db.commit()
        db.refresh(updated)
        return BookingResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


def delete_booking(db: Session, booking_id: str) -> dict:
    _get_or_raise(db, booking_id, repository, Booking, "Booking")
    
    try:
        repository.delete(db, booking_id, Booking)
        db.commit()
        return {"message": "Booking deleted successfully"}
    except Exception:
        db.rollback()
        raise


def get_booking_details(db: Session, booking_id: str) -> BookingDetailResponse:
    booking = repository.get_booking_with_details(db, booking_id)
    if not booking:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Booking with id {booking_id} not found"
        )
    
    return BookingDetailResponse.model_validate(booking)


def cancel_booking(db: Session, booking_id: str) -> BookingResponse:
    booking = _get_or_raise(db, booking_id, repository, Booking, "Booking")
    
    if booking.booking_status == "Cancelled":
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Booking is already cancelled"
        )
    
    if booking.booking_status in ["Checked In", "Boarded"]:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Cannot cancel booking with status {booking.booking_status}"
        )
    
    try:
        update_data = {
            "booking_status": "Cancelled",
            "payment_status": "Refunded" if booking.payment_status == "Completed" else booking.payment_status
        }
        updated = repository.update(db, booking_id, update_data, Booking)
        db.commit()
        db.refresh(updated)
        return BookingResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


def change_booking_seat(db: Session, booking_id: str, new_seat_id: str) -> BookingResponse:
    booking = _get_or_raise(db, booking_id, repository, Booking, "Booking")
    
    if booking.booking_status not in ["Confirmed", "Pending"]:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Cannot change seat for booking with status {booking.booking_status}"
        )
    
    flight = db.query(Flight).filter(Flight.id == booking.flight_id).first()
    seat = _get_or_raise(db, new_seat_id, aircraft_repo, Seat, "Seat")
    
    if seat.aircraft_id != flight.aircraft_id:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Seat does not belong to the flight's aircraft"
        )
    
    existing_booking = repository.get_booking_by_seat_and_flight(db, new_seat_id, booking.flight_id)
    if existing_booking and existing_booking.id != booking_id:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail="Seat is already booked for this flight"
        )
    
    try:
        update_data = {"seat_id": new_seat_id}
        updated = repository.update(db, booking_id, update_data, Booking)
        db.commit()
        db.refresh(updated)
        return BookingResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


def change_booking_flight(db: Session, booking_id: str, new_flight_id: str) -> BookingResponse:
    booking = _get_or_raise(db, booking_id, repository, Booking, "Booking")
    
    if booking.booking_status not in ["Confirmed", "Pending"]:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Cannot change flight for booking with status {booking.booking_status}"
        )
    
    new_flight = _get_or_raise(db, new_flight_id, flight_repo, Flight, "Flight")
    
    if new_flight.status != "Scheduled":
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Can only change to flights with status Scheduled"
        )
    
    if new_flight.scheduled_departure < datetime.utcnow():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Cannot change to flights in the past"
        )
    
    booking_count = len(repository.get_bookings_by_flight_id(db, new_flight_id))
    from aircraft_management.models import Aircraft
    aircraft = db.query(Aircraft).filter(Aircraft.id == new_flight.aircraft_id).first()
    if booking_count >= aircraft.total_seats:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="New flight is fully booked"
        )
    
    try:
        update_data = {
            "flight_id": new_flight_id,
            "seat_id": None
        }
        updated = repository.update(db, booking_id, update_data, Booking)
        db.commit()
        db.refresh(updated)
        return BookingResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise