from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from typing import List, Optional
from datetime import datetime, date
from decimal import Decimal
from . import repository
from .schema import (
    FareclassCreate,
    FareclassUpdate,
    FareclassResponse,
    FlightCreate,
    FlightUpdate,
    FlightResponse,
    FlightDetailResponse,
    FlightSearchRequest,
    FlightStatusUpdateRequest,
    PassengerManifestResponse,
)
from aircraft_management import repository as aircraft_repo
from airport_route_management import repository as route_repo


def _get_or_raise(db: Session, entity_id: str, repo_module) -> any:
    entity = repo_module.get_by_id(db, entity_id)
    if not entity:
        raise HTTPException(status_code=404, detail=f"Entity with id {entity_id} not found")
    return entity


# Fareclass handlers
def create_fareclass(db: Session, data: FareclassCreate) -> FareclassResponse:
    existing = repository.get_fareclass_by_name(db, data.name)
    if existing:
        raise HTTPException(status_code=409, detail=f"Fare class with name '{data.name}' already exists")
    
    try:
        fareclass = repository.create(db, data.model_dump())
        db.commit()
        db.refresh(fareclass)
        return FareclassResponse.model_validate(fareclass)
    except Exception:
        db.rollback()
        raise


def list_fareclasses(
    db: Session,
    limit: int = 20,
    offset: int = 0,
    search: Optional[str] = None,
    class_type: Optional[str] = None,
) -> List[FareclassResponse]:
    filters = {}
    if search:
        filters["search"] = search
    if class_type:
        filters["class_type"] = class_type
    
    fareclasses = repository.list_all(db, limit, offset, **filters)
    return [FareclassResponse.model_validate(fc) for fc in fareclasses]


def get_fareclass(db: Session, fareclass_id: str) -> FareclassResponse:
    fareclass = _get_or_raise(db, fareclass_id, repository)
    return FareclassResponse.model_validate(fareclass)


def update_fareclass(db: Session, fareclass_id: str, data: FareclassUpdate) -> FareclassResponse:
    fareclass = _get_or_raise(db, fareclass_id, repository)
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "name" in update_data and update_data["name"] != fareclass.name:
        existing = repository.get_fareclass_by_name(db, update_data["name"])
        if existing:
            raise HTTPException(status_code=409, detail=f"Fare class with name '{update_data['name']}' already exists")
    
    try:
        updated_fareclass = repository.update(db, fareclass_id, update_data)
        db.commit()
        db.refresh(updated_fareclass)
        return FareclassResponse.model_validate(updated_fareclass)
    except Exception:
        db.rollback()
        raise


def delete_fareclass(db: Session, fareclass_id: str) -> dict:
    fareclass = _get_or_raise(db, fareclass_id, repository)
    
    try:
        repository.delete(db, fareclass_id)
        db.commit()
        return {"message": "Fare class deleted successfully"}
    except Exception:
        db.rollback()
        raise


# Flight handlers
def create_flight(db: Session, data: FlightCreate) -> FlightResponse:
    route = route_repo.get_by_id(db, data.route_id)
    if not route:
        raise HTTPException(status_code=404, detail=f"Route with id {data.route_id} not found")
    
    aircraft = aircraft_repo.get_by_id(db, data.aircraft_id)
    if not aircraft:
        raise HTTPException(status_code=404, detail=f"Aircraft with id {data.aircraft_id} not found")
    
    if aircraft.status != "Active":
        raise HTTPException(status_code=400, detail="Aircraft must be in Active status to be assigned to a flight")
    
    if data.scheduled_departure <= datetime.utcnow():
        raise HTTPException(status_code=400, detail="Flight departure time must be in the future")
    
    if data.scheduled_arrival <= data.scheduled_departure:
        raise HTTPException(status_code=400, detail="Flight arrival time must be after departure time")
    
    try:
        flight = repository.create_flight(db, data.model_dump())
        db.commit()
        db.refresh(flight)
        return FlightResponse.model_validate(flight)
    except Exception:
        db.rollback()
        raise


def list_flights(
    db: Session,
    limit: int = 20,
    offset: int = 0,
    status: Optional[str] = None,
    aircraft_id: Optional[str] = None,
    route_id: Optional[str] = None,
    date: Optional[date] = None,
) -> List[FlightResponse]:
    filters = {}
    if status:
        filters["status"] = status
    if aircraft_id:
        filters["aircraft_id"] = aircraft_id
    if route_id:
        filters["route_id"] = route_id
    if date:
        filters["date"] = date
    
    flights = repository.list_flights(db, limit, offset, **filters)
    return [FlightResponse.model_validate(f) for f in flights]


def get_flight(db: Session, flight_id: str) -> FlightResponse:
    flight = _get_or_raise(db, flight_id, repository)
    return FlightResponse.model_validate(flight)


