"""Passenger checkout + eSewa callbacks (FR-13/FR-14, BR-2).

Flow: book -> Booking(PENDING_PAYMENT, 15-min deadline) -> /checkout/<id>/
auto-submits the signed form to eSewa -> eSewa redirects to /payments/esewa/
success|failure. Every callback is logged append-only (PaymentWebhookLog),
signature-verified, and idempotent per transaction_uuid.
"""
from datetime import timedelta

from django.contrib import messages
from django.shortcuts import redirect, render
from django.utils import timezone

from .models import Booking, Payment, PaymentWebhookLog
from .payments import (build_esewa_form, check_status, decode_callback,
                       esewa_form_url, verify_callback_signature)
from .realtime import broadcast_seat_update, notify_admins, notify_operator
from .views import current_user, log_audit, role_required

PAYMENT_WINDOW_MINUTES = 15  # BR-2


def expire_stale_bookings():
    """Lazy sweep: PENDING_PAYMENT bookings past their deadline become
    EXPIRED and their seats go back on sale. Called from the hot passenger
    paths, so no background worker is required."""
    now = timezone.now()
    stale = Booking.objects.filter(booking_status='PENDING_PAYMENT',
                                   payment_deadline__lt=now).select_related('schedule')
    for b in stale:
        seat_numbers = list(b.booking_seats.values_list('seat__seat_number', flat=True))
        b.booking_status = 'EXPIRED'
        b.cancelled_by = 'SYSTEM'
        b.cancel_reason = 'Payment window expired'
        b.cancelled_at = now
        b.save(update_fields=['booking_status', 'cancelled_by', 'cancel_reason', 'cancelled_at'])
        Payment.objects.filter(booking=b, status__in=['INITIATED', 'PENDING']).update(status='FAILED')
        if seat_numbers:
            broadcast_seat_update(b.schedule_id, seat_numbers, 'AVAILABLE')


def _next_transaction_uuid(booking):
    n = Payment.objects.filter(booking=booking).count() + 1
    return f"{booking.booking_code}-A{n}"  # alphanumeric + hyphen only, unique per attempt


@role_required('PASSENGER')
def checkout(request, booking_id):
    expire_stale_bookings()
    user = current_user(request)
    booking = Booking.objects.select_related(
        'schedule__bus', 'schedule__route__source_city', 'schedule__route__destination_city'
    ).filter(id=booking_id, user=user).first()

    if booking is None:
        messages.error(request, 'Booking not found.')
        return redirect('my_bookings')
    if booking.booking_status == 'CONFIRMED':
        messages.success(request, f'Booking {booking.booking_code} is already paid and confirmed.')
        return redirect('my_bookings')
    if booking.booking_status != 'PENDING_PAYMENT':
        messages.error(request, f'This booking is {booking.booking_status} and cannot be paid.')
        return redirect('my_bookings')

    remaining = int((booking.payment_deadline - timezone.now()).total_seconds())

    payment = Payment.objects.create(
        booking=booking, gateway='ESEWA',
        transaction_uuid=_next_transaction_uuid(booking),
        amount=booking.total_amount, status='INITIATED',
    )

    form_fields = build_esewa_form(
        payment,
        success_url=request.build_absolute_uri('/payments/esewa/success/'),
        failure_url=request.build_absolute_uri('/payments/esewa/failure/'),
    )
    seats = list(booking.booking_seats.select_related('seat').all())
    return render(request, 'checkout.html', {
        'booking': booking, 'payment': payment, 'seats': seats,
        'form_fields': form_fields, 'esewa_url': esewa_form_url(),
        'remaining_seconds': max(remaining, 0),
    })


def _confirm_payment(payment, ref_id, raw):
    """Idempotently mark a payment COMPLETE and its booking CONFIRMED."""
    booking = payment.booking
    payment.status = 'COMPLETE'
    payment.ref_id = ref_id or payment.ref_id
    payment.raw_response = raw
    payment.save(update_fields=['status', 'ref_id', 'raw_response'])

    if booking.booking_status == 'PENDING_PAYMENT':
        booking.booking_status = 'CONFIRMED'
        booking.save(update_fields=['booking_status'])
        seat_numbers = list(booking.booking_seats.values_list('seat__seat_number', flat=True))
        if seat_numbers:
            broadcast_seat_update(booking.schedule_id, seat_numbers, 'BOOKED')
        notify_operator(booking.schedule.bus.operator_id, 'new_booking', {
            'booking_code': booking.booking_code, 'seats': booking.seats_count,
            'total': float(booking.total_amount),
        })
        notify_admins('payment_complete', {
            'booking_code': booking.booking_code, 'total': float(booking.total_amount),
        })
        from .views import log_activity
        log_activity(booking.user, 'PASSENGER', 'PAYMENT_COMPLETED',
                    f'{booking.booking_code} - Rs.{booking.total_amount} via eSewa', amount=booking.total_amount)
        log_activity(booking.schedule.bus.operator.user, 'OPERATOR', 'REVENUE_EARNED',
                    f'{booking.booking_code} - Rs.{booking.operator_earning} earned '
                    f'(Rs.{booking.commission_amount} commission)', amount=booking.operator_earning)


