import logging
from paypalcheckoutsdk.core import PayPalHttpClient, SandboxEnvironment, LiveEnvironment
from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersGetRequest, OrdersCaptureRequest
from paypalcheckoutsdk.payments import CapturesRefundRequest
from typing import Optional, Dict, Any
from ...shared.config.settings import settings
from ...shared.config.logging_config import get_logger
from ..models.payment import Payment, PaymentMethod, PaymentType, PaymentStatus
from ...bookings.models.booking import Booking, BookingStatus
from ...system.models.system_settings import SystemSettings
from sqlalchemy.orm import Session
from datetime import datetime
import json
logger = get_logger(__name__)

def get_paypal_client_id(db: Session) -> Optional[str]:
    try:
        setting = db.query(SystemSettings).filter(SystemSettings.key == 'paypal_client_id').first()
        if setting and setting.value:
            return setting.value
    except Exception:
        pass
    return settings.PAYPAL_CLIENT_ID if settings.PAYPAL_CLIENT_ID else None

def get_paypal_client_secret(db: Session) -> Optional[str]:
    try:
        setting = db.query(SystemSettings).filter(SystemSettings.key == 'paypal_client_secret').first()
        if setting and setting.value:
            return setting.value
    except Exception:
        pass
    return settings.PAYPAL_CLIENT_SECRET if settings.PAYPAL_CLIENT_SECRET else None

def get_paypal_mode(db: Session) -> str:
    try:
        setting = db.query(SystemSettings).filter(SystemSettings.key == 'paypal_mode').first()
        if setting and setting.value:
            return setting.value
    except Exception:
        pass
    return settings.PAYPAL_MODE if settings.PAYPAL_MODE else 'sandbox'

def get_paypal_client(db: Optional[Session]=None) -> PayPalHttpClient:
    client_id = None
    client_secret = None
    mode = 'sandbox'
    if db:
        client_id = get_paypal_client_id(db)
        client_secret = get_paypal_client_secret(db)
        mode = get_paypal_mode(db)
    if not client_id:
        client_id = settings.PAYPAL_CLIENT_ID
    if not client_secret:
        client_secret = settings.PAYPAL_CLIENT_SECRET
    if not mode:
        mode = settings.PAYPAL_MODE or 'sandbox'
    if not client_id or not client_secret:
        raise ValueError('PayPal credentials are not configured')
    if mode.lower() == 'live':
        environment = LiveEnvironment(client_id=client_id, client_secret=client_secret)
    else:
        environment = SandboxEnvironment(client_id=client_id, client_secret=client_secret)
    return PayPalHttpClient(environment)

