from sqlalchemy.orm import Session
from fastapi import HTTPException
from typing import List, Optional
from datetime import datetime, timedelta
from . import repository
from .schema import (
    BookingCreate, BookingUpdate, BookingResponse,
    TravelerCreate, TravelerUpdate, TravelerResponse,
    TourscheduleCreate, TourscheduleUpdate, TourscheduleResponse,
    TourschedulehotelCreate, TourschedulehotelUpdate, TourschedulehotelResponse,
    TourscheduletransportationCreate, TourscheduletransportationUpdate, TourscheduletransportationResponse,
    BookingDetailResponse, TourScheduleFullDetailResponse
)
from user_management import repository as user_repo
from tour_catalog import repository as tour_repo
from service_provider_management import repository as service_repo
from payment_management import repository as payment_repo


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


# Booking handlers
def create_booking(db: Session, data: BookingCreate) -> BookingResponse:
    existing = repository.get_by_booking_number(db, data.booking_number)
    if existing:
        raise HTTPException(status_code=409, detail="Booking number already exists")
    
    customer = _get_or_raise(db, data.customer_id, user_repo)
    tour_schedule = _get_or_raise(db, data.tour_schedule_id, repository)
    
    if data.booking_agent_id:
        _get_or_raise(db, data.booking_agent_id, user_repo)
    
    if data.discount_id:
        _get_or_raise(db, data.discount_id, payment_repo)
    
    if tour_schedule.available_slots < (data.number_of_adults + data.number_of_children):
        raise HTTPException(status_code=400, detail="Not enough available slots for this booking")
    
    departure_datetime = datetime.combine(tour_schedule.departure_date, datetime.min.time())
    if departure_datetime - datetime.utcnow() < timedelta(hours=48):
        raise HTTPException(status_code=400, detail="Minimum booking lead time is 48 hours before departure")
    
    try:
        booking = repository.create(db, data.model_dump())
        db.commit()
        db.refresh(booking)
        return BookingResponse.model_validate(booking)
    except Exception:
        db.rollback()
        raise


def list_bookings(db: Session, limit: int, offset: int, customer_id: Optional[str] = None, 
                  tour_schedule_id: Optional[str] = None, booking_status: Optional[str] = None,
                  payment_status: Optional[str] = None) -> List[BookingResponse]:
    filters = {}
    if customer_id:
        filters["customer_id"] = customer_id
    if tour_schedule_id:
        filters["tour_schedule_id"] = tour_schedule_id
    if booking_status:
        filters["booking_status"] = booking_status
    if payment_status:
        filters["payment_status"] = payment_status
    
    bookings = repository.list_all(db, limit, offset, **filters)
    return [BookingResponse.model_validate(b) for b in bookings]


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


def update_booking(db: Session, booking_id: str, data: BookingUpdate) -> BookingResponse:
    booking = _get_or_raise(db, booking_id, repository)
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "booking_agent_id" in update_data and update_data["booking_agent_id"]:
        _get_or_raise(db, update_data["booking_agent_id"], user_repo)
    
    if "discount_id" in update_data and update_data["discount_id"]:
        _get_or_raise(db, update_data["discount_id"], payment_repo)
    
    try:
        updated = repository.update(db, booking_id, update_data)
        db.commit()
        db.refresh(updated)
        return BookingResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


def delete_booking(db: Session, booking_id: str) -> dict:
    booking = _get_or_raise(db, booking_id, repository)
    
    try:
        repository.delete(db, booking_id)
        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_with_details(db, booking_id)
    if not booking:
        raise HTTPException(status_code=404, detail="Booking not found")
    return BookingDetailResponse.model_validate(booking)


def confirm_booking(db: Session, booking_id: str) -> BookingResponse:
    booking = _get_or_raise(db, booking_id, repository)
    
    if booking.booking_status != "pending":
        raise HTTPException(status_code=400, detail="Only pending bookings can be confirmed")
    
    tour_schedule = _get_or_raise(db, booking.tour_schedule_id, repository)
    
    total_travelers = booking.number_of_adults + booking.number_of_children
    if tour_schedule.available_slots < total_travelers:
        raise HTTPException(status_code=400, detail="Not enough available slots")
    
    try:
        tour_schedule.booked_slots += total_travelers
        tour_schedule.available_slots -= total_travelers
        repository.update_tourschedule(db, tour_schedule.id, {
            "booked_slots": tour_schedule.booked_slots,
            "available_slots": tour_schedule.available_slots
        })
        
        updated = repository.update(db, booking_id, {"booking_status": "confirmed"})
        db.commit()
        db.refresh(updated)
        return BookingResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


