import logging
import stripe
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
logger = get_logger(__name__)

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

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

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

class StripeService:

    @staticmethod
    def create_payment_intent(amount: float, currency: str='usd', metadata: Optional[Dict[str, Any]]=None, customer_id: Optional[str]=None, db: Optional[Session]=None) -> Dict[str, Any]:
        secret_key = None
        if db:
            secret_key = get_stripe_secret_key(db)
        if not secret_key:
            secret_key = settings.STRIPE_SECRET_KEY
        if not secret_key:
            raise ValueError('Stripe secret key is not configured')
        stripe.api_key = secret_key
        if amount <= 0:
            raise ValueError('Amount must be greater than 0')
        if amount > 999999.99:
            raise ValueError(f"Amount ${amount:,.2f} exceeds Stripe's maximum of $999,999.99")
        amount_in_cents = int(round(amount * 100))
        if amount_in_cents > 99999999:
            raise ValueError(f"Amount ${amount:,.2f} (${amount_in_cents} cents) exceeds Stripe's maximum")
        intent_params = {'amount': amount_in_cents, 'currency': currency, 'automatic_payment_methods': {'enabled': True}, 'metadata': metadata or {}}
        if customer_id:
            intent_params['customer'] = customer_id
        try:
            intent = stripe.PaymentIntent.create(**intent_params)
            return {'client_secret': intent.client_secret, 'id': intent.id, 'status': intent.status, 'amount': intent.amount, 'currency': intent.currency}
        except stripe.StripeError as e:
            raise ValueError(f'Stripe error: {str(e)}')

    @staticmethod
    def retrieve_payment_intent(payment_intent_id: str, db: Optional[Session]=None) -> Dict[str, Any]:
        secret_key = None
        if db:
            secret_key = get_stripe_secret_key(db)
        if not secret_key:
            secret_key = settings.STRIPE_SECRET_KEY
        if not secret_key:
            raise ValueError('Stripe secret key is not configured')
        stripe.api_key = secret_key
        try:
            intent = stripe.PaymentIntent.retrieve(payment_intent_id)
            charges = []
            if hasattr(intent, 'charges') and intent.charges:
                charges_data = getattr(intent.charges, 'data', [])
                charges = [{'id': charge.id, 'paid': charge.paid, 'status': charge.status} for charge in charges_data]
            return {'id': intent.id, 'status': intent.status, 'amount': intent.amount / 100, 'currency': intent.currency, 'metadata': intent.metadata, 'charges': charges}
        except stripe.StripeError as e:
            raise ValueError(f'Stripe error: {str(e)}')

    @staticmethod
    async def confirm_payment(payment_intent_id: str, db: Session, booking_id: Optional[int]=None) -> Dict[str, Any]:
        try:
            intent_data = StripeService.retrieve_payment_intent(payment_intent_id, db)
            if not booking_id and intent_data.get('metadata'):
                booking_id = intent_data['metadata'].get('booking_id')
                if booking_id:
                    booking_id = int(booking_id)
            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')
            payment_status = intent_data.get('status')
            logger.info(f'Payment intent status: {payment_status}', extra={'payment_intent_id': payment_intent_id, 'booking_id': booking_id})
            if payment_status not in ['succeeded', 'processing']:
                raise ValueError(f'Payment intent not in a valid state. Status: {payment_status}. Payment may still be processing or may have failed.')
            payment = db.query(Payment).filter(Payment.booking_id == booking_id, Payment.transaction_id == payment_intent_id, Payment.payment_method == PaymentMethod.stripe).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 = intent_data['amount']
            if payment:
                if payment_status == 'succeeded':
                    payment.payment_status = PaymentStatus.completed
                    payment.payment_date = datetime.utcnow()
                payment.amount = amount
                payment.payment_method = PaymentMethod.stripe
            else:
                payment_type = PaymentType.full
                if booking.requires_deposit and (not booking.deposit_paid):
                    payment_type = PaymentType.deposit
                payment_status_enum = PaymentStatus.completed if payment_status == 'succeeded' else PaymentStatus.pending
                payment_date = datetime.utcnow() if payment_status == 'succeeded' else None
                payment = Payment(booking_id=booking_id, amount=amount, payment_method=PaymentMethod.stripe, payment_type=payment_type, payment_status=payment_status_enum, transaction_id=payment_intent_id, payment_date=payment_date, notes=f'Stripe payment - Intent: {payment_intent_id} (Status: {payment_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
                from ..services.invoice_service import InvoiceService
                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)}')
                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
            try:
                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 AttributeError as ae:
                logger.error(f'AttributeError accessing payment fields: {ae}', exc_info=True, extra={
                    'payment_id': payment.id if hasattr(payment, 'id') else None,
                    'booking_id': booking_id,
                    'payment_method': payment.payment_method if hasattr(payment, 'payment_method') else 'missing',
                    'payment_type': payment.payment_type if hasattr(payment, 'payment_type') else 'missing',
                    'payment_status': payment.payment_status if hasattr(payment, 'payment_status') else 'missing'
                })
                raise
        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={'payment_intent_id': payment_intent_id, 'booking_id': booking_id})
            db.rollback()
            raise ValueError(f'Error confirming payment: {error_msg}')

    @staticmethod
    async def handle_webhook(payload: bytes, signature: str, db: Session) -> Dict[str, Any]:
        webhook_secret = get_stripe_webhook_secret(db)
        if not webhook_secret:
            webhook_secret = settings.STRIPE_WEBHOOK_SECRET
        if not webhook_secret:
            raise ValueError('Stripe webhook secret is not configured. Please configure it in Admin Panel (Settings > Stripe Settings) or set STRIPE_WEBHOOK_SECRET environment variable.')
        try:
            event = stripe.Webhook.construct_event(payload, signature, webhook_secret)
        except ValueError as e:
            raise ValueError(f'Invalid payload: {str(e)}')
        except stripe.SignatureVerificationError as e:
            raise ValueError(f'Invalid signature: {str(e)}')
        if event['type'] == 'payment_intent.succeeded':
            payment_intent = event['data']['object']
            payment_intent_id = payment_intent['id']
            metadata = payment_intent.get('metadata', {})
            booking_id = metadata.get('booking_id')
            if booking_id:
                try:
                    await StripeService.confirm_payment(payment_intent_id=payment_intent_id, db=db, booking_id=int(booking_id))
                except Exception as e:
                    import logging
                    logger = logging.getLogger(__name__)
                    logger.error(f'Error processing webhook for booking {booking_id}: {str(e)}')
        elif event['type'] == 'payment_intent.payment_failed':
            payment_intent = event['data']['object']
            payment_intent_id = payment_intent['id']
            metadata = payment_intent.get('metadata', {})
            booking_id = metadata.get('booking_id')
            if booking_id:
                payment = db.query(Payment).filter(Payment.transaction_id == payment_intent_id, Payment.booking_id == int(booking_id)).first()
                if payment:
                    payment.payment_status = PaymentStatus.failed
                    db.commit()
                    booking = db.query(Booking).filter(Booking.id == int(booking_id)).first()
                    if booking and booking.status != BookingStatus.cancelled:
                        booking.status = BookingStatus.cancelled
                        db.commit()
                        db.refresh(booking)
                        try:
                            if booking.user:
                                from ...shared.utils.mailer import send_email
                                from ...shared.utils.email_templates import booking_status_changed_email_template
                                from ...system.models.system_settings import SystemSettings
                                from ...shared.config.settings import settings
                                import os
                                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, 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 {'status': 'success', 'event_type': event['type'], 'event_id': event['id']}