"""
Ecommerce public views — shop, cart, checkout, order confirmation, domain search.
"""
import json
import logging
import stripe
from decimal import Decimal

logger = logging.getLogger(__name__)
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from django.views.decorators.clickjacking import xframe_options_exempt
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.conf import settings
from django.utils import timezone

from .models import Product, Order, OrderItem, DomainRegistration, ProductInterest


def _stripe_public_key():
    """Publishable key from Settings, so the page and the charge agree on
    which Stripe account they are talking to."""
    from apps.pages.api_keys import get_stripe_public_key
    return get_stripe_public_key()
from .stripe_utils import create_checkout_session, create_balance_payment_session, construct_webhook_event
from . import namecheap


# ──────────────────────────────────────────────────────────────────────────────
# Helpers
# ──────────────────────────────────────────────────────────────────────────────

def _get_cart(request):
    """Return the cart dict from the session (creates empty one if missing)."""
    if 'cart' not in request.session:
        request.session['cart'] = {}
    return request.session['cart']


def _save_cart(request, cart):
    request.session['cart'] = cart
    request.session.modified = True


def _cart_total(cart, products):
    """Calculate total for a cart dict {product_id: {qty, unit_price}}."""
    total = Decimal('0')
    for pid, data in cart.items():
        total += Decimal(str(data['unit_price'])) * data['qty']
    return total


# ──────────────────────────────────────────────────────────────────────────────
# Shop — product listing
# ──────────────────────────────────────────────────────────────────────────────

def shop(request):
    """Public shop / product catalogue page. Domains have their own page (/shop/domains/)."""
    from .models import ProductCategory
    cat_slug   = request.GET.get('cat', '')
    products   = Product.objects.filter(
        is_active=True,
        show_in_shop=True,
    ).exclude(product_type='domain').exclude(is_auction=True).select_related('category').order_by('-is_featured', 'sort_order', '-created_at')
    if cat_slug:
        products = products.filter(category__slug=cat_slug)
    categories = ProductCategory.objects.exclude(
        products__product_type='domain'
    ).filter(products__is_active=True, products__show_in_shop=True).distinct().order_by('sort_order', 'name')
    context = {
        'products':   products,
        'categories': categories,
        'cat_filter': cat_slug,
    }
    return render(request, 'public/shop/shop.html', context)


def product_detail(request, slug):
    """Public product detail page."""
    product = get_object_or_404(Product, slug=slug, is_active=True)

    # A domain has one address, /domains/<slug>/. Serving it here as well gave
    # it a second page with none of the selling behaviour on it, no badge, no
    # offer form, no reserve. Domains route on product type as well as the
    # auction flag, because a fixed price domain has the flag unticked.
    if product.is_auction or product.product_type == Product.TYPE_DOMAIN:
        from django.shortcuts import redirect as _redirect
        return _redirect('auction_detail', slug=product.slug, permanent=True)

    # Related items must not advertise domains or auction listings from the
    # shop either.
    related = (Product.objects.filter(is_active=True)
               .exclude(is_auction=True)
               .exclude(product_type=Product.TYPE_DOMAIN)
               .exclude(pk=product.pk)[:3])
    interest_sent = request.session.pop(f'interest_sent_{product.pk}', False)
    cart = _get_cart(request)
    in_cart = str(product.pk) in cart

    context = {
        'product': product,
        'related': related,
        'interest_sent': interest_sent,
        'in_cart': in_cart,
        'cart_count': sum(v['qty'] for v in cart.values()),
    }
    return render(request, 'public/shop/product_detail.html', context)