def esewa_success(request):
    """eSewa redirects here with ?data=<base64 JSON> after payment."""
    data_b64 = request.GET.get('data', '')
    decoded = decode_callback(data_b64)

    payment = None
    if decoded:
        payment = Payment.objects.select_related(
            'booking__schedule__bus').filter(transaction_uuid=decoded.get('transaction_uuid')).first()

    sig_ok = bool(decoded) and verify_callback_signature(decoded)
    PaymentWebhookLog.objects.create(
        payment=payment,
        transaction_uuid=(decoded or {}).get('transaction_uuid', ''),
        raw_payload=data_b64 or '(empty)',
        signature_valid=sig_ok,
    )

    if payment is None:
        return render(request, 'payment_result.html',
                      {'success': False, 'title': 'Payment could not be matched',
                       'detail': 'We could not match this payment to a booking. If money was '
                                 'deducted, it will be verified automatically - contact support '
                                 'with your eSewa statement.'})

    if payment.status == 'COMPLETE':  # duplicate / replayed callback - idempotent
        return render(request, 'payment_result.html',
                      {'success': True, 'booking': payment.booking, 'payment': payment,
                       'title': 'Payment already confirmed'})

    status = (decoded or {}).get('status', '')
    if sig_ok and status == 'COMPLETE':
        _confirm_payment(payment, (decoded or {}).get('transaction_code'), data_b64)
        return render(request, 'payment_result.html',
                      {'success': True, 'booking': payment.booking, 'payment': payment,
                       'title': 'Payment successful!'})

    # Signature failed or status unclear -> ask eSewa directly (authoritative)
    enquiry = check_status(payment)
    if enquiry:
        PaymentWebhookLog.objects.create(
            payment=payment, transaction_uuid=payment.transaction_uuid,
            raw_payload=f'status-check: {enquiry}', signature_valid=True,
        )
    if enquiry and enquiry.get('status') == 'COMPLETE':
        _confirm_payment(payment, enquiry.get('ref_id'), str(enquiry))
        return render(request, 'payment_result.html',
                      {'success': True, 'booking': payment.booking, 'payment': payment,
                       'title': 'Payment successful!'})

    payment.status = 'AMBIGUOUS' if enquiry is None else enquiry.get('status', 'FAILED')
    if payment.status not in dict(Payment.STATUS_CHOICES):
        payment.status = 'FAILED'
    payment.raw_response = data_b64
    payment.save(update_fields=['status', 'raw_response'])
    return render(request, 'payment_result.html',
                  {'success': False, 'booking': payment.booking, 'payment': payment,
                   'title': 'Payment not verified',
                   'detail': 'The payment response could not be verified. If money was deducted, '
                             'use "Complete payment" on My Bookings to retry, or contact support.'})


def esewa_failure(request):
    """eSewa sends the user here on failed/cancelled payment. The booking stays
    PENDING_PAYMENT until its deadline so the passenger can retry."""
    data_b64 = request.GET.get('data', '')
    decoded = decode_callback(data_b64)
    payment = None
    if decoded and decoded.get('transaction_uuid'):
        payment = Payment.objects.filter(transaction_uuid=decoded['transaction_uuid']).first()

    PaymentWebhookLog.objects.create(
        payment=payment,
        transaction_uuid=(decoded or {}).get('transaction_uuid', ''),
        raw_payload=data_b64 or '(no data param)',
        signature_valid=bool(decoded) and verify_callback_signature(decoded),
    )
    if payment and payment.status in ('INITIATED', 'PENDING'):
        payment.status = 'CANCELED'
        payment.raw_response = data_b64
        payment.save(update_fields=['status', 'raw_response'])

    booking = payment.booking if payment else None
    return render(request, 'payment_result.html',
                  {'success': False, 'booking': booking, 'payment': payment,
                   'title': 'Payment cancelled',
                   'detail': 'No money was taken. Your seats are held until the payment window '
                             'ends - you can retry from My Bookings.'})