# Traveler handlers
def create_traveler(db: Session, data: TravelerCreate) -> TravelerResponse:
    _get_or_raise(db, data.booking_id, repository)
    
    try:
        traveler = repository.create_traveler(db, data.model_dump())
        db.commit()
        db.refresh(traveler)
        return TravelerResponse.model_validate(traveler)
    except Exception:
        db.rollback()
        raise


def list_travelers(db: Session, limit: int, offset: int, booking_id: Optional[str] = None,
                   traveler_type: Optional[str] = None) -> List[TravelerResponse]:
    filters = {}
    if booking_id:
        filters["booking_id"] = booking_id
    if traveler_type:
        filters["traveler_type"] = traveler_type
    
    travelers = repository.list_travelers(db, limit, offset, **filters)
    return [TravelerResponse.model_validate(t) for t in travelers]


def get_traveler(db: Session, traveler_id: str) -> TravelerResponse:
    traveler = repository.get_traveler_by_id(db, traveler_id)
    if not traveler:
        raise HTTPException(status_code=404, detail="Traveler not found")
    return TravelerResponse.model_validate(traveler)


def update_traveler(db: Session, traveler_id: str, data: TravelerUpdate) -> TravelerResponse:
    traveler = repository.get_traveler_by_id(db, traveler_id)
    if not traveler:
        raise HTTPException(status_code=404, detail="Traveler not found")
    
    update_data = data.model_dump(exclude_unset=True)
    
    try:
        updated = repository.update_traveler(db, traveler_id, update_data)
        db.commit()
        db.refresh(updated)
        return TravelerResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


def delete_traveler(db: Session, traveler_id: str) -> dict:
    traveler = repository.get_traveler_by_id(db, traveler_id)
    if not traveler:
        raise HTTPException(status_code=404, detail="Traveler not found")
    
    try:
        repository.delete_traveler(db, traveler_id)
        db.commit()
        return {"message": "Traveler deleted successfully"}
    except Exception:
        db.rollback()
        raise


# Tourschedule handlers
def create_tourschedule(db: Session, data: TourscheduleCreate) -> TourscheduleResponse:
    _get_or_raise(db, data.tour_package_id, tour_repo)
    
    if data.tour_guide_id:
        _get_or_raise(db, data.tour_guide_id, user_repo)
    
    if data.return_date < data.departure_date:
        raise HTTPException(status_code=400, detail="Return date must be after departure date")
    
    if data.departure_date <= datetime.utcnow().date():
        raise HTTPException(status_code=400, detail="Departure date must be in the future")
    
    try:
        tourschedule = repository.create_tourschedule(db, data.model_dump())
        db.commit()
        db.refresh(tourschedule)
        return TourscheduleResponse.model_validate(tourschedule)
    except Exception:
        db.rollback()
        raise


def list_tourschedules(db: Session, limit: int, offset: int, tour_package_id: Optional[str] = None,
                       tour_guide_id: Optional[str] = None, status: Optional[str] = None) -> List[TourscheduleResponse]:
    filters = {}
    if tour_package_id:
        filters["tour_package_id"] = tour_package_id
    if tour_guide_id:
        filters["tour_guide_id"] = tour_guide_id
    if status:
        filters["status"] = status
    
    tourschedules = repository.list_tourschedules(db, limit, offset, **filters)
    return [TourscheduleResponse.model_validate(ts) for ts in tourschedules]


def get_tourschedule(db: Session, tourschedule_id: str) -> TourscheduleResponse:
    tourschedule = _get_or_raise(db, tourschedule_id, repository)
    return TourscheduleResponse.model_validate(tourschedule)