def product_show_interest(request, slug):
    """Interest form for auction products — creates a pending Order (AJAX or plain POST)."""
    product = get_object_or_404(Product, slug=slug, is_active=True, is_auction=True)
    is_ajax = request.headers.get('X-Requested-With') == 'XMLHttpRequest'
    if request.method == 'POST':
        name    = request.POST.get('name', '').strip()
        email   = request.POST.get('email', '').strip()
        phone   = request.POST.get('phone', '').strip()
        offer   = request.POST.get('offer', '').strip() or None
        msg     = request.POST.get('message', '').strip()
        if name and email:
            # Use offer amount if supplied, otherwise fall back to reserve / list price
            price = Decimal(str(offer)) if offer else (product.auction_reserve or product.price)
            tenant = getattr(request, 'tenant', None)
            order = Order.objects.create(
                tenant=tenant,
                customer_name=name,
                customer_email=email,
                customer_phone=phone,
                customer_notes=msg,
                subtotal=price,
                total=price,
                ip_address=request.META.get('REMOTE_ADDR'),
            )
            OrderItem.objects.create(
                order=order,
                product=product,
                product_name=product.name,
                product_type=product.product_type,
                quantity=1,
                unit_price=price,
                total_price=price,
            )
            if is_ajax:
                return JsonResponse({'ok': True})
            request.session[f'interest_sent_{product.pk}'] = True
        else:
            if is_ajax:
                return JsonResponse({'ok': False, 'error': 'Name and email are required.'}, status=400)
    return redirect('shop_product_detail', slug=slug)


# ──────────────────────────────────────────────────────────────────────────────
# Cart
# ──────────────────────────────────────────────────────────────────────────────

def cart_add(request, product_id):
    """Add a product to the session cart."""
    product = get_object_or_404(Product, pk=product_id, is_active=True)
    cart = _get_cart(request)
    key = str(product_id)

    # Determine billing period and price for subscriptions
    billing_period = request.POST.get('billing_period', 'monthly')
    if billing_period == 'annual' and product.annual_price:
        unit_price = product.annual_price
    else:
        billing_period = 'monthly'
        unit_price = product.price

    # For non-subscriptions, ignore billing_period
    if product.product_type != Product.TYPE_SUBSCRIPTION:
        billing_period = None
        unit_price = product.price

    if key in cart:
        cart[key]['qty'] += 1
    else:
        cart[key] = {
            'name': product.name,
            'slug': product.slug,
            'qty': 1,
            'unit_price': str(unit_price),
            'deposit_amount': str(product.deposit_amount) if product.deposit_amount else None,
            'product_type': product.product_type,
            'icon_class': product.icon_class,
            'billing_period': billing_period,
        }
    _save_cart(request, cart)

    # Remember the product page so the cart can offer a sensible back link
    referer = request.META.get('HTTP_REFERER', '')
    if referer:
        request.session['cart_back_url'] = referer

    if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
        return JsonResponse({
            'success': True,
            'cart_count': sum(v['qty'] for v in cart.values()),
        })
    return redirect('shop_cart')


def cart_remove(request, product_id):
    cart = _get_cart(request)
    cart.pop(str(product_id), None)
    _save_cart(request, cart)
    return redirect('shop_cart')


def cart_decrement(request, product_id):
    """Remove one unit; delete the entry if qty reaches 0."""
    cart = _get_cart(request)
    key = str(product_id)
    if key in cart:
        cart[key]['qty'] -= 1
        if cart[key]['qty'] <= 0:
            cart.pop(key)
    _save_cart(request, cart)
    return redirect('shop_cart')


def cart_view(request):
    """Shopping cart page."""
    cart = _get_cart(request)
    # Enrich with DB objects where possible
    product_ids = [int(k) for k in cart.keys()]
    db_products = {str(p.pk): p for p in Product.objects.filter(pk__in=product_ids)}

    items = []
    subtotal = Decimal('0')
    for pid, data in cart.items():
        unit_price = Decimal(str(data['unit_price']))
        line_total = unit_price * data['qty']
        subtotal += line_total
        items.append({
            'product_id': pid,
            'product': db_products.get(pid),
            'name': data['name'],
            'qty': data['qty'],
            'unit_price': unit_price,
            'line_total': line_total,
            'deposit_amount': Decimal(str(data['deposit_amount'])) if data.get('deposit_amount') else None,
        })

    context = {
        'items': items,
        'subtotal': subtotal,
        'cart_count': sum(v['qty'] for v in cart.values()),
        'back_url': request.session.get('cart_back_url', '/products/'),
    }
    return render(request, 'public/shop/cart.html', context)


