from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session, joinedload, selectinload, load_only
from sqlalchemy import and_, or_, func
from sqlalchemy.exc import IntegrityError
from typing import Optional
from datetime import datetime
import secrets
import os
from ...shared.config.database import get_db
from ...shared.config.settings import settings
from ...security.middleware.auth import get_current_user, authorize_roles
from ...auth.models.user import User
from ...auth.models.role import Role
from ..models.booking import Booking, BookingStatus
from ...rooms.models.room import Room, RoomStatus
from ...rooms.models.room_type import RoomType
from ...payments.models.payment import Payment, PaymentMethod, PaymentType, PaymentStatus
from ...hotel_services.models.service_usage import ServiceUsage
from ...loyalty.models.user_loyalty import UserLoyalty
from ...loyalty.models.referral import Referral, ReferralStatus
from ...rooms.services.room_service import normalize_images, get_base_url
from fastapi import Request
from ...shared.utils.mailer import send_email
from ...shared.utils.email_templates import booking_confirmation_email_template, booking_status_changed_email_template
from ...loyalty.services.loyalty_service import LoyaltyService
from ...shared.utils.currency_helpers import get_currency_symbol
from ...shared.utils.response_helpers import success_response
from ...analytics.services.audit_service import audit_service
from ..schemas.booking import CreateBookingRequest, UpdateBookingRequest
from ..schemas.admin_booking import AdminCreateBookingRequest
router = APIRouter(prefix='/bookings', tags=['bookings'])

def _generate_invoice_email_html(invoice: dict, is_proforma: bool=False) -> str:
    invoice_type = 'Proforma Invoice' if is_proforma else 'Invoice'
    items_html = ''.join([f for item in invoice.get('items', [])])
    return f

def generate_booking_number() -> str:
    prefix = 'BK'
    ts = int(datetime.utcnow().timestamp() * 1000)
    # Use cryptographically secure random number to prevent enumeration attacks
    rand = secrets.randbelow(9000) + 1000  # Random number between 1000-9999
    return f'{prefix}-{ts}-{rand}'

def calculate_booking_payment_balance(booking: Booking) -> dict:
    total_paid = 0.0
    if booking.payments:
        total_paid = sum((float(payment.amount) if payment.amount else 0.0 for payment in booking.payments if payment.payment_status == PaymentStatus.completed))
    total_price = float(booking.total_price) if booking.total_price else 0.0
    remaining_balance = total_price - total_paid
    return {'total_paid': total_paid, 'total_price': total_price, 'remaining_balance': remaining_balance, 'is_fully_paid': remaining_balance <= 0.01, 'payment_percentage': total_paid / total_price * 100 if total_price > 0 else 0}