def update_tourschedule(db: Session, tourschedule_id: str, data: TourscheduleUpdate) -> TourscheduleResponse:
    tourschedule = _get_or_raise(db, tourschedule_id, repository)
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "tour_guide_id" in update_data and update_data["tour_guide_id"]:
        _get_or_raise(db, update_data["tour_guide_id"], user_repo)
    
    try:
        updated = repository.update_tourschedule(db, tourschedule_id, update_data)
        db.commit()
        db.refresh(updated)
        return TourscheduleResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


def delete_tourschedule(db: Session, tourschedule_id: str) -> dict:
    tourschedule = _get_or_raise(db, tourschedule_id, repository)
    
    try:
        repository.delete_tourschedule(db, tourschedule_id)
        db.commit()
        return {"message": "Tour schedule deleted successfully"}
    except Exception:
        db.rollback()
        raise


def get_tourschedule_details(db: Session, schedule_id: str) -> TourScheduleFullDetailResponse:
    schedule = repository.get_tourschedule_with_details(db, schedule_id)
    if not schedule:
        raise HTTPException(status_code=404, detail="Tour schedule not found")
    return TourScheduleFullDetailResponse.model_validate(schedule)


def assign_guide_to_schedule(db: Session, schedule_id: str, guide_id: str) -> TourscheduleResponse:
    schedule = _get_or_raise(db, schedule_id, repository)
    _get_or_raise(db, guide_id, user_repo)
    
    try:
        updated = repository.update_tourschedule(db, schedule_id, {"tour_guide_id": guide_id})
        db.commit()
        db.refresh(updated)
        return TourscheduleResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


def complete_tour_schedule(db: Session, schedule_id: str) -> TourscheduleResponse:
    schedule = _get_or_raise(db, schedule_id, repository)
    
    if schedule.status not in ["confirmed", "in_progress"]:
        raise HTTPException(status_code=400, detail="Only confirmed or in-progress schedules can be completed")
    
    from .models import Booking
    bookings = db.query(Booking).filter(Booking.tour_schedule_id == schedule_id).all()
    booking_ids = [b.id for b in bookings]
    
    try:
        for booking in bookings:
            if booking.booking_status == "confirmed":
                repository.update(db, booking.id, {"booking_status": "completed"})
        
        updated = repository.update_tourschedule(db, schedule_id, {"status": "completed"})
        db.commit()
        db.refresh(updated)
        return TourscheduleResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


# Tourschedulehotel handlers
def create_tourschedulehotel(db: Session, data: TourschedulehotelCreate) -> TourschedulehotelResponse:
    _get_or_raise(db, data.tour_schedule_id, repository)
    _get_or_raise(db, data.hotel_id, service_repo)
    
    if data.room_type_id:
        _get_or_raise(db, data.room_type_id, service_repo)
    
    if data.check_out_date < data.check_in_date:
        raise HTTPException(status_code=400, detail="Check-out date must be after check-in date")
    
    try:
        tourschedulehotel = repository.create_tourschedulehotel(db, data.model_dump())
        db.commit()
        db.refresh(tourschedulehotel)
        return TourschedulehotelResponse.model_validate(tourschedulehotel)
    except Exception:
        db.rollback()
        raise


def list_tourschedulehotels(db: Session, limit: int, offset: int, tour_schedule_id: Optional[str] = None,
                            hotel_id: Optional[str] = None, booking_status: Optional[str] = None) -> List[TourschedulehotelResponse]:
    filters = {}
    if tour_schedule_id:
        filters["tour_schedule_id"] = tour_schedule_id
    if hotel_id:
        filters["hotel_id"] = hotel_id
    if booking_status:
        filters["booking_status"] = booking_status
    
    tourschedulehotels = repository.list_tourschedulehotels(db, limit, offset, **filters)
    return [TourschedulehotelResponse.model_validate(tsh) for tsh in tourschedulehotels]


def get_tourschedulehotel(db: Session, tourschedulehotel_id: str) -> TourschedulehotelResponse:
    tourschedulehotel = repository.get_tourschedulehotel_by_id(db, tourschedulehotel_id)
    if not tourschedulehotel:
        raise HTTPException(status_code=404, detail="Tour schedule hotel not found")
    return TourschedulehotelResponse.model_validate(tourschedulehotel)