# ──────────────────────────────────────────────────────────────────────────────
# Checkout
# ──────────────────────────────────────────────────────────────────────────────

def checkout(request):
    """Checkout form — customer details + payment method selection."""
    cart = _get_cart(request)
    if not cart:
        return redirect('shop_cart')

    if request.method == 'POST':
        # Build the Order
        first_name = request.POST.get('first_name', '').strip()
        last_name  = request.POST.get('last_name', '').strip()
        name       = f"{first_name} {last_name}".strip() or request.POST.get('name', '').strip()
        email      = request.POST.get('email', '').strip()
        phone      = request.POST.get('phone', '').strip()
        company    = request.POST.get('company', '').strip()
        position   = request.POST.get('position', '').strip()
        address    = request.POST.get('address', '').strip()
        postcode   = request.POST.get('postcode', '').strip()
        country    = request.POST.get('country', '').strip()
        notes      = request.POST.get('notes', '').strip()
        payment_mode = request.POST.get('payment_mode', 'full')  # 'full' or 'deposit'

        if not name or not email:
            messages.error(request, 'Name and email are required.')
            return redirect('shop_checkout')

        # Get tenant
        tenant = getattr(request, 'tenant', None)

        order = Order.objects.create(
            tenant=tenant,
            customer_name=name,
            customer_email=email,
            customer_phone=phone,
            customer_company=f"{company}{' — ' + position if position else ''}".strip(' — ') if company else position,
            customer_address=address,
            customer_postcode=postcode,
            customer_country=country,
            customer_notes=notes,
            is_deposit_order=(payment_mode == 'deposit'),
            ip_address=request.META.get('REMOTE_ADDR'),
        )

        product_ids = [int(k) for k in cart.keys()]
        db_products = {str(p.pk): p for p in Product.objects.filter(pk__in=product_ids)}

        subtotal = Decimal('0')
        for pid, data in cart.items():
            product = db_products.get(pid)
            unit_price = Decimal(str(data['unit_price']))
            qty = data['qty']
            line_total = unit_price * qty
            subtotal += line_total
            OrderItem.objects.create(
                order=order,
                product=product,
                product_name=data['name'],
                product_type=data.get('product_type', ''),
                quantity=qty,
                unit_price=unit_price,
                total_price=line_total,
            )

        order.subtotal = subtotal
        order.total = subtotal  # add tax logic here if needed
        order.save()

        # Create Stripe session
        try:
            session = create_checkout_session(order, request, mode=payment_mode)
            order.stripe_session_id = session.id
            order.save(update_fields=['stripe_session_id'])
            _save_cart(request, {})
            return redirect(session.url, permanent=False)
        except Exception as e:
            order.delete()
            from django.core.cache import cache
            from django.utils import timezone
            errors = cache.get('stripe_payment_errors', [])
            errors.insert(0, {'msg': str(e)[:200], 'at': timezone.now().strftime('%d %b %H:%M')})
            cache.set('stripe_payment_errors', errors[:5], 60 * 60 * 24)
            # Render the form again directly — preserves filled-in fields
            checkout_error = f'Payment error: {str(e)[:200]}'
            return _render_checkout(request, cart, checkout_error=checkout_error, form_data=request.POST)

    return _render_checkout(request, cart)


def _render_checkout(request, cart, checkout_error=None, form_data=None):
    """Build checkout page context and render."""
    product_ids = [int(k) for k in cart.keys()]
    db_products = {str(p.pk): p for p in Product.objects.filter(pk__in=product_ids)}

    items = []
    subtotal = Decimal('0')
    any_deposit = False
    for pid, data in cart.items():
        unit_price = Decimal(str(data['unit_price']))
        deposit = Decimal(str(data['deposit_amount'])) if data.get('deposit_amount') else None
        line_total = unit_price * data['qty']
        subtotal += line_total
        if deposit:
            any_deposit = True
        items.append({
            'product_id': pid,
            'name': data['name'],
            'qty': data['qty'],
            'unit_price': unit_price,
            'line_total': line_total,
            'deposit_amount': deposit,
        })

    context = {
        'items': items,
        'subtotal': subtotal,
        'any_deposit': any_deposit,
        'stripe_public_key': _stripe_public_key(),
        'checkout_error': checkout_error or request.session.pop('checkout_error', None),
        'form_data': form_data or {},
    }
    return render(request, 'public/shop/checkout.html', context)