def update_flight(db: Session, flight_id: str, data: FlightUpdate) -> FlightResponse:
    flight = repository.get_flight_by_id(db, flight_id)
    if not flight:
        raise HTTPException(status_code=404, detail=f"Flight with id {flight_id} not found")
    
    if flight.status in ["Departed", "Arrived"]:
        raise HTTPException(status_code=400, detail="Flights cannot be edited after status is departed or arrived")
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "route_id" in update_data:
        route = route_repo.get_by_id(db, update_data["route_id"])
        if not route:
            raise HTTPException(status_code=404, detail=f"Route with id {update_data['route_id']} not found")
    
    if "aircraft_id" in update_data:
        aircraft = aircraft_repo.get_by_id(db, update_data["aircraft_id"])
        if not aircraft:
            raise HTTPException(status_code=404, detail=f"Aircraft with id {update_data['aircraft_id']} not found")
        if aircraft.status != "Active":
            raise HTTPException(status_code=400, detail="Aircraft must be in Active status")
    
    if "scheduled_arrival" in update_data or "scheduled_departure" in update_data:
        departure = update_data.get("scheduled_departure", flight.scheduled_departure)
        arrival = update_data.get("scheduled_arrival", flight.scheduled_arrival)
        if arrival <= departure:
            raise HTTPException(status_code=400, detail="Flight arrival time must be after departure time")
    
    try:
        updated_flight = repository.update_flight(db, flight_id, update_data)
        db.commit()
        db.refresh(updated_flight)
        return FlightResponse.model_validate(updated_flight)
    except Exception:
        db.rollback()
        raise


def delete_flight(db: Session, flight_id: str) -> dict:
    flight = _get_or_raise(db, flight_id, repository)
    
    try:
        repository.delete_flight(db, flight_id)
        db.commit()
        return {"message": "Flight deleted successfully"}
    except Exception:
        db.rollback()
        raise


def get_flight_details(db: Session, flight_id: str) -> FlightDetailResponse:
    flight = repository.get_flight_with_details(db, flight_id)
    if not flight:
        raise HTTPException(status_code=404, detail=f"Flight with id {flight_id} not found")
    
    return FlightDetailResponse.model_validate(flight)


def search_flights(db: Session, search_params: FlightSearchRequest) -> List[FlightResponse]:
    flights = repository.search_flights(db, search_params.origin, search_params.destination, search_params.date)
    return [FlightResponse.model_validate(f) for f in flights]


def update_flight_status(db: Session, flight_id: str, status_data: FlightStatusUpdateRequest) -> FlightResponse:
    flight = repository.get_flight_by_id(db, flight_id)
    if not flight:
        raise HTTPException(status_code=404, detail=f"Flight with id {flight_id} not found")
    
    new_status = status_data.status.value
    
    update_data = {"status": new_status}
    
    if new_status == "Departed" and not flight.actual_departure:
        update_data["actual_departure"] = datetime.utcnow()
    elif new_status == "Arrived" and not flight.actual_arrival:
        update_data["actual_arrival"] = datetime.utcnow()
    
    try:
        updated_flight = repository.update_flight(db, flight_id, update_data)
        db.commit()
        db.refresh(updated_flight)
        return FlightResponse.model_validate(updated_flight)
    except Exception:
        db.rollback()
        raise


def cancel_flight_and_bookings(db: Session, flight_id: str) -> FlightResponse:
    from booking_management.models import Booking
    
    flight = repository.get_flight_by_id(db, flight_id)
    if not flight:
        raise HTTPException(status_code=404, detail=f"Flight with id {flight_id} not found")
    
    if flight.status in ["Departed", "Arrived", "Cancelled"]:
        raise HTTPException(status_code=400, detail=f"Cannot cancel flight with status {flight.status}")
    
    try:
        bookings = db.query(Booking).filter(Booking.flight_id == flight_id).all()
        
        for booking in bookings:
            booking.booking_status = "Cancelled"
        
        flight.status = "Cancelled"
        
        db.commit()
        db.refresh(flight)
        return FlightResponse.model_validate(flight)
    except Exception:
        db.rollback()
        raise


def get_passenger_manifest(db: Session, flight_id: str) -> List[PassengerManifestResponse]:
    from booking_management.models import Booking, Passenger
    from .models import Fareclass
    from aircraft_management.models import Seat
    
    flight = repository.get_flight_by_id(db, flight_id)
    if not flight:
        raise HTTPException(status_code=404, detail=f"Flight with id {flight_id} not found")
    
    bookings = (
        db.query(Booking)
        .filter(Booking.flight_id == flight_id)
        .options(
            joinedload(Booking.passenger),
            joinedload(Booking.seat),
            joinedload(Booking.fare_class)
        )
        .all()
    )
    
    manifest = []
    for booking in bookings:
        manifest.append(
            PassengerManifestResponse(
                booking_reference=booking.booking_reference,
                passenger_first_name=booking.passenger.first_name,
                passenger_last_name=booking.passenger.last_name,
                seat_number=booking.seat.seat_number if booking.seat else None,
                booking_status=booking.booking_status,
                fare_class_name=booking.fare_class.name,
            )
        )
    
    return manifest