class PayPalService:

    @staticmethod
    def create_order(amount: float, currency: str='USD', metadata: Optional[Dict[str, Any]]=None, db: Optional[Session]=None) -> Dict[str, Any]:
        client = get_paypal_client(db)
        if amount <= 0:
            raise ValueError('Amount must be greater than 0')
        if amount > 100000:
            raise ValueError(f"Amount ${amount:,.2f} exceeds PayPal's maximum of $100,000")
        request = OrdersCreateRequest()
        request.prefer('return=representation')
        order_data = {'intent': 'CAPTURE', 'purchase_units': [{'amount': {'currency_code': currency.upper(), 'value': f'{amount:.2f}'}, 'description': metadata.get('description', 'Hotel Booking Payment') if metadata else 'Hotel Booking Payment', 'custom_id': metadata.get('booking_id') if metadata else None}], 'application_context': {'brand_name': 'Hotel Booking', 'landing_page': 'BILLING', 'user_action': 'PAY_NOW', 'return_url': metadata.get('return_url') if metadata else None, 'cancel_url': metadata.get('cancel_url') if metadata else None}}
        if metadata:
            order_data['purchase_units'][0]['invoice_id'] = metadata.get('booking_number')
        request.request_body(order_data)
        try:
            response = client.execute(request)
            order = response.result
            approval_url = None
            for link in order.links:
                if link.rel == 'approve':
                    approval_url = link.href
                    break
            return {'id': order.id, 'status': order.status, 'approval_url': approval_url, 'amount': amount, 'currency': currency.upper()}
        except Exception as e:
            error_msg = str(e)
            if hasattr(e, 'message'):
                error_msg = e.message
            elif hasattr(e, 'details') and e.details:
                error_msg = json.dumps(e.details)
            raise ValueError(f'PayPal error: {error_msg}')

    @staticmethod
    def get_order(order_id: str, db: Optional[Session]=None) -> Dict[str, Any]:
        client = get_paypal_client(db)
        request = OrdersGetRequest(order_id)
        try:
            response = client.execute(request)
            order = response.result
            amount = 0.0
            currency = 'USD'
            if order.purchase_units and len(order.purchase_units) > 0:
                amount_str = order.purchase_units[0].amount.value
                currency = order.purchase_units[0].amount.currency_code
                amount = float(amount_str)
            return {'id': order.id, 'status': order.status, 'amount': amount, 'currency': currency, 'create_time': order.create_time, 'update_time': order.update_time}
        except Exception as e:
            error_msg = str(e)
            if hasattr(e, 'message'):
                error_msg = e.message
            raise ValueError(f'PayPal error: {error_msg}')

    @staticmethod
    def capture_order(order_id: str, db: Optional[Session]=None) -> Dict[str, Any]:
        client = get_paypal_client(db)
        request = OrdersCaptureRequest(order_id)
        request.prefer('return=representation')
        try:
            response = client.execute(request)
            order = response.result
            capture_id = None
            amount = 0.0
            currency = 'USD'
            status = order.status
            if order.purchase_units and len(order.purchase_units) > 0:
                payments = order.purchase_units[0].payments
                if payments and payments.captures and (len(payments.captures) > 0):
                    capture = payments.captures[0]
                    capture_id = capture.id
                    amount_str = capture.amount.value
                    currency = capture.amount.currency_code
                    amount = float(amount_str)
                    status = capture.status
            return {'order_id': order.id, 'capture_id': capture_id, 'status': status, 'amount': amount, 'currency': currency}
        except Exception as e:
            error_msg = str(e)
            if hasattr(e, 'message'):
                error_msg = e.message
            raise ValueError(f'PayPal error: {error_msg}')

    @staticmethod
    async def confirm_payment(order_id: str, db: Session, booking_id: Optional[int]=None) -> Dict[str, Any]:
        try:
            capture_data = PayPalService.capture_order(order_id, db)
            if not booking_id:
                order_data = PayPalService.get_order(order_id, db)
            if not booking_id:
                raise ValueError('Booking ID is required')
            booking = db.query(Booking).filter(Booking.id == booking_id).first()
            if not booking:
                raise ValueError('Booking not found')
            capture_status = capture_data.get('status')
            if capture_status not in ['COMPLETED', 'PENDING']:
                raise ValueError(f'Payment capture not in a valid state. Status: {capture_status}')
            payment = db.query(Payment).filter(Payment.booking_id == booking_id, Payment.transaction_id == order_id, Payment.payment_method == PaymentMethod.paypal).first()
            if not payment:
                payment = db.query(Payment).filter(Payment.booking_id == booking_id, Payment.payment_method == PaymentMethod.paypal, Payment.payment_status == PaymentStatus.pending).order_by(Payment.created_at.desc()).first()
            if not payment:
                payment = db.query(Payment).filter(Payment.booking_id == booking_id, Payment.payment_type == PaymentType.deposit, Payment.payment_status == PaymentStatus.pending).order_by(Payment.created_at.desc()).first()
            amount = capture_data['amount']
            capture_id = capture_data.get('capture_id')
            if payment:
                if capture_status == 'COMPLETED':
                    payment.payment_status = PaymentStatus.completed
                    payment.payment_date = datetime.utcnow()
                payment.amount = amount
                payment.payment_method = PaymentMethod.paypal
                if capture_id:
                    payment.transaction_id = f'{order_id}|{capture_id}'
            else:
                payment_type = PaymentType.full
                if booking.requires_deposit and (not booking.deposit_paid):
                    payment_type = PaymentType.deposit
                payment_status_enum = PaymentStatus.completed if capture_status == 'COMPLETED' else PaymentStatus.pending
                payment_date = datetime.utcnow() if capture_status == 'COMPLETED' else None
                transaction_id = f'{order_id}|{capture_id}' if capture_id else order_id
                payment = Payment(booking_id=booking_id, amount=amount, payment_method=PaymentMethod.paypal, payment_type=payment_type, payment_status=payment_status_enum, transaction_id=transaction_id, payment_date=payment_date, notes=f'PayPal payment - Order: {order_id}, Capture: {capture_id} (Status: {capture_status})')
                db.add(payment)
            db.commit()
            db.refresh(payment)
            if payment.payment_status == PaymentStatus.completed:
                db.refresh(booking)
                total_paid = sum((float(p.amount) for p in booking.payments if p.payment_status == PaymentStatus.completed))
                from ..models.invoice import Invoice, InvoiceStatus
                invoices = db.query(Invoice).filter(Invoice.booking_id == booking_id).all()
                for invoice in invoices:
                    invoice.amount_paid = total_paid
                    invoice.balance_due = float(invoice.total_amount) - total_paid
                    if invoice.balance_due <= 0:
                        invoice.status = InvoiceStatus.paid
                        invoice.paid_date = datetime.utcnow()
                    elif invoice.amount_paid > 0:
                        invoice.status = InvoiceStatus.sent
                booking_was_confirmed = False
                should_send_email = False
                if payment.payment_type == PaymentType.deposit:
                    booking.deposit_paid = True
                    if booking.status in [BookingStatus.pending, BookingStatus.cancelled]:
                        booking.status = BookingStatus.confirmed
                        booking_was_confirmed = True
                        should_send_email = True
                    elif booking.status == BookingStatus.confirmed:
                        should_send_email = True
                elif payment.payment_type == PaymentType.full:
                    if total_paid >= float(booking.total_price) or float(payment.amount) >= float(booking.total_price):
                        if booking.status in [BookingStatus.pending, BookingStatus.cancelled]:
                            booking.status = BookingStatus.confirmed
                            booking_was_confirmed = True
                            should_send_email = True
                        elif booking.status == BookingStatus.confirmed:
                            should_send_email = True
                if should_send_email:
                    try:
                        from ...shared.utils.mailer import send_email
                        from ...shared.utils.email_templates import booking_confirmation_email_template
                        from ...system.models.system_settings import SystemSettings
                        from ...rooms.models.room import Room
                        from sqlalchemy.orm import selectinload
                        import os
                        from ...shared.config.settings import settings
                        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')
                        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)
                        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'
                        amount_paid = total_paid
                        payment_type_str = payment.payment_type.value if payment.payment_type else None
                        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=False, deposit_amount=None, amount_paid=amount_paid, payment_type=payment_type_str, client_url=client_url, currency_symbol=currency_symbol)
                        if booking.user:
                            await send_email(to=booking.user.email, subject=f'Booking Confirmed - {booking.booking_number}', html=email_html)
                            logger.info(f'Booking confirmation email sent to {booking.user.email}')
                    except Exception as email_error:
                        logger.error(f'Failed to send booking confirmation email: {str(email_error)}')
                from ...shared.utils.mailer import send_email
                from ..services.invoice_service import InvoiceService
                from ..routes.booking_routes import _generate_invoice_email_html
                from ...auth.models.user import User
                user = db.query(User).filter(User.id == booking.user_id).first()
                for invoice in invoices:
                    if invoice.status == InvoiceStatus.paid and invoice.balance_due <= 0:
                        try:
                            invoice_dict = InvoiceService.invoice_to_dict(invoice)
                            invoice_html = _generate_invoice_email_html(invoice_dict, is_proforma=invoice.is_proforma)
                            invoice_type = 'Proforma Invoice' if invoice.is_proforma else 'Invoice'
                            if user:
                                await send_email(to=user.email, subject=f'{invoice_type} {invoice.invoice_number} - Payment Confirmed', html=invoice_html)
                                logger.info(f'{invoice_type} {invoice.invoice_number} sent to {user.email}')
                        except Exception as email_error:
                            logger.error(f'Failed to send invoice email: {str(email_error)}')
                from ...shared.utils.mailer import send_email
                from ..services.invoice_service import InvoiceService
                from ..models.invoice import InvoiceStatus
                from ..routes.booking_routes import _generate_invoice_email_html
                from ...auth.models.user import User
                user = db.query(User).filter(User.id == booking.user_id).first()
                for invoice in invoices:
                    if invoice.status == InvoiceStatus.paid and invoice.balance_due <= 0:
                        try:
                            invoice_dict = InvoiceService.invoice_to_dict(invoice)
                            invoice_html = _generate_invoice_email_html(invoice_dict, is_proforma=invoice.is_proforma)
                            invoice_type = 'Proforma Invoice' if invoice.is_proforma else 'Invoice'
                            if user:
                                await send_email(to=user.email, subject=f'{invoice_type} {invoice.invoice_number} - Payment Confirmed', html=invoice_html)
                                logger.info(f'{invoice_type} {invoice.invoice_number} sent to {user.email}')
                        except Exception as email_error:
                            logger.error(f'Failed to send invoice email: {str(email_error)}')
                db.commit()
                db.refresh(booking)

            def get_enum_value(enum_obj):
                if enum_obj is None:
                    return None
                if isinstance(enum_obj, (PaymentMethod, PaymentType, PaymentStatus)):
                    return enum_obj.value
                return enum_obj
            return {'id': payment.id, 'booking_id': payment.booking_id, 'amount': float(payment.amount) if payment.amount else 0.0, 'payment_method': get_enum_value(payment.payment_method), 'payment_type': get_enum_value(payment.payment_type), 'payment_status': get_enum_value(payment.payment_status), 'transaction_id': payment.transaction_id, 'payment_date': payment.payment_date.isoformat() if payment.payment_date else None}
        except ValueError as e:
            db.rollback()
            raise
        except Exception as e:
            error_msg = str(e) if str(e) else f'{type(e).__name__}: {repr(e)}'
            logger.error(f'Error in confirm_payment: {error_msg}', exc_info=True, extra={'order_id': order_id, 'booking_id': booking_id})
            db.rollback()
            raise ValueError(f'Error confirming payment: {error_msg}')