def order_success(request):
    """Payment success page — called after Stripe redirect."""
    session_id   = request.GET.get('session_id', '')
    order_number = request.GET.get('order_number', '')   # set for balance payments
    order = None
    if session_id:
        try:
            # Balance payments pass order_number in URL; regular checkout uses stripe_session_id
            if order_number:
                order = Order.objects.get(order_number=order_number)
            else:
                order = Order.objects.get(stripe_session_id=session_id)

            if order and order.status not in (
                Order.STATUS_PAID, Order.STATUS_COMPLETED, Order.STATUS_PROCESSING
            ):
                from apps.ecommerce.stripe_utils import retrieve_session, _get_stripe_secret_key
                import stripe as _stripe
                _stripe.api_key = _get_stripe_secret_key(getattr(order, 'tenant', None))
                stripe_session = retrieve_session(session_id)
                if stripe_session.payment_status == 'paid':
                    amount = Decimal(str(stripe_session.amount_total / 100))
                    is_balance = stripe_session.metadata.get('is_balance_payment') == 'true'
                    if is_balance:
                        order.amount_paid = (order.amount_paid or Decimal('0')) + amount
                        if order.amount_paid >= order.total:
                            order.status = Order.STATUS_PAID
                            if not order.paid_at:
                                from django.utils import timezone
                                order.paid_at = timezone.now()
                        else:
                            order.status = Order.STATUS_DEPOSIT_PAID
                        order.save()
                        recorded = True
                    else:
                        recorded = order.mark_paid(
                            amount=amount,
                            payment_intent_id=stripe_session.get('payment_intent', ''),
                        )
                    # Only a payment this call actually recorded gets an
                    # email. The outer guard already stops a refresh getting
                    # this far, but a duplicate intent could still slip
                    # through if another path recorded it a moment earlier.
                    if recorded:
                        from apps.ecommerce.email_utils import (
                            send_order_confirmation,
                            send_order_admin_notification,
                        )
                        send_order_confirmation(order)
                        send_order_admin_notification(order, request)
        except Exception as exc:
            logger.error('order_success failed for session_id=%s: %s', session_id, exc, exc_info=True)

    context = {'order': order}
    return render(request, 'public/shop/order_success.html', context)


def order_detail(request, order_number):
    """Public order detail page (for balance payment link)."""
    order = get_object_or_404(Order, order_number=order_number)
    context = {'order': order}
    return render(request, 'public/shop/order_detail.html', context)


def pay_balance(request, order_number):
    """Create a Stripe session for the remaining balance on an order."""
    order = get_object_or_404(Order, order_number=order_number)
    if not order.balance_due or order.balance_due <= 0:
        messages.info(request, 'No balance outstanding on this order.')
        return redirect('shop_order_detail', order_number=order_number)
    try:
        session = create_balance_payment_session(order, request)
        return redirect(session.url, permanent=False)
    except Exception as e:
        messages.error(request, f'Payment setup failed: {e}')
        return redirect('shop_order_detail', order_number=order_number)


# ──────────────────────────────────────────────────────────────────────────────
# Domain search
# ──────────────────────────────────────────────────────────────────────────────

