from fastapi import APIRouter, Depends, HTTPException, status, Query
from sqlalchemy.orm import Session, joinedload, selectinload
from sqlalchemy import and_, or_
from typing import Optional
from datetime import datetime
import random
import os
from ..config.database import get_db
from ..config.settings import settings
from ..middleware.auth import get_current_user, authorize_roles
from ..models.user import User
from ..models.role import Role
from ..models.booking import Booking, BookingStatus
from ..models.room import Room, RoomStatus
from ..models.room_type import RoomType
from ..models.payment import Payment, PaymentMethod, PaymentType, PaymentStatus
from ..models.service_usage import ServiceUsage
from ..services.room_service import normalize_images, get_base_url
from fastapi import Request
from ..utils.mailer import send_email
from ..utils.email_templates import booking_confirmation_email_template, booking_status_changed_email_template
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)
    rand = random.randint(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:
        query = db.query(Booking).options(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)
        total = query.count()
        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 {'status': 'success', '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': True, 'data': {'bookings': result}}
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.post('/')
async def create_booking(booking_data: dict, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
    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')
    try:
        import logging
        logger = logging.getLogger(__name__)
        if not isinstance(booking_data, dict):
            logger.error(f'Invalid booking_data type: {type(booking_data)}, value: {booking_data}')
            raise HTTPException(status_code=400, detail='Invalid request body. Expected JSON object.')
        logger.info(f'Received booking request from user {current_user.id}: {booking_data}')
        room_id = booking_data.get('room_id')
        check_in_date = booking_data.get('check_in_date')
        check_out_date = booking_data.get('check_out_date')
        total_price = booking_data.get('total_price')
        guest_count = booking_data.get('guest_count', 1)
        notes = booking_data.get('notes')
        payment_method = booking_data.get('payment_method', 'cash')
        promotion_code = booking_data.get('promotion_code')
        invoice_info = booking_data.get('invoice_info', {})
        missing_fields = []
        if not room_id:
            missing_fields.append('room_id')
        if not check_in_date:
            missing_fields.append('check_in_date')
        if not check_out_date:
            missing_fields.append('check_out_date')
        if total_price is None:
            missing_fields.append('total_price')
        if missing_fields:
            error_msg = f'Missing required booking fields: {', '.join(missing_fields)}'
            logger.error(error_msg)
            raise HTTPException(status_code=400, detail=error_msg)
        room = db.query(Room).filter(Room.id == room_id).first()
        if not room:
            raise HTTPException(status_code=404, detail='Room not found')
        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')
        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)).first()
        if overlapping:
            raise HTTPException(status_code=409, detail='Room already booked for the selected dates')
        booking_number = generate_booking_number()
        requires_deposit = payment_method == 'cash'
        deposit_percentage = 20 if requires_deposit else 0
        deposit_amount = float(total_price) * deposit_percentage / 100 if requires_deposit else 0
        initial_status = BookingStatus.pending
        if payment_method in ['stripe', 'paypal']:
            initial_status = BookingStatus.pending
        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
        room_total = room_price * number_of_nights
        services = booking_data.get('services', [])
        services_total = 0.0
        if services:
            from ..models.service import Service
            for service_item in services:
                service_id = service_item.get('service_id')
                quantity = service_item.get('quantity', 1)
                if service_id:
                    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
        discount_amount = max(0.0, original_price - float(total_price)) if promotion_code else 0.0
        final_notes = notes or ''
        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=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()
        if payment_method in ['stripe', 'paypal']:
            from ..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=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 ..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}%')
        services = booking_data.get('services', [])
        if services:
            from ..models.service import Service
            for service_item in services:
                service_id = service_item.get('service_id')
                quantity = service_item.get('quantity', 1)
                if not service_id:
                    continue
                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)
        db.commit()
        db.refresh(booking)
        try:
            from ..services.invoice_service import InvoiceService
            from ..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 ..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 = float(total_price) * 0.2
                remaining_amount = float(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}
        return {'success': True, 'data': {'booking': booking_dict}, 'message': f'Booking created. Please pay {deposit_percentage}% deposit to confirm.' if requires_deposit else 'Booking created successfully'}
    except HTTPException:
        raise
    except Exception as e:
        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()}')
        db.rollback()
        raise HTTPException(status_code=500, detail=str(e))

@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')
        if current_user.role_id != 1 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': True, 'data': {'booking': booking_dict}}
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@router.patch('/{id}/cancel')
async def cancel_booking(id: int, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
    try:
        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')
        if booking.status == BookingStatus.confirmed:
            raise HTTPException(status_code=400, detail='Cannot cancel a confirmed booking. Please contact support for assistance.')
        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.')
        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
        if payments_updated > 0:
            db.flush()
        db.commit()
        try:
            from ..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': True, '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'))])
async def update_booking(id: int, booking_data: dict, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
    try:
        booking = db.query(Booking).options(selectinload(Booking.payments)).filter(Booking.id == id).first()
        if not booking:
            raise HTTPException(status_code=404, detail='Booking not found')
        old_status = booking.status
        status_value = booking_data.get('status')
        if status_value:
            try:
                new_status = BookingStatus(status_value)
                booking.status = new_status
                if new_status == BookingStatus.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()
            except ValueError:
                raise HTTPException(status_code=400, detail='Invalid status')
        db.commit()
        db.refresh(booking)
        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 ..models.system_settings import SystemSettings
                    from ..services.room_service import get_base_url
                    from fastapi import Request
                    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:
                        from sqlalchemy.orm import selectinload
                        booking_with_room = db.query(Booking).options(selectinload(Booking.room).selectinload(Room.room_type)).filter(Booking.id == booking.id).first()
                        room = booking_with_room.room if booking_with_room else None
                        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_symbols = {'USD': '$', 'EUR': '€', 'GBP': '£', 'JPY': '¥', 'CNY': '¥', 'KRW': '₩', 'SGD': 'S$', 'THB': '฿', 'AUD': 'A$', 'CAD': 'C$', 'VND': '₫', 'INR': '₹', 'CHF': 'CHF', 'NZD': 'NZ$'}
                        currency_symbol = currency_symbols.get(currency, currency)
                        email_html = booking_confirmation_email_template(booking_number=booking.booking_number, guest_name=booking.user.full_name if booking.user else 'Guest', 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)
                        await send_email(to=booking.user.email if booking.user else None, subject=f'Booking Confirmed - {booking.booking_number}', html=email_html)
                    elif booking.status == BookingStatus.cancelled:
                        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 status change email: {e}')
        response_data = {'status': 'success', 'message': 'Booking updated successfully', 'data': {'booking': booking}}
        if payment_warning:
            response_data['warning'] = payment_warning
            response_data['message'] = 'Booking updated successfully. ⚠️ Payment reminder: Guest has remaining balance.'
        return response_data
    except HTTPException:
        raise
    except Exception as e:
        db.rollback()
        raise HTTPException(status_code=500, detail=str(e))

@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 = {'status': 'success', '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))