@router.get('/')
async def get_all_bookings(search: Optional[str]=Query(None), status_filter: Optional[str]=Query(None, alias='status'), startDate: Optional[str]=Query(None), endDate: Optional[str]=Query(None), page: int=Query(1, ge=1), limit: int=Query(10, ge=1, le=100), current_user: User=Depends(authorize_roles('admin', 'staff')), db: Session=Depends(get_db)):
    try:
        # Use load_only to exclude non-existent columns (rate_plan_id, group_booking_id)
        query = db.query(Booking).options(
            load_only(
                Booking.id, Booking.booking_number, Booking.user_id, Booking.room_id,
                Booking.check_in_date, Booking.check_out_date, Booking.num_guests,
                Booking.total_price, Booking.original_price, Booking.discount_amount,
                Booking.promotion_code, Booking.status, Booking.deposit_paid,
                Booking.requires_deposit, Booking.special_requests,
                Booking.created_at, Booking.updated_at
            ),
            selectinload(Booking.payments),
            joinedload(Booking.user),
            joinedload(Booking.room).joinedload(Room.room_type)
        )
        if search:
            query = query.filter(Booking.booking_number.like(f'%{search}%'))
        if status_filter:
            try:
                query = query.filter(Booking.status == BookingStatus(status_filter))
            except ValueError:
                pass
        if startDate:
            start = datetime.fromisoformat(startDate.replace('Z', '+00:00'))
            query = query.filter(Booking.check_in_date >= start)
        if endDate:
            end = datetime.fromisoformat(endDate.replace('Z', '+00:00'))
            query = query.filter(Booking.check_in_date <= end)
        # Use func.count() to avoid loading all columns (including non-existent rate_plan_id)
        total = query.with_entities(func.count(Booking.id)).scalar()
        offset = (page - 1) * limit
        bookings = query.order_by(Booking.created_at.desc()).offset(offset).limit(limit).all()
        result = []
        for booking in bookings:
            payment_method_from_payments = None
            payment_status_from_payments = 'unpaid'
            if booking.payments:
                latest_payment = max(booking.payments, key=lambda p: p.created_at if p.created_at else datetime.min)
                if isinstance(latest_payment.payment_method, PaymentMethod):
                    payment_method_from_payments = latest_payment.payment_method.value
                elif hasattr(latest_payment.payment_method, 'value'):
                    payment_method_from_payments = latest_payment.payment_method.value
                else:
                    payment_method_from_payments = str(latest_payment.payment_method)
                if latest_payment.payment_status == PaymentStatus.completed:
                    payment_status_from_payments = 'paid'
                elif latest_payment.payment_status == PaymentStatus.refunded:
                    payment_status_from_payments = 'refunded'
            booking_dict = {'id': booking.id, 'booking_number': booking.booking_number, 'user_id': booking.user_id, 'room_id': booking.room_id, 'check_in_date': booking.check_in_date.strftime('%Y-%m-%d') if booking.check_in_date else None, 'check_out_date': booking.check_out_date.strftime('%Y-%m-%d') if booking.check_out_date else None, 'num_guests': booking.num_guests, 'guest_count': booking.num_guests, 'total_price': float(booking.total_price) if booking.total_price else 0.0, 'original_price': float(booking.original_price) if booking.original_price else None, 'discount_amount': float(booking.discount_amount) if booking.discount_amount else None, 'promotion_code': booking.promotion_code, 'status': booking.status.value if isinstance(booking.status, BookingStatus) else booking.status, 'payment_method': payment_method_from_payments if payment_method_from_payments else 'cash', 'payment_status': payment_status_from_payments, 'deposit_paid': booking.deposit_paid, 'requires_deposit': booking.requires_deposit, 'special_requests': booking.special_requests, 'notes': booking.special_requests, 'created_at': booking.created_at.isoformat() if booking.created_at else None, 'createdAt': booking.created_at.isoformat() if booking.created_at else None, 'updated_at': booking.updated_at.isoformat() if booking.updated_at else None, 'updatedAt': booking.updated_at.isoformat() if booking.updated_at else None}
            if booking.user:
                booking_dict['user'] = {'id': booking.user.id, 'name': booking.user.full_name, 'full_name': booking.user.full_name, 'email': booking.user.email, 'phone': booking.user.phone, 'phone_number': booking.user.phone}
            if booking.room:
                booking_dict['room'] = {'id': booking.room.id, 'room_number': booking.room.room_number, 'floor': booking.room.floor}
                try:
                    if hasattr(booking.room, 'room_type') and booking.room.room_type:
                        booking_dict['room']['room_type'] = {'id': booking.room.room_type.id, 'name': booking.room.room_type.name, 'base_price': float(booking.room.room_type.base_price) if booking.room.room_type.base_price else 0.0, 'capacity': booking.room.room_type.capacity}
                except Exception as room_type_error:
                    import logging
                    logger = logging.getLogger(__name__)
                    logger.warning(f'Could not load room_type for booking {booking.id}: {room_type_error}')
            if booking.payments:
                booking_dict['payments'] = [{'id': p.id, 'amount': float(p.amount) if p.amount else 0.0, 'payment_method': p.payment_method.value if isinstance(p.payment_method, PaymentMethod) else p.payment_method.value if hasattr(p.payment_method, 'value') else str(p.payment_method), 'payment_type': p.payment_type.value if isinstance(p.payment_type, PaymentType) else p.payment_type.value if hasattr(p.payment_type, 'value') else str(p.payment_type), 'payment_status': p.payment_status.value if isinstance(p.payment_status, PaymentStatus) else p.payment_status, 'transaction_id': p.transaction_id, 'payment_date': p.payment_date.isoformat() if p.payment_date else None, 'created_at': p.created_at.isoformat() if p.created_at else None} for p in booking.payments]
            else:
                booking_dict['payments'] = []
            result.append(booking_dict)
        return success_response(
            data={'bookings': result, 'pagination': {'total': total, 'page': page, 'limit': limit, 'totalPages': (total + limit - 1) // limit}}
        )
    except Exception as e:
        import logging
        import traceback
        logger = logging.getLogger(__name__)
        logger.error(f'Error in get_all_bookings: {str(e)}')
        logger.error(traceback.format_exc())
        raise HTTPException(status_code=500, detail=str(e))

@router.get('/me')
async def get_my_bookings(request: Request, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
    try:
        bookings = db.query(Booking).options(selectinload(Booking.payments), joinedload(Booking.room).joinedload(Room.room_type)).filter(Booking.user_id == current_user.id).order_by(Booking.created_at.desc()).all()
        base_url = get_base_url(request)
        result = []
        for booking in bookings:
            payment_method_from_payments = None
            payment_status_from_payments = 'unpaid'
            if booking.payments:
                latest_payment = max(booking.payments, key=lambda p: p.created_at if p.created_at else datetime.min)
                if isinstance(latest_payment.payment_method, PaymentMethod):
                    payment_method_from_payments = latest_payment.payment_method.value
                elif hasattr(latest_payment.payment_method, 'value'):
                    payment_method_from_payments = latest_payment.payment_method.value
                else:
                    payment_method_from_payments = str(latest_payment.payment_method)
                if latest_payment.payment_status == PaymentStatus.completed:
                    payment_status_from_payments = 'paid'
                elif latest_payment.payment_status == PaymentStatus.refunded:
                    payment_status_from_payments = 'refunded'
            booking_dict = {'id': booking.id, 'booking_number': booking.booking_number, 'room_id': booking.room_id, 'check_in_date': booking.check_in_date.strftime('%Y-%m-%d') if booking.check_in_date else None, 'check_out_date': booking.check_out_date.strftime('%Y-%m-%d') if booking.check_out_date else None, 'num_guests': booking.num_guests, 'guest_count': booking.num_guests, 'total_price': float(booking.total_price) if booking.total_price else 0.0, 'original_price': float(booking.original_price) if booking.original_price else None, 'discount_amount': float(booking.discount_amount) if booking.discount_amount else None, 'promotion_code': booking.promotion_code, 'status': booking.status.value if isinstance(booking.status, BookingStatus) else booking.status, 'payment_method': payment_method_from_payments if payment_method_from_payments else 'cash', 'payment_status': payment_status_from_payments, 'deposit_paid': booking.deposit_paid, 'requires_deposit': booking.requires_deposit, 'special_requests': booking.special_requests, 'notes': booking.special_requests, 'created_at': booking.created_at.isoformat() if booking.created_at else None, 'createdAt': booking.created_at.isoformat() if booking.created_at else None, 'updated_at': booking.updated_at.isoformat() if booking.updated_at else None, 'updatedAt': booking.updated_at.isoformat() if booking.updated_at else None}
            if booking.room and booking.room.room_type:
                room_images = []
                if booking.room.images:
                    try:
                        room_images = normalize_images(booking.room.images, base_url)
                    except:
                        room_images = []
                booking_dict['room'] = {'id': booking.room.id, 'room_number': booking.room.room_number, 'floor': booking.room.floor, 'images': room_images, 'room_type': {'id': booking.room.room_type.id, 'name': booking.room.room_type.name, 'base_price': float(booking.room.room_type.base_price) if booking.room.room_type.base_price else 0.0, 'capacity': booking.room.room_type.capacity, 'images': room_images}}
            if booking.payments:
                booking_dict['payments'] = [{'id': p.id, 'amount': float(p.amount) if p.amount else 0.0, 'payment_method': p.payment_method.value if isinstance(p.payment_method, PaymentMethod) else p.payment_method.value if hasattr(p.payment_method, 'value') else str(p.payment_method), 'payment_type': p.payment_type.value if isinstance(p.payment_type, PaymentType) else p.payment_type.value if hasattr(p.payment_type, 'value') else str(p.payment_type), 'payment_status': p.payment_status.value if isinstance(p.payment_status, PaymentStatus) else p.payment_status, 'transaction_id': p.transaction_id, 'payment_date': p.payment_date.isoformat() if p.payment_date else None, 'created_at': p.created_at.isoformat() if p.created_at else None} for p in booking.payments]
            else:
                booking_dict['payments'] = []
            result.append(booking_dict)
        return success_response(data={'bookings': result})
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        import logging
        logger = logging.getLogger(__name__)
        request_id = getattr(request.state, 'request_id', None) if hasattr(request, 'state') else None
        logger.error(f'Error in get_my_bookings: {str(e)}', extra={'request_id': request_id, 'user_id': current_user.id}, exc_info=True)
        raise HTTPException(status_code=500, detail='An error occurred while fetching bookings')

@router.post('/')
async def create_booking(booking_data: CreateBookingRequest, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
    """Create a new booking with validated input using Pydantic schema."""
    role = db.query(Role).filter(Role.id == current_user.role_id).first()
    if role and role.name in ['admin', 'staff', 'accountant']:
        raise HTTPException(status_code=403, detail='Admin, staff, and accountant users cannot create bookings')
    
    import logging
    logger = logging.getLogger(__name__)
    
    # Start transaction
    transaction = db.begin()
    try:
        logger.info(f'Received booking request from user {current_user.id}: {booking_data.dict()}')
        
        # Extract validated data from Pydantic model
        room_id = booking_data.room_id
        check_in_date = booking_data.check_in_date
        check_out_date = booking_data.check_out_date
        total_price = booking_data.total_price
        guest_count = booking_data.guest_count
        notes = booking_data.notes
        payment_method = booking_data.payment_method
        promotion_code = booking_data.promotion_code
        referral_code = booking_data.referral_code
        services = booking_data.services or []
        invoice_info = booking_data.invoice_info.dict() if booking_data.invoice_info else {}
        
        # Lock room row to prevent race conditions (SELECT FOR UPDATE)
        room = db.query(Room).filter(Room.id == room_id).with_for_update().first()
        if not room:
            transaction.rollback()
            raise HTTPException(status_code=404, detail='Room not found')
        
        # Parse dates (schema validation already ensures format is valid)
        if 'T' in check_in_date or 'Z' in check_in_date or '+' in check_in_date:
            check_in = datetime.fromisoformat(check_in_date.replace('Z', '+00:00'))
        else:
            check_in = datetime.strptime(check_in_date, '%Y-%m-%d')
        if 'T' in check_out_date or 'Z' in check_out_date or '+' in check_out_date:
            check_out = datetime.fromisoformat(check_out_date.replace('Z', '+00:00'))
        else:
            check_out = datetime.strptime(check_out_date, '%Y-%m-%d')
        
        # Date validation already done in schema, but keeping as safety check
        if check_in >= check_out:
            transaction.rollback()
            raise HTTPException(status_code=400, detail='Check-out date must be after check-in date')
        
        # Check for overlapping bookings with row-level locking to prevent race conditions
        overlapping = db.query(Booking).filter(
            and_(
                Booking.room_id == room_id,
                Booking.status != BookingStatus.cancelled,
                Booking.check_in_date < check_out,
                Booking.check_out_date > check_in
            )
        ).with_for_update().first()
        if overlapping:
            transaction.rollback()
            raise HTTPException(status_code=409, detail='Room already booked for the selected dates')
        
        # Check for maintenance blocks
        from ...rooms.models.room_maintenance import RoomMaintenance, MaintenanceStatus
        maintenance_block = db.query(RoomMaintenance).filter(
            and_(
                RoomMaintenance.room_id == room_id,
                RoomMaintenance.blocks_room == True,
                RoomMaintenance.status.in_([MaintenanceStatus.scheduled, MaintenanceStatus.in_progress]),
                or_(
                    and_(
                        RoomMaintenance.block_start.isnot(None),
                        RoomMaintenance.block_end.isnot(None),
                        RoomMaintenance.block_start < check_out,
                        RoomMaintenance.block_end > check_in
                    ),
                    and_(
                        RoomMaintenance.scheduled_start < check_out,
                        RoomMaintenance.scheduled_end.isnot(None),
                        RoomMaintenance.scheduled_end > check_in
                    )
                )
            )
        ).first()
        if maintenance_block:
            raise HTTPException(status_code=409, detail=f'Room is blocked for maintenance: {maintenance_block.title}')
        booking_number = generate_booking_number()
        
        # Calculate room price
        room_price = float(room.price) if room.price and room.price > 0 else float(room.room_type.base_price) if room.room_type else 0.0
        number_of_nights = (check_out - check_in).days
        if number_of_nights <= 0:
            number_of_nights = 1  # Minimum 1 night
        room_total = room_price * number_of_nights
        
        # Calculate services total if any (using Pydantic model)
        services_total = 0.0
        if services:
            from ...hotel_services.models.service import Service
            for service_item in services:
                service_id = service_item.service_id
                quantity = service_item.quantity
                service = db.query(Service).filter(Service.id == service_id).first()
                if service and service.is_active:
                    services_total += float(service.price) * quantity
        
        original_price = room_total + services_total
        
        # Validate and use calculated price (same logic as admin booking)
        calculated_total = original_price
        provided_total = float(total_price) if total_price else 0.0
        
        if promotion_code:
            # With promotion, allow the provided price (it might include discount)
            discount_amount = max(0.0, original_price - provided_total)
            final_total_price = provided_total
        else:
            # Without promotion, use calculated price to ensure consistency
            # Allow small differences (0.01) for rounding, but use calculated price
            if abs(calculated_total - provided_total) > 0.01:
                logger.warning(f'Price mismatch: calculated={calculated_total}, provided={provided_total}. Using calculated price.')
                final_total_price = calculated_total
            else:
                final_total_price = provided_total
            discount_amount = 0.0
        
        requires_deposit = payment_method == 'cash'
        deposit_percentage = 20 if requires_deposit else 0
        deposit_amount = final_total_price * deposit_percentage / 100 if requires_deposit else 0
        
        initial_status = BookingStatus.pending
        if payment_method in ['stripe', 'paypal']:
            initial_status = BookingStatus.pending
        
        # Sanitize user-provided notes to prevent XSS
        from html import escape
        final_notes = escape(notes) if notes else ''
        if promotion_code:
            promotion_note = f'Promotion Code: {promotion_code}'
            final_notes = f'{promotion_note}\n{final_notes}'.strip() if final_notes else promotion_note
        
        booking = Booking(
            booking_number=booking_number,
            user_id=current_user.id,
            room_id=room_id,
            check_in_date=check_in,
            check_out_date=check_out,
            num_guests=guest_count,
            total_price=final_total_price,
            original_price=original_price if promotion_code else None,
            discount_amount=discount_amount if promotion_code and discount_amount > 0 else None,
            promotion_code=promotion_code,
            special_requests=final_notes,
            status=initial_status,
            requires_deposit=requires_deposit,
            deposit_paid=False
        )
        db.add(booking)
        db.flush()
        
        # Process referral code if provided
        if referral_code:
            try:
                from ...loyalty.services.loyalty_service import LoyaltyService
                from ...system.models.system_settings import SystemSettings
                
                # Check if loyalty program is enabled
                setting = db.query(SystemSettings).filter(
                    SystemSettings.key == 'loyalty_program_enabled'
                ).first()
                is_enabled = True  # Default to enabled
                if setting:
                    is_enabled = setting.value.lower() == 'true'
                
                if is_enabled:
                    # Process referral code - this will create referral record and award points when booking is confirmed
                    LoyaltyService.process_referral(
                        db,
                        current_user.id,
                        referral_code.upper().strip(),
                        booking.id
                    )
                    logger.info(f"Referral code {referral_code} processed for booking {booking.id}")
            except Exception as referral_error:
                logger.warning(f"Failed to process referral code {referral_code}: {referral_error}")
                # Don't fail the booking if referral processing fails
        if payment_method in ['stripe', 'paypal']:
            from ...payments.models.payment import Payment, PaymentMethod, PaymentStatus, PaymentType
            if payment_method == 'stripe':
                payment_method_enum = PaymentMethod.stripe
            elif payment_method == 'paypal':
                payment_method_enum = PaymentMethod.paypal
            else:
                logger.warning(f'Unexpected payment_method: {payment_method}, defaulting to stripe')
                payment_method_enum = PaymentMethod.stripe
            logger.info(f'Creating payment for booking {booking.id} with payment_method: {payment_method} -> enum: {payment_method_enum.value}')
            payment = Payment(booking_id=booking.id, amount=final_total_price, payment_method=payment_method_enum, payment_type=PaymentType.full, payment_status=PaymentStatus.pending, payment_date=None)
            db.add(payment)
            db.flush()
            logger.info(f'Payment created: ID={payment.id}, method={(payment.payment_method.value if hasattr(payment.payment_method, 'value') else payment.payment_method)}')
        if requires_deposit and deposit_amount > 0:
            from ...payments.models.payment import Payment, PaymentMethod, PaymentStatus, PaymentType
            deposit_payment = Payment(booking_id=booking.id, amount=deposit_amount, payment_method=PaymentMethod.stripe, payment_type=PaymentType.deposit, deposit_percentage=deposit_percentage, payment_status=PaymentStatus.pending, payment_date=None)
            db.add(deposit_payment)
            db.flush()
            logger.info(f'Deposit payment created: ID={deposit_payment.id}, amount={deposit_amount}, percentage={deposit_percentage}%')
        # Add service usages (services already extracted from Pydantic model)
        if services:
            from ...hotel_services.models.service import Service
            for service_item in services:
                service_id = service_item.service_id
                quantity = service_item.quantity
                service = db.query(Service).filter(Service.id == service_id).first()
                if not service or not service.is_active:
                    continue
                unit_price = float(service.price)
                total_price = unit_price * quantity
                service_usage = ServiceUsage(booking_id=booking.id, service_id=service_id, quantity=quantity, unit_price=unit_price, total_price=total_price)
                db.add(service_usage)
        
        # Commit transaction - all database operations are atomic
        transaction.commit()
        db.refresh(booking)
        
        # Send booking confirmation notification (outside transaction)
        try:
            from ...notifications.services.notification_service import NotificationService
            if booking.status == BookingStatus.confirmed:
                NotificationService.send_booking_confirmation(db, booking)
        except Exception as e:
            logger.warning(f'Failed to send booking confirmation notification: {e}')
        
        # Create invoice (outside transaction to avoid nested transaction issues)
        try:
            from ...payments.services.invoice_service import InvoiceService
            from ...shared.utils.mailer import send_email
            from sqlalchemy.orm import joinedload, selectinload
            booking = db.query(Booking).options(selectinload(Booking.service_usages).selectinload(ServiceUsage.service)).filter(Booking.id == booking.id).first()
            from ...system.models.system_settings import SystemSettings
            company_settings = {}
            for key in ['company_name', 'company_address', 'company_phone', 'company_email', 'company_tax_id', 'company_logo_url']:
                setting = db.query(SystemSettings).filter(SystemSettings.key == key).first()
                if setting and setting.value:
                    company_settings[key] = setting.value
            tax_rate_setting = db.query(SystemSettings).filter(SystemSettings.key == 'tax_rate').first()
            tax_rate = float(tax_rate_setting.value) if tax_rate_setting and tax_rate_setting.value else 0.0
            invoice_kwargs = {**company_settings}
            if invoice_info:
                if invoice_info.get('company_name'):
                    invoice_kwargs['company_name'] = invoice_info.get('company_name')
                if invoice_info.get('company_address'):
                    invoice_kwargs['company_address'] = invoice_info.get('company_address')
                if invoice_info.get('company_tax_id'):
                    invoice_kwargs['company_tax_id'] = invoice_info.get('company_tax_id')
                if invoice_info.get('customer_tax_id'):
                    invoice_kwargs['customer_tax_id'] = invoice_info.get('customer_tax_id')
                if invoice_info.get('notes'):
                    invoice_kwargs['notes'] = invoice_info.get('notes')
                if invoice_info.get('terms_and_conditions'):
                    invoice_kwargs['terms_and_conditions'] = invoice_info.get('terms_and_conditions')
                if invoice_info.get('payment_instructions'):
                    invoice_kwargs['payment_instructions'] = invoice_info.get('payment_instructions')
            booking_discount = float(booking.discount_amount) if booking.discount_amount else 0.0
            invoice_notes = invoice_kwargs.get('notes', '')
            if booking.promotion_code:
                promotion_note = f'Promotion Code: {booking.promotion_code}'
                invoice_notes = f'{promotion_note}\n{invoice_notes}'.strip() if invoice_notes else promotion_note
                invoice_kwargs['notes'] = invoice_notes
            if payment_method == 'cash':
                deposit_amount = final_total_price * 0.2
                remaining_amount = final_total_price * 0.8
                deposit_discount = booking_discount * 0.2 if booking_discount > 0 else 0.0
                proforma_discount = booking_discount * 0.8 if booking_discount > 0 else 0.0
                deposit_invoice = InvoiceService.create_invoice_from_booking(booking_id=booking.id, db=db, created_by_id=current_user.id, tax_rate=tax_rate, discount_amount=deposit_discount, due_days=30, is_proforma=False, invoice_amount=deposit_amount, **invoice_kwargs)
                proforma_invoice = InvoiceService.create_invoice_from_booking(booking_id=booking.id, db=db, created_by_id=current_user.id, tax_rate=tax_rate, discount_amount=proforma_discount, due_days=30, is_proforma=True, invoice_amount=remaining_amount, **invoice_kwargs)
                try:
                    invoice_html = _generate_invoice_email_html(deposit_invoice, is_proforma=False)
                    await send_email(to=current_user.email, subject=f'Invoice {deposit_invoice['invoice_number']} - Deposit Payment', html=invoice_html)
                    logger.info(f'Deposit invoice sent to {current_user.email}')
                except Exception as email_error:
                    logger.error(f'Failed to send deposit invoice email: {str(email_error)}')
                try:
                    proforma_html = _generate_invoice_email_html(proforma_invoice, is_proforma=True)
                    await send_email(to=current_user.email, subject=f'Proforma Invoice {proforma_invoice['invoice_number']} - Remaining Balance', html=proforma_html)
                    logger.info(f'Proforma invoice sent to {current_user.email}')
                except Exception as email_error:
                    logger.error(f'Failed to send proforma invoice email: {str(email_error)}')
            else:
                full_invoice = InvoiceService.create_invoice_from_booking(booking_id=booking.id, db=db, created_by_id=current_user.id, tax_rate=tax_rate, discount_amount=booking_discount, due_days=30, is_proforma=False, **invoice_kwargs)
                logger.info(f'Invoice {full_invoice['invoice_number']} created for booking {booking.id} (will be sent after payment confirmation)')
        except Exception as e:
            import logging
            import traceback
            logger = logging.getLogger(__name__)
            logger.error(f'Failed to create invoice for booking {booking.id}: {str(e)}')
            logger.error(f'Traceback: {traceback.format_exc()}')
        from sqlalchemy.orm import joinedload, selectinload
        booking = db.query(Booking).options(joinedload(Booking.payments), selectinload(Booking.service_usages).selectinload(ServiceUsage.service)).filter(Booking.id == booking.id).first()
        payment_method_from_payments = None
        payment_status_from_payments = 'unpaid'
        if booking.payments:
            latest_payment = sorted(booking.payments, key=lambda p: p.created_at, reverse=True)[0]
            if isinstance(latest_payment.payment_method, PaymentMethod):
                payment_method_from_payments = latest_payment.payment_method.value
            elif hasattr(latest_payment.payment_method, 'value'):
                payment_method_from_payments = latest_payment.payment_method.value
            else:
                payment_method_from_payments = str(latest_payment.payment_method)
            logger.info(f'Booking {booking.id} - Latest payment method: {payment_method_from_payments}, raw: {latest_payment.payment_method}')
            if latest_payment.payment_status == PaymentStatus.completed:
                payment_status_from_payments = 'paid'
            elif latest_payment.payment_status == PaymentStatus.refunded:
                payment_status_from_payments = 'refunded'
        final_payment_method = payment_method_from_payments if payment_method_from_payments else payment_method
        logger.info(f'Booking {booking.id} - Final payment_method: {final_payment_method} (from_payments: {payment_method_from_payments}, request: {payment_method})')
        booking_dict = {'id': booking.id, 'booking_number': booking.booking_number, 'user_id': booking.user_id, 'room_id': booking.room_id, 'check_in_date': booking.check_in_date.strftime('%Y-%m-%d') if booking.check_in_date else None, 'check_out_date': booking.check_out_date.strftime('%Y-%m-%d') if booking.check_out_date else None, 'guest_count': booking.num_guests, 'total_price': float(booking.total_price) if booking.total_price else 0.0, 'status': booking.status.value if isinstance(booking.status, BookingStatus) else booking.status, 'payment_method': final_payment_method, 'payment_status': payment_status_from_payments, 'deposit_paid': booking.deposit_paid, 'requires_deposit': booking.requires_deposit, 'notes': booking.special_requests, 'guest_info': {'full_name': current_user.full_name, 'email': current_user.email, 'phone': current_user.phone_number if hasattr(current_user, 'phone_number') else current_user.phone if hasattr(current_user, 'phone') else ''}, 'createdAt': booking.created_at.isoformat() if booking.created_at else None, 'updatedAt': booking.updated_at.isoformat() if booking.updated_at else None, 'created_at': booking.created_at.isoformat() if booking.created_at else None}
        if booking.payments:
            booking_dict['payments'] = [{'id': p.id, 'booking_id': p.booking_id, 'amount': float(p.amount) if p.amount else 0.0, 'payment_method': p.payment_method.value if isinstance(p.payment_method, PaymentMethod) else p.payment_method.value if hasattr(p.payment_method, 'value') else str(p.payment_method), 'payment_type': p.payment_type.value if isinstance(p.payment_type, PaymentType) else p.payment_type, 'deposit_percentage': p.deposit_percentage, 'payment_status': p.payment_status.value if isinstance(p.payment_status, PaymentStatus) else p.payment_status, 'transaction_id': p.transaction_id, 'payment_date': p.payment_date.isoformat() if p.payment_date else None, 'notes': p.notes, 'created_at': p.created_at.isoformat() if p.created_at else None} for p in booking.payments]
        service_usages = getattr(booking, 'service_usages', None)
        import logging
        logger = logging.getLogger(__name__)
        logger.info(f'Booking {booking.id} - service_usages: {service_usages}, type: {type(service_usages)}')
        if service_usages and len(service_usages) > 0:
            logger.info(f'Booking {booking.id} - Found {len(service_usages)} service usages')
            booking_dict['service_usages'] = [{'id': su.id, 'service_id': su.service_id, 'service_name': su.service.name if hasattr(su, 'service') and su.service else 'Unknown Service', 'quantity': su.quantity, 'unit_price': float(su.unit_price) if su.unit_price else 0.0, 'total_price': float(su.total_price) if su.total_price else 0.0} for su in service_usages]
            logger.info(f'Booking {booking.id} - Serialized service_usages: {booking_dict['service_usages']}')
        else:
            logger.info(f'Booking {booking.id} - No service_usages found, initializing empty array')
            booking_dict['service_usages'] = []
        if booking.room:
            booking_dict['room'] = {'id': booking.room.id, 'room_number': booking.room.room_number, 'floor': booking.room.floor}
            if booking.room.room_type:
                booking_dict['room']['room_type'] = {'id': booking.room.room_type.id, 'name': booking.room.room_type.name, 'base_price': float(booking.room.room_type.base_price) if booking.room.room_type.base_price else 0.0, 'capacity': booking.room.room_type.capacity}
        message = f'Booking created. Please pay {deposit_percentage}% deposit to confirm.' if requires_deposit else 'Booking created successfully'
        return success_response(data={'booking': booking_dict}, message=message)
    except HTTPException:
        if 'transaction' in locals():
            try:
                transaction.rollback()
            except Exception:
                pass
        raise
    except IntegrityError as e:
        if 'transaction' in locals():
            try:
                transaction.rollback()
            except Exception:
                pass
        logger.error(f'Database integrity error during booking creation: {str(e)}')
        raise HTTPException(status_code=409, detail='Booking conflict detected. Please try again.')
    except Exception as e:
        if 'transaction' in locals():
            try:
                transaction.rollback()
            except Exception:
                pass
        logger.error(f'Error creating booking: {str(e)}', exc_info=True)
        import logging
        import traceback
        logger = logging.getLogger(__name__)
        logger.error(f'Error creating booking (payment_method: {payment_method}): {str(e)}')
        logger.error(f'Traceback: {traceback.format_exc()}')
        try:
            db.rollback()
        except Exception:
            pass
        raise HTTPException(status_code=500, detail='An error occurred while creating the booking. Please try again.')

@router.get('/{id}')
async def get_booking_by_id(id: int, request: Request, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
    try:
        from sqlalchemy.orm import selectinload
        booking = db.query(Booking).options(selectinload(Booking.payments), selectinload(Booking.service_usages).selectinload(ServiceUsage.service), joinedload(Booking.user), joinedload(Booking.room).joinedload(Room.room_type)).filter(Booking.id == id).first()
        if not booking:
            raise HTTPException(status_code=404, detail='Booking not found')
        from ...shared.utils.role_helpers import can_manage_bookings
        # Allow admin/staff to view any booking, customers can only view their own
        if not can_manage_bookings(current_user, db) and booking.user_id != current_user.id:
            raise HTTPException(status_code=403, detail='Forbidden')
        import logging
        logger = logging.getLogger(__name__)
        payment_method_from_payments = None
        payment_status = 'unpaid'
        if booking.payments:
            latest_payment = max(booking.payments, key=lambda p: p.created_at if p.created_at else datetime.min)
            if isinstance(latest_payment.payment_method, PaymentMethod):
                payment_method_from_payments = latest_payment.payment_method.value
            elif hasattr(latest_payment.payment_method, 'value'):
                payment_method_from_payments = latest_payment.payment_method.value
            else:
                payment_method_from_payments = str(latest_payment.payment_method)
            logger.info(f'Get booking {id} - Latest payment method: {payment_method_from_payments}, raw: {latest_payment.payment_method}')
            if latest_payment.payment_status == PaymentStatus.completed:
                payment_status = 'paid'
            elif latest_payment.payment_status == PaymentStatus.refunded:
                payment_status = 'refunded'
        final_payment_method = payment_method_from_payments if payment_method_from_payments else 'cash'
        logger.info(f'Get booking {id} - Final payment_method: {final_payment_method}')
        booking_dict = {'id': booking.id, 'booking_number': booking.booking_number, 'user_id': booking.user_id, 'room_id': booking.room_id, 'check_in_date': booking.check_in_date.strftime('%Y-%m-%d') if booking.check_in_date else None, 'check_out_date': booking.check_out_date.strftime('%Y-%m-%d') if booking.check_out_date else None, 'guest_count': booking.num_guests, 'total_price': float(booking.total_price) if booking.total_price else 0.0, 'status': booking.status.value if isinstance(booking.status, BookingStatus) else booking.status, 'payment_method': final_payment_method, 'payment_status': payment_status, 'deposit_paid': booking.deposit_paid, 'requires_deposit': booking.requires_deposit, 'notes': booking.special_requests, 'guest_info': {'full_name': booking.user.full_name if booking.user else '', 'email': booking.user.email if booking.user else '', 'phone': booking.user.phone_number if booking.user and hasattr(booking.user, 'phone_number') else booking.user.phone if booking.user and hasattr(booking.user, 'phone') else ''} if booking.user else None, 'createdAt': booking.created_at.isoformat() if booking.created_at else None, 'updatedAt': booking.updated_at.isoformat() if booking.updated_at else None, 'created_at': booking.created_at.isoformat() if booking.created_at else None}
        if booking.room and booking.room.images:
            base_url = get_base_url(request)
            try:
                room_images = normalize_images(booking.room.images, base_url)
            except:
                room_images = []
        else:
            room_images = []
        if booking.room:
            booking_dict['room'] = {'id': booking.room.id, 'room_number': booking.room.room_number, 'floor': booking.room.floor, 'status': booking.room.status.value if isinstance(booking.room.status, RoomStatus) else booking.room.status, 'images': room_images}
            if booking.room.room_type:
                room_type_images = room_images if room_images else []
                booking_dict['room']['room_type'] = {'id': booking.room.room_type.id, 'name': booking.room.room_type.name, 'base_price': float(booking.room.room_type.base_price) if booking.room.room_type.base_price else 0.0, 'capacity': booking.room.room_type.capacity, 'images': room_type_images}
        if booking.payments:
            booking_dict['payments'] = [{'id': p.id, 'amount': float(p.amount) if p.amount else 0.0, 'payment_method': p.payment_method.value if isinstance(p.payment_method, PaymentMethod) else p.payment_method.value if hasattr(p.payment_method, 'value') else str(p.payment_method), 'payment_type': p.payment_type.value if isinstance(p.payment_type, PaymentType) else p.payment_type.value if hasattr(p.payment_type, 'value') else str(p.payment_type), 'payment_status': p.payment_status.value if isinstance(p.payment_status, PaymentStatus) else p.payment_status, 'transaction_id': p.transaction_id, 'payment_date': p.payment_date.isoformat() if p.payment_date else None, 'created_at': p.created_at.isoformat() if p.created_at else None} for p in booking.payments]
        service_usages = getattr(booking, 'service_usages', None)
        import logging
        logger = logging.getLogger(__name__)
        logger.info(f'Get booking {id} - service_usages: {service_usages}, type: {type(service_usages)}')
        if service_usages and len(service_usages) > 0:
            logger.info(f'Get booking {id} - Found {len(service_usages)} service usages')
            booking_dict['service_usages'] = [{'id': su.id, 'service_id': su.service_id, 'service_name': su.service.name if hasattr(su, 'service') and su.service else 'Unknown Service', 'quantity': su.quantity, 'unit_price': float(su.unit_price) if su.unit_price else 0.0, 'total_price': float(su.total_price) if su.total_price else 0.0} for su in service_usages]
            logger.info(f'Get booking {id} - Serialized service_usages: {booking_dict['service_usages']}')
        else:
            logger.info(f'Get booking {id} - No service_usages found, initializing empty array')
            booking_dict['service_usages'] = []
        return success_response(data={'booking': booking_dict})
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        import logging
        logger = logging.getLogger(__name__)
        request_id = getattr(request.state, 'request_id', None) if hasattr(request, 'state') else None
        logger.error(f'Error in get_booking_by_id: {str(e)}', extra={'request_id': request_id, 'booking_id': id, 'user_id': current_user.id}, exc_info=True)
        raise HTTPException(status_code=500, detail='An error occurred while fetching booking')

@router.patch('/{id}/cancel')
async def cancel_booking(id: int, request: Request, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
    try:
        client_ip = request.client.host if request.client else None
        user_agent = request.headers.get('User-Agent')
        request_id = getattr(request.state, 'request_id', None)
        
        booking = db.query(Booking).filter(Booking.id == id).first()
        if not booking:
            raise HTTPException(status_code=404, detail='Booking not found')
        if booking.user_id != current_user.id:
            raise HTTPException(status_code=403, detail='Forbidden')
        if booking.status == BookingStatus.cancelled:
            raise HTTPException(status_code=400, detail='Booking already cancelled')
        
        old_status = booking.status.value if hasattr(booking.status, 'value') else str(booking.status)
        # Customers can only cancel pending bookings
        # Admin/Staff can cancel any booking via update_booking endpoint
        if booking.status != BookingStatus.pending:
            raise HTTPException(status_code=400, detail=f'Cannot cancel booking with status: {booking.status.value}. Only pending bookings can be cancelled. Please contact support for assistance.')
        booking = db.query(Booking).options(selectinload(Booking.payments)).filter(Booking.id == id).first()
        payments_updated = False
        if booking.payments:
            for payment in booking.payments:
                if payment.payment_status == PaymentStatus.pending:
                    payment.payment_status = PaymentStatus.failed
                    existing_notes = payment.notes or ''
                    cancellation_note = f'\nPayment cancelled due to booking cancellation on {datetime.utcnow().isoformat()}'
                    payment.notes = existing_notes + cancellation_note if existing_notes else cancellation_note.strip()
                    payments_updated = True
        from sqlalchemy import update, func
        pending_payments = db.query(Payment).filter(Payment.booking_id == id, Payment.payment_status == PaymentStatus.pending).all()
        for payment in pending_payments:
            payment.payment_status = PaymentStatus.failed
            existing_notes = payment.notes or ''
            cancellation_note = f'\nPayment cancelled due to booking cancellation on {datetime.utcnow().isoformat()}'
            payment.notes = existing_notes + cancellation_note if existing_notes else cancellation_note.strip()
            payments_updated = True
        booking.status = BookingStatus.cancelled
        
        # Update room status when booking is cancelled
        if booking.room:
            # Check if room has other active bookings
            active_booking = db.query(Booking).filter(
                and_(
                    Booking.room_id == booking.room_id,
                    Booking.id != booking.id,
                    Booking.status.in_([BookingStatus.confirmed, BookingStatus.checked_in]),
                    Booking.check_in_date <= datetime.utcnow(),
                    Booking.check_out_date > datetime.utcnow()
                )
            ).first()
            
            if not active_booking:
                # Check for maintenance
                from ...rooms.models.room_maintenance import RoomMaintenance, MaintenanceStatus
                active_maintenance = db.query(RoomMaintenance).filter(
                    and_(
                        RoomMaintenance.room_id == booking.room_id,
                        RoomMaintenance.blocks_room == True,
                        RoomMaintenance.status.in_([MaintenanceStatus.scheduled, MaintenanceStatus.in_progress])
                    )
                ).first()
                
                if active_maintenance:
                    booking.room.status = RoomStatus.maintenance
                else:
                    booking.room.status = RoomStatus.available
        
        if payments_updated:
            db.flush()
        db.commit()
        
        # SECURITY: Log booking cancellation for audit trail
        try:
            await audit_service.log_action(
                db=db,
                action='booking_cancelled',
                resource_type='booking',
                user_id=current_user.id,
                resource_id=booking.id,
                ip_address=client_ip,
                user_agent=user_agent,
                request_id=request_id,
                details={
                    'booking_number': booking.booking_number,
                    'old_status': old_status,
                    'new_status': 'cancelled',
                    'booking_user_id': booking.user_id,
                    'room_id': booking.room_id if booking.room else None,
                    'total_price': float(booking.total_price) if booking.total_price else 0.0,
                    'payments_updated': payments_updated
                },
                status='success'
            )
        except Exception as e:
            import logging
            logger = logging.getLogger(__name__)
            logger.warning(f'Failed to log booking cancellation audit: {e}')
        
        try:
            from ...system.models.system_settings import SystemSettings
            client_url_setting = db.query(SystemSettings).filter(SystemSettings.key == 'client_url').first()
            client_url = client_url_setting.value if client_url_setting and client_url_setting.value else settings.CLIENT_URL or os.getenv('CLIENT_URL', 'http://localhost:5173')
            email_html = booking_status_changed_email_template(booking_number=booking.booking_number, guest_name=booking.user.full_name if booking.user else 'Guest', status='cancelled', client_url=client_url)
            await send_email(to=booking.user.email if booking.user else None, subject=f'Booking Cancelled - {booking.booking_number}', html=email_html)
        except Exception as e:
            import logging
            logger = logging.getLogger(__name__)
            logger.error(f'Failed to send cancellation email: {e}')
        return success_response(data={'booking': booking})
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=str(e))

@router.put('/{id}', dependencies=[Depends(authorize_roles('admin', 'staff'))])
async def update_booking(id: int, request: Request, booking_data: UpdateBookingRequest, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
    client_ip = request.client.host if request.client else None
    user_agent = request.headers.get('User-Agent')
    request_id = getattr(request.state, 'request_id', None)
    
    try:
        booking = db.query(Booking).options(
            selectinload(Booking.payments),
            joinedload(Booking.user)
        ).filter(Booking.id == id).first()
        if not booking:
            raise HTTPException(status_code=404, detail='Booking not found')
        old_status = booking.status
        old_status_value = old_status.value if hasattr(old_status, 'value') else str(old_status)
        status_value = booking_data.status
        room = booking.room
        new_status = None
        if status_value:
            try:
                new_status = BookingStatus(status_value)
                booking.status = new_status
                
                # Update room status based on booking status
                if new_status == BookingStatus.checked_in:
                    # Set room to occupied when checked in
                    if room and room.status != RoomStatus.maintenance:
                        room.status = RoomStatus.occupied
                elif new_status == BookingStatus.checked_out:
                    # Set room to cleaning when checked out (housekeeping needed)
                    if room:
                        # Check if there's active maintenance
                        from ...rooms.models.room_maintenance import RoomMaintenance, MaintenanceStatus
                        active_maintenance = db.query(RoomMaintenance).filter(
                            and_(
                                RoomMaintenance.room_id == room.id,
                                RoomMaintenance.blocks_room == True,
                                RoomMaintenance.status.in_([MaintenanceStatus.scheduled, MaintenanceStatus.in_progress])
                            )
                        ).first()
                        if active_maintenance:
                            room.status = RoomStatus.maintenance
                        else:
                            room.status = RoomStatus.cleaning
                            
                            # Auto-create housekeeping task for checkout cleaning
                            from ...hotel_services.models.housekeeping_task import HousekeepingTask, HousekeepingStatus, HousekeepingType
                            
                            # Check if a pending checkout task already exists for this room
                            existing_task = db.query(HousekeepingTask).filter(
                                and_(
                                    HousekeepingTask.room_id == room.id,
                                    HousekeepingTask.booking_id == booking.id,
                                    HousekeepingTask.task_type == HousekeepingType.checkout,
                                    HousekeepingTask.status.in_([HousekeepingStatus.pending, HousekeepingStatus.in_progress])
                                )
                            ).first()
                            
                            if not existing_task:
                                # Default checklist items for checkout cleaning
                                checkout_checklist = [
                                    {'item': 'Bathroom cleaned', 'completed': False, 'notes': ''},
                                    {'item': 'Beds made with fresh linens', 'completed': False, 'notes': ''},
                                    {'item': 'Trash emptied', 'completed': False, 'notes': ''},
                                    {'item': 'Towels replaced', 'completed': False, 'notes': ''},
                                    {'item': 'Amenities restocked', 'completed': False, 'notes': ''},
                                    {'item': 'Floor vacuumed and mopped', 'completed': False, 'notes': ''},
                                    {'item': 'Surfaces dusted', 'completed': False, 'notes': ''},
                                    {'item': 'Windows and mirrors cleaned', 'completed': False, 'notes': ''},
                                ]
                                
                                housekeeping_task = HousekeepingTask(
                                    room_id=room.id,
                                    booking_id=booking.id,
                                    task_type=HousekeepingType.checkout,
                                    status=HousekeepingStatus.pending,
                                    scheduled_time=datetime.utcnow(),  # Schedule immediately
                                    created_by=current_user.id,
                                    checklist_items=checkout_checklist,
                                    notes=f'Auto-created on checkout for booking {booking.booking_number}',
                                    estimated_duration_minutes=45  # Default 45 minutes for checkout cleaning
                                )
                                db.add(housekeeping_task)
                                db.flush()  # Flush to get the task ID if needed for notifications
                                
                                # Send notification to all housekeeping users via dedicated notification system
                                try:
                                    from ...notifications.routes.notification_routes import notification_manager
                                    
                                    task_data_notification = {
                                        'id': housekeeping_task.id,
                                        'room_id': housekeeping_task.room_id,
                                        'room_number': room.room_number,
                                        'task_type': housekeeping_task.task_type.value,
                                        'status': housekeeping_task.status.value,
                                        'scheduled_time': housekeeping_task.scheduled_time.isoformat() if housekeeping_task.scheduled_time else None,
                                        'assigned_to': housekeeping_task.assigned_to,
                                        'booking_number': booking.booking_number,
                                        'created_at': housekeeping_task.created_at.isoformat() if housekeeping_task.created_at else None
                                    }
                                    notification_data = {
                                        'type': 'housekeeping_task_available',
                                        'data': task_data_notification
                                    }
                                    
                                    # Send notification to all housekeeping users
                                    await notification_manager.send_to_role('housekeeping', notification_data)
                                except Exception as e:
                                    logger.error(f'Error sending housekeeping notification: {str(e)}', exc_info=True)
                elif new_status == BookingStatus.cancelled:
                    # Update room status when booking is cancelled
                    if booking.payments:
                        for payment in booking.payments:
                            if payment.payment_status == PaymentStatus.pending:
                                payment.payment_status = PaymentStatus.failed
                                existing_notes = payment.notes or ''
                                cancellation_note = f'\nPayment cancelled due to booking cancellation on {datetime.utcnow().isoformat()}'
                                payment.notes = existing_notes + cancellation_note if existing_notes else cancellation_note.strip()
                                db.flush()
                    
                    # Check if room has other active bookings
                    if room:
                        active_booking = db.query(Booking).filter(
                            and_(
                                Booking.room_id == room.id,
                                Booking.id != booking.id,
                                Booking.status.in_([BookingStatus.confirmed, BookingStatus.checked_in]),
                                Booking.check_in_date <= datetime.utcnow(),
                                Booking.check_out_date > datetime.utcnow()
                            )
                        ).first()
                        
                        if not active_booking:
                            # Check for maintenance
                            from ...rooms.models.room_maintenance import RoomMaintenance, MaintenanceStatus
                            active_maintenance = db.query(RoomMaintenance).filter(
                                and_(
                                    RoomMaintenance.room_id == room.id,
                                    RoomMaintenance.blocks_room == True,
                                    RoomMaintenance.status.in_([MaintenanceStatus.scheduled, MaintenanceStatus.in_progress])
                                )
                            ).first()
                            
                            if active_maintenance:
                                room.status = RoomStatus.maintenance
                            else:
                                room.status = RoomStatus.available
            except ValueError:
                raise HTTPException(status_code=400, detail='Invalid status')
        
        # Update other fields from schema if provided
        if booking_data.check_in_date is not None:
            if 'T' in booking_data.check_in_date or 'Z' in booking_data.check_in_date or '+' in booking_data.check_in_date:
                booking.check_in_date = datetime.fromisoformat(booking_data.check_in_date.replace('Z', '+00:00'))
            else:
                booking.check_in_date = datetime.strptime(booking_data.check_in_date, '%Y-%m-%d')
        
        if booking_data.check_out_date is not None:
            if 'T' in booking_data.check_out_date or 'Z' in booking_data.check_out_date or '+' in booking_data.check_out_date:
                booking.check_out_date = datetime.fromisoformat(booking_data.check_out_date.replace('Z', '+00:00'))
            else:
                booking.check_out_date = datetime.strptime(booking_data.check_out_date, '%Y-%m-%d')
        
        if booking_data.guest_count is not None:
            booking.num_guests = booking_data.guest_count
        
        if booking_data.notes is not None:
            # Sanitize user-provided notes to prevent XSS
            from html import escape
            booking.special_requests = escape(booking_data.notes)
        
        # Restrict staff from modifying booking prices (only admin can)
        if booking_data.total_price is not None:
            from ...shared.utils.role_helpers import is_admin
            if not is_admin(current_user, db):
                raise HTTPException(
                    status_code=403,
                    detail='Staff members cannot modify booking prices. Please contact an administrator.'
                )
            booking.total_price = booking_data.total_price
        
        db.commit()
        
        # SECURITY: Log booking status changes for audit trail (especially cancellations)
        if status_value and old_status != booking.status:
            try:
                new_status_value = booking.status.value if hasattr(booking.status, 'value') else str(booking.status)
                action_name = 'booking_status_changed'
                if new_status == BookingStatus.cancelled:
                    action_name = 'booking_cancelled_by_admin'
                
                await audit_service.log_action(
                    db=db,
                    action=action_name,
                    resource_type='booking',
                    user_id=current_user.id,
                    resource_id=booking.id,
                    ip_address=client_ip,
                    user_agent=user_agent,
                    request_id=request_id,
                    details={
                        'booking_number': booking.booking_number,
                        'old_status': old_status_value,
                        'new_status': new_status_value,
                        'booking_user_id': booking.user_id,
                        'room_id': booking.room_id if booking.room else None,
                        'total_price': float(booking.total_price) if booking.total_price else 0.0,
                        'changed_by': 'admin' if current_user.role and current_user.role.name == 'admin' else 'staff'
                    },
                    status='success'
                )
            except Exception as e:
                import logging
                logger = logging.getLogger(__name__)
                logger.warning(f'Failed to log booking status change audit: {e}')
        
        # Send booking confirmation notification if status changed to confirmed
        if new_status == BookingStatus.confirmed:
            try:
                from ...notifications.services.notification_service import NotificationService
                NotificationService.send_booking_confirmation(db, booking)
            except Exception as e:
                import logging
                logger = logging.getLogger(__name__)
                logger.warning(f'Failed to send booking confirmation notification: {e}')
        
        # Reload booking with all relationships after commit
        booking = db.query(Booking).options(
            selectinload(Booking.payments),
            joinedload(Booking.user),
            joinedload(Booking.room).joinedload(Room.room_type)
        ).filter(Booking.id == id).first()
        
        if not booking:
            raise HTTPException(status_code=404, detail='Booking not found after update')
        
        payment_warning = None
        if status_value and old_status != booking.status and (booking.status == BookingStatus.checked_in):
            payment_balance = calculate_booking_payment_balance(booking)
            if payment_balance['remaining_balance'] > 0.01:
                payment_warning = {'message': f'Guest has not fully paid. Remaining balance: {payment_balance['remaining_balance']:.2f}', 'total_paid': payment_balance['total_paid'], 'total_price': payment_balance['total_price'], 'remaining_balance': payment_balance['remaining_balance'], 'payment_percentage': payment_balance['payment_percentage']}
        if status_value and old_status != booking.status:
            if booking.status in [BookingStatus.confirmed, BookingStatus.cancelled]:
                try:
                    from ...system.models.system_settings import SystemSettings
                    client_url_setting = db.query(SystemSettings).filter(SystemSettings.key == 'client_url').first()
                    client_url = client_url_setting.value if client_url_setting and client_url_setting.value else settings.CLIENT_URL or os.getenv('CLIENT_URL', 'http://localhost:5173')
                    if booking.status == BookingStatus.confirmed:
                        room = booking.room
                        room_type_name = room.room_type.name if room and room.room_type else 'Room'
                        currency_setting = db.query(SystemSettings).filter(SystemSettings.key == 'platform_currency').first()
                        currency = currency_setting.value if currency_setting and currency_setting.value else 'USD'
                        currency_symbol = get_currency_symbol(currency)
                        guest_name = booking.user.full_name if booking.user else 'Guest'
                        guest_email = booking.user.email if booking.user else None
                        email_html = booking_confirmation_email_template(booking_number=booking.booking_number, guest_name=guest_name, room_number=room.room_number if room else 'N/A', room_type=room_type_name, check_in=booking.check_in_date.strftime('%B %d, %Y') if booking.check_in_date else 'N/A', check_out=booking.check_out_date.strftime('%B %d, %Y') if booking.check_out_date else 'N/A', num_guests=booking.num_guests, total_price=float(booking.total_price), requires_deposit=booking.requires_deposit, deposit_amount=float(booking.total_price) * 0.2 if booking.requires_deposit else None, original_price=float(booking.original_price) if booking.original_price else None, discount_amount=float(booking.discount_amount) if booking.discount_amount else None, promotion_code=booking.promotion_code, client_url=client_url, currency_symbol=currency_symbol)
                        if guest_email:
                            await send_email(to=guest_email, subject=f'Booking Confirmed - {booking.booking_number}', html=email_html)
                        
                        # Award loyalty points for confirmed booking
                        if booking.user:
                            try:
                                # Check if booking already earned points
                                from ...loyalty.models.loyalty_point_transaction import LoyaltyPointTransaction, TransactionSource
                                existing_points = db.query(LoyaltyPointTransaction).filter(
                                    LoyaltyPointTransaction.booking_id == booking.id,
                                    LoyaltyPointTransaction.source == TransactionSource.booking
                                ).first()
                                
                                if not existing_points:
                                    # Award points based on total price paid
                                    total_paid = sum(float(p.amount) for p in booking.payments if p.payment_status == PaymentStatus.completed)
                                    if total_paid > 0:
                                        LoyaltyService.earn_points_from_booking(db, booking.user_id, booking, total_paid)
                                    
                                    # Process referral if applicable - referral is already processed when booking is created
                                    # This section is for backward compatibility with existing referrals
                                    if booking.user:
                                        user_loyalty = db.query(UserLoyalty).filter(UserLoyalty.user_id == booking.user_id).first()
                                        if user_loyalty and user_loyalty.referral_code:
                                            # Check if there's a referral for this user that hasn't been rewarded yet
                                            from ...loyalty.models.referral import Referral
                                            referral = db.query(Referral).filter(
                                                Referral.referred_user_id == booking.user_id,
                                                Referral.booking_id == booking.id,
                                                Referral.status.in_([ReferralStatus.pending, ReferralStatus.completed])
                                            ).first()
                                            if referral and referral.status == ReferralStatus.pending:
                                                # Award points now that booking is confirmed
                                                LoyaltyService.process_referral(db, booking.user_id, referral.referral_code, booking.id)
                            except Exception as loyalty_error:
                                import logging
                                logger = logging.getLogger(__name__)
                                logger.error(f'Failed to award loyalty points: {loyalty_error}')
                    elif booking.status == BookingStatus.cancelled:
                        guest_name = booking.user.full_name if booking.user else 'Guest'
                        guest_email = booking.user.email if booking.user else None
                        email_html = booking_status_changed_email_template(booking_number=booking.booking_number, guest_name=guest_name, status='cancelled', client_url=client_url)
                        if guest_email:
                            await send_email(to=guest_email, subject=f'Booking Cancelled - {booking.booking_number}', html=email_html)
                except Exception as e:
                    import logging
                    logger = logging.getLogger(__name__)
                    logger.error(f'Failed to send status change email: {e}')
                    import traceback
                    logger.error(traceback.format_exc())
        
        # Build response with booking data
        booking_dict = {
            'id': booking.id,
            'booking_number': booking.booking_number,
            'status': booking.status.value if isinstance(booking.status, BookingStatus) else booking.status,
        }
        
        message = 'Booking updated successfully. ⚠️ Payment reminder: Guest has remaining balance.' if payment_warning else 'Booking updated successfully'
        response_data = success_response(data={'booking': booking_dict}, message=message)
        if payment_warning:
            response_data['warning'] = payment_warning
        return response_data
    except HTTPException:
        raise
    except Exception as e:
        import logging
        import traceback
        logger = logging.getLogger(__name__)
        request_id = getattr(current_user, 'request_id', None) if hasattr(current_user, 'request_id') else None
        logger.error(f'Error updating booking {id}: {str(e)}', extra={'request_id': request_id, 'booking_id': id, 'user_id': current_user.id}, exc_info=True)
        logger.error(traceback.format_exc())
        db.rollback()
        raise HTTPException(status_code=500, detail='An error occurred while updating booking')

@router.get('/check/{booking_number}')
async def check_booking_by_number(booking_number: str, db: Session=Depends(get_db)):
    try:
        booking = db.query(Booking).options(selectinload(Booking.payments), joinedload(Booking.user), joinedload(Booking.room).joinedload(Room.room_type)).filter(Booking.booking_number == booking_number).first()
        if not booking:
            raise HTTPException(status_code=404, detail='Booking not found')
        payment_method_from_payments = None
        payment_status_from_payments = 'unpaid'
        if booking.payments:
            latest_payment = max(booking.payments, key=lambda p: p.created_at if p.created_at else datetime.min)
            if isinstance(latest_payment.payment_method, PaymentMethod):
                payment_method_from_payments = latest_payment.payment_method.value
            elif hasattr(latest_payment.payment_method, 'value'):
                payment_method_from_payments = latest_payment.payment_method.value
            else:
                payment_method_from_payments = str(latest_payment.payment_method)
            if latest_payment.payment_status == PaymentStatus.completed:
                payment_status_from_payments = 'paid'
            elif latest_payment.payment_status == PaymentStatus.refunded:
                payment_status_from_payments = 'refunded'
        booking_dict = {'id': booking.id, 'booking_number': booking.booking_number, 'user_id': booking.user_id, 'room_id': booking.room_id, 'check_in_date': booking.check_in_date.strftime('%Y-%m-%d') if booking.check_in_date else None, 'check_out_date': booking.check_out_date.strftime('%Y-%m-%d') if booking.check_out_date else None, 'num_guests': booking.num_guests, 'guest_count': booking.num_guests, 'total_price': float(booking.total_price) if booking.total_price else 0.0, 'original_price': float(booking.original_price) if booking.original_price else None, 'discount_amount': float(booking.discount_amount) if booking.discount_amount else None, 'promotion_code': booking.promotion_code, 'status': booking.status.value if isinstance(booking.status, BookingStatus) else booking.status, 'payment_method': payment_method_from_payments if payment_method_from_payments else 'cash', 'payment_status': payment_status_from_payments, 'deposit_paid': booking.deposit_paid, 'requires_deposit': booking.requires_deposit, 'special_requests': booking.special_requests, 'notes': booking.special_requests, 'created_at': booking.created_at.isoformat() if booking.created_at else None, 'createdAt': booking.created_at.isoformat() if booking.created_at else None, 'updated_at': booking.updated_at.isoformat() if booking.updated_at else None, 'updatedAt': booking.updated_at.isoformat() if booking.updated_at else None}
        if booking.user:
            booking_dict['user'] = {'id': booking.user.id, 'name': booking.user.full_name, 'full_name': booking.user.full_name, 'email': booking.user.email, 'phone': booking.user.phone, 'phone_number': booking.user.phone}
        if booking.room:
            booking_dict['room'] = {'id': booking.room.id, 'room_number': booking.room.room_number, 'floor': booking.room.floor}
            if booking.room.room_type:
                booking_dict['room']['room_type'] = {'id': booking.room.room_type.id, 'name': booking.room.room_type.name, 'base_price': float(booking.room.room_type.base_price) if booking.room.room_type.base_price else 0.0, 'capacity': booking.room.room_type.capacity}
        if booking.payments:
            booking_dict['payments'] = [{'id': p.id, 'amount': float(p.amount) if p.amount else 0.0, 'payment_method': p.payment_method.value if isinstance(p.payment_method, PaymentMethod) else p.payment_method.value if hasattr(p.payment_method, 'value') else str(p.payment_method), 'payment_type': p.payment_type.value if isinstance(p.payment_type, PaymentType) else p.payment_type.value if hasattr(p.payment_type, 'value') else str(p.payment_type), 'payment_status': p.payment_status.value if isinstance(p.payment_status, PaymentStatus) else p.payment_status, 'transaction_id': p.transaction_id, 'payment_date': p.payment_date.isoformat() if p.payment_date else None, 'created_at': p.created_at.isoformat() if p.created_at else None} for p in booking.payments]
        else:
            booking_dict['payments'] = []
        payment_balance = calculate_booking_payment_balance(booking)
        booking_dict['payment_balance'] = {'total_paid': payment_balance['total_paid'], 'total_price': payment_balance['total_price'], 'remaining_balance': payment_balance['remaining_balance'], 'is_fully_paid': payment_balance['is_fully_paid'], 'payment_percentage': payment_balance['payment_percentage']}
        response_data = success_response(data={'booking': booking_dict})
        if payment_balance['remaining_balance'] > 0.01:
            response_data['warning'] = {'message': f'Guest has not fully paid. Remaining balance: {payment_balance['remaining_balance']:.2f}', 'remaining_balance': payment_balance['remaining_balance'], 'payment_percentage': payment_balance['payment_percentage']}
        return response_data
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.post('/admin-create', dependencies=[Depends(authorize_roles('admin', 'staff'))])
async def admin_create_booking(booking_data: AdminCreateBookingRequest, current_user: User=Depends(authorize_roles('admin', 'staff')), db: Session=Depends(get_db)):
    """Create a booking on behalf of a user (admin/staff only)"""
    import logging
    logger = logging.getLogger(__name__)
    
    # Start transaction
    transaction = db.begin()
    try:
        # Extract validated data from Pydantic model
        user_id = booking_data.user_id
        room_id = booking_data.room_id
        check_in_date = booking_data.check_in_date
        check_out_date = booking_data.check_out_date
        total_price = booking_data.total_price
        guest_count = booking_data.guest_count
        notes = booking_data.notes
        payment_method = booking_data.payment_method
        payment_status = booking_data.payment_status
        promotion_code = booking_data.promotion_code
        status = booking_data.status
        services = booking_data.services or []
        invoice_info = booking_data.invoice_info.dict() if booking_data.invoice_info else {}
        
        # Verify user exists
        target_user = db.query(User).filter(User.id == user_id).first()
        if not target_user:
            transaction.rollback()
            raise HTTPException(status_code=404, detail='User not found')
        
        logger.info(f'Admin/Staff {current_user.id} creating booking for user {user_id}')
        
        # Lock room row to prevent race conditions
        room = db.query(Room).filter(Room.id == room_id).with_for_update().first()
        if not room:
            transaction.rollback()
            raise HTTPException(status_code=404, detail='Room not found')
        
        # Parse dates (already validated by Pydantic)
        if 'T' in check_in_date or 'Z' in check_in_date or '+' in check_in_date:
            check_in = datetime.fromisoformat(check_in_date.replace('Z', '+00:00'))
        else:
            check_in = datetime.strptime(check_in_date, '%Y-%m-%d')
        if 'T' in check_out_date or 'Z' in check_out_date or '+' in check_out_date:
            check_out = datetime.fromisoformat(check_out_date.replace('Z', '+00:00'))
        else:
            check_out = datetime.strptime(check_out_date, '%Y-%m-%d')
        
        # Check for overlapping bookings with row-level locking
        overlapping = db.query(Booking).filter(
            and_(
                Booking.room_id == room_id,
                Booking.status != BookingStatus.cancelled,
                Booking.check_in_date < check_out,
                Booking.check_out_date > check_in
            )
        ).with_for_update().first()
        if overlapping:
            transaction.rollback()
            raise HTTPException(status_code=409, detail='Room already booked for the selected dates')
        
        booking_number = generate_booking_number()
        # Calculate room price first (needed for deposit calculation)
        room_price = float(room.price) if room.price and room.price > 0 else float(room.room_type.base_price) if room.room_type else 0.0
        number_of_nights = (check_out - check_in).days
        if number_of_nights <= 0:
            number_of_nights = 1
        room_total = room_price * number_of_nights
        
        # Calculate services total if any
        services_total = 0.0
        if services:
            from ...hotel_services.models.service import Service
            for service_item in services:
                service_id = service_item.service_id
                quantity = service_item.quantity
                service = db.query(Service).filter(Service.id == service_id).first()
                if service and service.is_active:
                    services_total += float(service.price) * quantity
        
        original_price = room_total + services_total
        
        # Validate and use calculated price
        calculated_total = original_price
        provided_total = float(total_price) if total_price else 0.0
        
        if promotion_code:
            discount_amount = max(0.0, original_price - provided_total)
            final_total_price = provided_total
        else:
            if abs(calculated_total - provided_total) > 0.01:
                logger.warning(f'Price mismatch: calculated={calculated_total}, provided={provided_total}. Using calculated price.')
                final_total_price = calculated_total
            else:
                final_total_price = provided_total
            discount_amount = 0.0
        
        # Determine deposit requirements based on payment_status (use final_total_price)
        if payment_status == 'deposit':
            requires_deposit = True
            deposit_percentage = 20
            deposit_amount = final_total_price * 0.2
        elif payment_status == 'full':
            requires_deposit = False
            deposit_percentage = 0
            deposit_amount = 0
        else:  # unpaid
            requires_deposit = payment_method == 'cash'
            deposit_percentage = 20 if requires_deposit else 0
            deposit_amount = final_total_price * deposit_percentage / 100 if requires_deposit else 0
        
        # Set initial status (admin can set it directly)
        try:
            initial_status = BookingStatus(status) if status else BookingStatus.confirmed
        except ValueError:
            initial_status = BookingStatus.confirmed
        
        # Sanitize user-provided notes to prevent XSS
        from html import escape
        final_notes = escape(notes) if notes else ''
        if promotion_code:
            promotion_note = f'Promotion Code: {promotion_code}'
            final_notes = f'{promotion_note}\n{final_notes}'.strip() if final_notes else promotion_note
        
        # Add admin note
        admin_note = f'Created by {current_user.full_name} (Admin/Staff)'
        final_notes = f'{admin_note}\n{final_notes}'.strip() if final_notes else admin_note
        
        # Determine deposit_paid status
        deposit_paid = payment_status == 'deposit' or payment_status == 'full'
        
        # Create booking
        booking = Booking(
            booking_number=booking_number,
            user_id=user_id,
            room_id=room_id,
            check_in_date=check_in,
            check_out_date=check_out,
            num_guests=guest_count,
            total_price=final_total_price,
            original_price=original_price if promotion_code else None,
            discount_amount=discount_amount if promotion_code and discount_amount > 0 else None,
            promotion_code=promotion_code,
            special_requests=final_notes,
            status=initial_status,
            requires_deposit=requires_deposit,
            deposit_paid=deposit_paid
        )
        db.add(booking)
        db.flush()
        
        # Create payment records based on payment_status
        from ...payments.models.payment import Payment, PaymentMethod, PaymentStatus, PaymentType
        from datetime import datetime as dt
        
        # Determine payment method enum
        if payment_method == 'stripe':
            payment_method_enum = PaymentMethod.stripe
        elif payment_method == 'paypal':
            payment_method_enum = PaymentMethod.paypal
        else:
            payment_method_enum = PaymentMethod.cash
        
        # Handle payment creation based on payment_status
        if payment_status == 'full':
            # Fully paid - create a single completed payment
            full_payment = Payment(
                booking_id=booking.id,
                amount=final_total_price,
                payment_method=payment_method_enum,
                payment_type=PaymentType.full,
                payment_status=PaymentStatus.completed,
                payment_date=dt.utcnow(),
                notes=f'Payment received at booking creation by {current_user.full_name}'
            )
            db.add(full_payment)
            db.flush()
        elif payment_status == 'deposit':
            # Deposit only - create completed deposit payment and pending remaining payment
            deposit_payment = Payment(
                booking_id=booking.id,
                amount=deposit_amount,
                payment_method=payment_method_enum,
                payment_type=PaymentType.deposit,
                deposit_percentage=deposit_percentage,
                payment_status=PaymentStatus.completed,
                payment_date=dt.utcnow(),
                notes=f'Deposit received at booking creation by {current_user.full_name}'
            )
            db.add(deposit_payment)
            db.flush()
            
            # Create pending payment for remaining amount
            remaining_amount = final_total_price - deposit_amount
            if remaining_amount > 0:
                remaining_payment = Payment(
                    booking_id=booking.id,
                    amount=remaining_amount,
                    payment_method=payment_method_enum,
                    payment_type=PaymentType.remaining,
                    payment_status=PaymentStatus.pending,
                    payment_date=None,
                    notes='Remaining balance to be paid at check-in'
                )
                db.add(remaining_payment)
                db.flush()
        else:
            # Unpaid - create pending payment(s)
            if requires_deposit and deposit_amount > 0:
                # Create pending deposit payment
                deposit_payment = Payment(
                    booking_id=booking.id,
                    amount=deposit_amount,
                    payment_method=payment_method_enum,
                    payment_type=PaymentType.deposit,
                    deposit_percentage=deposit_percentage,
                    payment_status=PaymentStatus.pending,
                    payment_date=None,
                    notes='Deposit to be paid at check-in'
                )
                db.add(deposit_payment)
                db.flush()
                
                # Create pending payment for remaining amount
                remaining_amount = final_total_price - deposit_amount
                if remaining_amount > 0:
                    remaining_payment = Payment(
                        booking_id=booking.id,
                        amount=remaining_amount,
                        payment_method=payment_method_enum,
                        payment_type=PaymentType.remaining,
                        payment_status=PaymentStatus.pending,
                        payment_date=None,
                        notes='Remaining balance to be paid at check-in'
                    )
                    db.add(remaining_payment)
                    db.flush()
            else:
                # Create single pending full payment
                full_payment = Payment(
                    booking_id=booking.id,
                    amount=final_total_price,
                    payment_method=payment_method_enum,
                    payment_type=PaymentType.full,
                    payment_status=PaymentStatus.pending,
                    payment_date=None,
                    notes='Payment to be made at check-in'
                )
                db.add(full_payment)
                db.flush()
        
        # Add service usages if any
        if services:
            from ...hotel_services.models.service import Service
            for service_item in services:
                service_id = service_item.service_id
                quantity = service_item.quantity
                service = db.query(Service).filter(Service.id == service_id).first()
                if not service or not service.is_active:
                    continue
                unit_price = float(service.price)
                total_price_service = unit_price * quantity
                service_usage = ServiceUsage(
                    booking_id=booking.id,
                    service_id=service_id,
                    quantity=quantity,
                    unit_price=unit_price,
                    total_price=total_price_service
                )
                db.add(service_usage)
        
        # Commit transaction
        transaction.commit()
        db.refresh(booking)
        
        # Load booking with relationships
        from sqlalchemy.orm import joinedload, selectinload
        booking = db.query(Booking).options(
            joinedload(Booking.payments),
            selectinload(Booking.service_usages).selectinload(ServiceUsage.service),
            joinedload(Booking.user),
            joinedload(Booking.room).joinedload(Room.room_type)
        ).filter(Booking.id == booking.id).first()
        
        # Build response
        payment_method_from_payments = None
        payment_status_from_payments = 'unpaid'
        if booking.payments:
            latest_payment = sorted(booking.payments, key=lambda p: p.created_at, reverse=True)[0]
            if isinstance(latest_payment.payment_method, PaymentMethod):
                payment_method_from_payments = latest_payment.payment_method.value
            elif hasattr(latest_payment.payment_method, 'value'):
                payment_method_from_payments = latest_payment.payment_method.value
            else:
                payment_method_from_payments = str(latest_payment.payment_method)
            if latest_payment.payment_status == PaymentStatus.completed:
                payment_status_from_payments = 'paid'
            elif latest_payment.payment_status == PaymentStatus.refunded:
                payment_status_from_payments = 'refunded'
        
        final_payment_method = payment_method_from_payments if payment_method_from_payments else payment_method
        
        booking_dict = {
            'id': booking.id,
            'booking_number': booking.booking_number,
            'user_id': booking.user_id,
            'room_id': booking.room_id,
            'check_in_date': booking.check_in_date.strftime('%Y-%m-%d') if booking.check_in_date else None,
            'check_out_date': booking.check_out_date.strftime('%Y-%m-%d') if booking.check_out_date else None,
            'guest_count': booking.num_guests,
            'total_price': float(booking.total_price) if booking.total_price else 0.0,
            'status': booking.status.value if isinstance(booking.status, BookingStatus) else booking.status,
            'payment_method': final_payment_method,
            'payment_status': payment_status_from_payments,
            'deposit_paid': booking.deposit_paid,
            'requires_deposit': booking.requires_deposit,
            'notes': booking.special_requests,
            'guest_info': {
                'full_name': booking.user.full_name if booking.user else '',
                'email': booking.user.email if booking.user else '',
                'phone': booking.user.phone if booking.user else ''
            },
            'createdAt': booking.created_at.isoformat() if booking.created_at else None,
            'updatedAt': booking.updated_at.isoformat() if booking.updated_at else None,
            'created_at': booking.created_at.isoformat() if booking.created_at else None
        }
        
        if booking.payments:
            booking_dict['payments'] = [
                {
                    'id': p.id,
                    'booking_id': p.booking_id,
                    'amount': float(p.amount) if p.amount else 0.0,
                    'payment_method': p.payment_method.value if isinstance(p.payment_method, PaymentMethod) else p.payment_method.value if hasattr(p.payment_method, 'value') else str(p.payment_method),
                    'payment_type': p.payment_type.value if isinstance(p.payment_type, PaymentType) else p.payment_type,
                    'deposit_percentage': p.deposit_percentage,
                    'payment_status': p.payment_status.value if isinstance(p.payment_status, PaymentStatus) else p.payment_status,
                    'transaction_id': p.transaction_id,
                    'payment_date': p.payment_date.isoformat() if p.payment_date else None,
                    'notes': p.notes,
                    'created_at': p.created_at.isoformat() if p.created_at else None
                } for p in booking.payments
            ]
        
        service_usages = getattr(booking, 'service_usages', None)
        if service_usages and len(service_usages) > 0:
            booking_dict['service_usages'] = [
                {
                    'id': su.id,
                    'service_id': su.service_id,
                    'service_name': su.service.name if hasattr(su, 'service') and su.service else 'Unknown Service',
                    'quantity': su.quantity,
                    'unit_price': float(su.unit_price) if su.unit_price else 0.0,
                    'total_price': float(su.total_price) if su.total_price else 0.0
                } for su in service_usages
            ]
        else:
            booking_dict['service_usages'] = []
        
        if booking.room:
            booking_dict['room'] = {
                'id': booking.room.id,
                'room_number': booking.room.room_number,
                'floor': booking.room.floor
            }
            if booking.room.room_type:
                booking_dict['room']['room_type'] = {
                    'id': booking.room.room_type.id,
                    'name': booking.room.room_type.name,
                    'base_price': float(booking.room.room_type.base_price) if booking.room.room_type.base_price else 0.0,
                    'capacity': booking.room.room_type.capacity
                }
        
        return success_response(
            data={'booking': booking_dict},
            message=f'Booking created successfully by {current_user.full_name}'
        )
    except HTTPException:
        if 'transaction' in locals():
            try:
                transaction.rollback()
            except Exception:
                pass
        raise
    except IntegrityError as e:
        if 'transaction' in locals():
            try:
                transaction.rollback()
            except Exception:
                pass
        logger.error(f'Database integrity error during admin booking creation: {str(e)}')
        raise HTTPException(status_code=409, detail='Booking conflict detected. Please try again.')
    except Exception as e:
        if 'transaction' in locals():
            try:
                transaction.rollback()
            except Exception:
                pass
        logger.error(f'Error creating booking (admin/staff): {str(e)}', exc_info=True)
        raise HTTPException(status_code=500, detail='An error occurred while creating the booking. Please try again.')