def domain_search(request):
    """Public domain search page."""
    query = request.GET.get('q', '').strip().lower()
    results = []
    tld_products = Product.objects.filter(product_type=Product.TYPE_DOMAIN, is_active=True)

    if query:
        # Strip leading www. or protocol if typed
        query = query.replace('https://', '').replace('http://', '').replace('www.', '').split('/')[0]
        # Build list of domain names to check
        domains_to_check = []
        for p in tld_products:
            tld = p.domain_tld if p.domain_tld.startswith('.') else f'.{p.domain_tld}'
            sld = query.split('.')[0]  # e.g. 'mybusiness' from 'mybusiness.co.uk'
            full_domain = f'{sld}{tld}'
            domains_to_check.append((full_domain, p))

        # Check availability via Namecheap (if configured)
        availability = {}
        if settings.NAMECHEAP_API_KEY:
            try:
                domain_names = [d for d, _ in domains_to_check]
                availability = namecheap.check_availability(domain_names)
            except Exception:
                pass  # Fall back to showing all as unchecked

        for full_domain, product in domains_to_check:
            avail = availability.get(full_domain)  # True/False/None
            results.append({
                'domain': full_domain,
                'product': product,
                'available': avail,
                'price': product.price,
                'tld': product.domain_tld,
            })

    context = {
        'query': query,
        'results': results,
        'cart_count': sum(v['qty'] for v in _get_cart(request).values()),
    }
    return render(request, 'public/shop/domain_search.html', context)


def domain_add_to_cart(request):
    """AJAX/POST — add a specific domain to the cart."""
    if request.method != 'POST':
        return JsonResponse({'error': 'POST required'}, status=405)

    domain_name = request.POST.get('domain_name', '').strip().lower()
    product_id  = request.POST.get('product_id')
    years       = int(request.POST.get('years', 1))

    product = get_object_or_404(Product, pk=product_id, product_type=Product.TYPE_DOMAIN, is_active=True)

    cart = _get_cart(request)
    key = f'domain_{domain_name}'
    unit_price = product.price * years

    cart[key] = {
        'name': f'{domain_name} ({years} year)',
        'slug': product.slug,
        'qty': 1,
        'unit_price': str(unit_price),
        'deposit_amount': None,
        'product_type': 'domain',
        'icon_class': 'fa-solid fa-globe',
        'domain_name': domain_name,
        'domain_tld': product.domain_tld,
        'registration_years': years,
        'product_id': str(product.pk),
    }
    _save_cart(request, cart)

    return JsonResponse({
        'success': True,
        'cart_count': sum(v['qty'] for v in cart.values()),
    })


# ──────────────────────────────────────────────────────────────────────────────
# Stripe Webhook
# ──────────────────────────────────────────────────────────────────────────────

def _preview_now():
    """A real datetime for the sample alerts, so date filters render properly."""
    from django.utils import timezone
    return timezone.localtime()