def update_tourschedulehotel(db: Session, tourschedulehotel_id: str, data: TourschedulehotelUpdate) -> TourschedulehotelResponse:
    tourschedulehotel = repository.get_tourschedulehotel_by_id(db, tourschedulehotel_id)
    if not tourschedulehotel:
        raise HTTPException(status_code=404, detail="Tour schedule hotel not found")
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "room_type_id" in update_data and update_data["room_type_id"]:
        _get_or_raise(db, update_data["room_type_id"], service_repo)
    
    try:
        updated = repository.update_tourschedulehotel(db, tourschedulehotel_id, update_data)
        db.commit()
        db.refresh(updated)
        return TourschedulehotelResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


def delete_tourschedulehotel(db: Session, tourschedulehotel_id: str) -> dict:
    tourschedulehotel = repository.get_tourschedulehotel_by_id(db, tourschedulehotel_id)
    if not tourschedulehotel:
        raise HTTPException(status_code=404, detail="Tour schedule hotel not found")
    
    try:
        repository.delete_tourschedulehotel(db, tourschedulehotel_id)
        db.commit()
        return {"message": "Tour schedule hotel deleted successfully"}
    except Exception:
        db.rollback()
        raise


# Tourscheduletransportation handlers
def create_tourscheduletransportation(db: Session, data: TourscheduletransportationCreate) -> TourscheduletransportationResponse:
    _get_or_raise(db, data.tour_schedule_id, repository)
    _get_or_raise(db, data.transportation_id, service_repo)
    
    if data.arrival_datetime < data.departure_datetime:
        raise HTTPException(status_code=400, detail="Arrival datetime must be after departure datetime")
    
    try:
        tourscheduletransportation = repository.create_tourscheduletransportation(db, data.model_dump())
        db.commit()
        db.refresh(tourscheduletransportation)
        return TourscheduletransportationResponse.model_validate(tourscheduletransportation)
    except Exception:
        db.rollback()
        raise


def list_tourscheduletransportations(db: Session, limit: int, offset: int, tour_schedule_id: Optional[str] = None,
                                     transportation_id: Optional[str] = None, booking_status: Optional[str] = None) -> List[TourscheduletransportationResponse]:
    filters = {}
    if tour_schedule_id:
        filters["tour_schedule_id"] = tour_schedule_id
    if transportation_id:
        filters["transportation_id"] = transportation_id
    if booking_status:
        filters["booking_status"] = booking_status
    
    tourscheduletransportations = repository.list_tourscheduletransportations(db, limit, offset, **filters)
    return [TourscheduletransportationResponse.model_validate(tst) for tst in tourscheduletransportations]


def get_tourscheduletransportation(db: Session, tourscheduletransportation_id: str) -> TourscheduletransportationResponse:
    tourscheduletransportation = repository.get_tourscheduletransportation_by_id(db, tourscheduletransportation_id)
    if not tourscheduletransportation:
        raise HTTPException(status_code=404, detail="Tour schedule transportation not found")
    return TourscheduletransportationResponse.model_validate(tourscheduletransportation)


def update_tourscheduletransportation(db: Session, tourscheduletransportation_id: str, data: TourscheduletransportationUpdate) -> TourscheduletransportationResponse:
    tourscheduletransportation = repository.get_tourscheduletransportation_by_id(db, tourscheduletransportation_id)
    if not tourscheduletransportation:
        raise HTTPException(status_code=404, detail="Tour schedule transportation not found")
    
    update_data = data.model_dump(exclude_unset=True)
    
    try:
        updated = repository.update_tourscheduletransportation(db, tourscheduletransportation_id, update_data)
        db.commit()
        db.refresh(updated)
        return TourscheduletransportationResponse.model_validate(updated)
    except Exception:
        db.rollback()
        raise


def delete_tourscheduletransportation(db: Session, tourscheduletransportation_id: str) -> dict:
    tourscheduletransportation = repository.get_tourscheduletransportation_by_id(db, tourscheduletransportation_id)
    if not tourscheduletransportation:
        raise HTTPException(status_code=404, detail="Tour schedule transportation not found")
    
    try:
        repository.delete_tourscheduletransportation(db, tourscheduletransportation_id)
        db.commit()
        return {"message": "Tour schedule transportation deleted successfully"}
    except Exception:
        db.rollback()
        raise