@xframe_options_exempt
@login_required
def email_preview(request, template):
    """Render an email template in the browser for visual checking. CMS users only."""
    from django.conf import settings as _s

    templates = {
        'order-confirmation': ('public/emails/order_confirmation.html', {
            'customer_name': 'John Smith',
            'order_number':  'FT-00123',
            'items': [
                type('Item', (), {'product_name': 'Website Design Package', 'quantity': 1, 'total_price': '1,200.00'})(),
                type('Item', (), {'product_name': 'SEO Setup', 'quantity': 1, 'total_price': '350.00'})(),
            ],
            'amount_paid': '1,550.00',
            'order_url':  'http://localhost:8000/products/order/FT-00123/',
            'sig_name': 'Emily Carter', 'sig_dept': 'Client Services',
        }),
        'order-admin': ('public/emails/order_admin_notification.html', {
            'order_number':     'FT-00123',
            'customer_name':    'John Smith',
            'customer_email':   'john@example.com',
            'customer_phone':   '+44 7700 000000',
            'customer_company': 'Acme Ltd',
            'customer_address': '123 High Street',
            'customer_postcode': 'EC1A 1BB',
            'customer_country': 'United Kingdom',
            'customer_notes':   'Please call before starting.',
            'items': [
                type('Item', (), {'product_name': 'Website Design Package', 'quantity': 1, 'total_price': '1,200.00'})(),
                type('Item', (), {'product_name': 'SEO Setup', 'quantity': 1, 'total_price': '350.00'})(),
            ],
            'amount_paid':   '1,550.00',
            'cms_order_url': 'http://localhost:8000/cms/orders/1/',
        }),
        'quote': ('public/emails/quote_email.html', {
            'first_name':    'Sarah',
            'project_label': 'E-commerce Website with Payment Integration',
            'quote_amount':  '3,500.00',
            'quote_weeks':   '6',
            'quote_notes':   'This includes up to 3 revision rounds, full mobile optimisation, and a 3-month support period after launch.',
            'service_types': 'Web Design, E-commerce',
            'budget':        '£3,000 – £5,000',
            'timeline':      '1 – 3 months',
            'sig_name': 'Daniel Walsh', 'sig_dept': 'Projects',
        }),
        'deal': ('public/emails/deal_email.html', {
            'customer_name': 'John Smith',
            'order_number':  'FT-00123',
            'items': [
                type('Item', (), {'product_name': 'Website Design Package', 'quantity': 1, 'total_price': '1,200.00'})(),
                type('Item', (), {'product_name': 'Monthly SEO Retainer', 'quantity': 1, 'total_price': '350.00'})(),
            ],
            'total':    '1,550.00',
            'deal_url': 'http://localhost:8000/deal/abc123/',
            'sig_name': 'Natalie Brooks', 'sig_dept': 'Account Management',
        }),
        'ticket-reply': ('public/emails/ticket_reply_email.html', {
            'subject':       'Website not loading on mobile',
            'ticket_number': 'FT-882748',
            'customer_name': 'Meghdad',
            'message':       'Thank you for reaching out. I have looked into this and the '
                             'issue was a caching rule on the mobile view, now corrected.\n\n'
                             'Please clear your browser cache and try again. If it persists, '
                             'reply here and I will take another look.',
            'sig_name':      'James Harrington',
            'sig_dept':      'Client Services',
        }),
        # Monitoring alerts. Sample data only, nothing is looked up, so the
        # preview works on an install that has never had an incident.
        'incident-down': ('public/emails/incident_down.html', {
            'recipient_name': 'MED',
            'headline':       'happyholiday.com is not responding',
            'site_name':      'happyholiday.com',
            'client_name':    'Happy Holiday',
            'cause':          'Server did not respond in time',
            'status_code':    None,
            'error':          'Read timed out after 10s',
            'diagnosis':      'DNS resolved and the connection opened, but no response arrived within ten seconds on three consecutive checks.',
            'suggested_fix':  'Check whether Gunicorn is running, then look at load on the server.',
            'fail_count':     3,
            'when':           _preview_now(),
            'panel_url':      f"{getattr(settings, 'SITE_URL', '')}/health/",
        }),
        'incident-recovered': ('public/emails/incident_recovered.html', {
            'recipient_name': 'MED',
            'headline':       'happyholiday.com is back',
            'site_name':      'happyholiday.com',
            'client_name':    'Happy Holiday',
            'cause':          'Server did not respond in time',
            'duration':       '14 minutes',
            'diagnosis':      'DNS resolved and the connection opened, but no response arrived within ten seconds on three consecutive checks.',
            'fail_count':     3,
            'when':           _preview_now(),
            'panel_url':      f"{getattr(settings, 'SITE_URL', '')}/health/",
        }),
        # Renewal notice. The same email goes out four times with a different
        # countdown, so the preview shows the seven day version, which is the
        # middle of the range and exercises the amber styling.
        'renewal-warning': ('public/emails/renewal_warning.html', {
            'recipient_name': 'Sam',
            'site_name':      'Example Client',
            'plan_label':     'Signature',
            'invoice_number': '26080042',
            'amount':         '149.00',
            'days_left':      7,
            'renewal_date':   _preview_now(),
            'pay_url':        'https://panel.example.com/renew/preview-token/',
            'operator_domain': 'www.example.com',
        }),
    }

    if template not in templates:
        from django.http import HttpResponse
        links = ''.join(f'<li><a href="/products/email-preview/{k}/">{k}</a></li>' for k in templates)
        return HttpResponse(f'<h2>Email Previews</h2><ul>{links}</ul>')

    tpl, context = templates[template]
    from django.template.loader import render_to_string
    html = render_to_string(tpl, context)
    from django.http import HttpResponse
    return HttpResponse(html)


@csrf_exempt
@require_POST
def stripe_webhook(request):
    """
    Stripe telling us a payment completed.

    Every failure here is logged. It used to return 400 and say nothing, so
    when the signing secret did not match, Stripe retried for hours, the order
    sat unpaid, and the only trace anywhere was a counter in Stripe's own
    dashboard. Silence is the worst possible behaviour for the one endpoint
    that records money.

    This is not the only path that records a payment. The return page confirms
    too, and `manage.py reconcile_payments` asks Stripe directly on a
    schedule. All three exist because each can fail on its own.
    """
    from apps.ecommerce.stripe_utils import _get_stripe_webhook_secret

    payload    = request.body
    sig_header = request.META.get('HTTP_STRIPE_SIGNATURE', '')

    if not _get_stripe_webhook_secret():
        logger.error(
            'stripe_webhook: no signing secret in Settings, so every event '
            'Stripe sends will be rejected. Set it under Settings, Stripe.'
        )
        return HttpResponse(status=400)

    try:
        event = construct_webhook_event(payload, sig_header)
    except ValueError as exc:
        logger.error('stripe_webhook: payload would not parse: %s', exc)
        return HttpResponse(status=400)
    except stripe.error.SignatureVerificationError as exc:
        logger.error(
            'stripe_webhook: signature rejected. The secret in Settings does '
            'not match this endpoint in Stripe. %s', exc
        )
        return HttpResponse(status=400)

    if event['type'] == 'checkout.session.completed':
        session  = event['data']['object']
        order_id = session.get('metadata', {}).get('order_id')
        is_deposit = session.get('metadata', {}).get('is_deposit') == 'true'
        amount_total = session.get('amount_total', 0) / 100  # Convert pence to GBP

        try:
            order = Order.objects.get(pk=order_id)
            recorded = order.mark_paid(
                amount=Decimal(str(amount_total)),
                payment_intent_id=session.get('payment_intent', ''),
            )
            order.stripe_session_id = session['id']
            order.save(update_fields=['stripe_session_id'])
            # Stripe retries a webhook for up to three days, and every retry
            # used to send another confirmation. Only a payment that was
            # actually recorded gets an email.
            if recorded:
                from apps.ecommerce.email_utils import (
                    send_order_confirmation,
                    send_order_admin_notification,
                )
                send_order_confirmation(order)
                send_order_admin_notification(order)

            # Trigger domain registrations if any
            _process_domain_registrations(order)

        except Order.DoesNotExist:
            logger.warning('stripe_webhook: order_id=%s not found', order_id)
        except Exception as exc:
            logger.error('stripe_webhook: failed for order_id=%s: %s', order_id, exc, exc_info=True)
            return HttpResponse(status=500)

    return HttpResponse(status=200)


def _process_domain_registrations(order):
    """Register any domains in this order via Namecheap API."""
    if not settings.NAMECHEAP_API_KEY:
        return

    for item in order.items.filter(product__product_type=Product.TYPE_DOMAIN):
        if not item.domain_name:
            continue
        # Create registration record
        reg, created = DomainRegistration.objects.get_or_create(
            order_item=item,
            defaults={
                'domain_name': item.domain_name,
                'tld': item.domain_tld,
                'registration_years': item.registration_years,
                'status': DomainRegistration.STATUS_PENDING,
            }
        )
        if not created:
            continue

        # Build registrant from order
        name_parts = order.customer_name.split(' ', 1)
        first = name_parts[0]
        last  = name_parts[1] if len(name_parts) > 1 else first

        registrant = {
            'FirstName':     first,
            'LastName':      last,
            'Address1':      'N/A',
            'City':          'London',
            'StateProvince': 'Greater London',
            'PostalCode':    'N/A',
            'Country':       'GB',
            'Phone':         order.customer_phone or '+44.0000000000',
            'EmailAddress':  order.customer_email,
        }

        try:
            order_id = namecheap.register_domain(
                item.domain_name,
                item.registration_years,
                registrant,
            )
            reg.registrar_order_id = order_id
            reg.status = DomainRegistration.STATUS_REGISTERED
            reg.registered_at = timezone.now()
            reg.save()
        except Exception as e:
            reg.status = DomainRegistration.STATUS_FAILED
            reg.error_message = str(e)
            reg.save()
