import json
import hashlib
import hmac
from datetime import datetime
from decimal import Decimal

from django.conf import settings
from django.contrib.auth.models import User
from django.db import transaction
from django.http import JsonResponse, HttpResponse
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt

from payments.models import Payment
from payments.paystack import PaystackClient, compute_booking_price
from businesses.models import Business
from sales.models import Sale, SaleItem
from products.models import Product


class BookingQuoteView(View):
    """POST: Compute booking price server-side from date range and pricing model."""

    def post(self, request):
        try:
            data = json.loads(request.body)
        except json.JSONDecodeError:
            return JsonResponse({'error': 'Invalid JSON.'}, status=400)

        start_str = data.get('start_date')
        end_str = data.get('end_date')
        start_time = data.get('start_time', '00:00')
        end_time = data.get('end_time', '23:59')
        pricing_model = data.get('pricing_model', 'daily_rate')

        if not start_str or not end_str:
            return JsonResponse({'error': 'Start and end dates are required.'}, status=400)

        try:
            start_at = timezone.make_aware(
                datetime.strptime(f"{start_str} {start_time}", "%Y-%m-%d %H:%M")
            )
            end_at = timezone.make_aware(
                datetime.strptime(f"{end_str} {end_time}", "%Y-%m-%d %H:%M")
            )
        except ValueError:
            return JsonResponse({'error': 'Invalid date format. Use YYYY-MM-DD.'}, status=400)

        now = timezone.now()
        if start_at < now:
            return JsonResponse({'error': 'Cannot select a date/time in the past.'}, status=400)
        if end_at <= start_at:
            return JsonResponse({'error': 'End date must be after start date.'}, status=400)

        price, num_days = compute_booking_price(start_at, end_at, pricing_model)

        return JsonResponse({
            'price': str(price),
            'num_days': num_days,
            'pricing_model': pricing_model,
            'daily_rate': str(settings.BOOKING_DAILY_RATE),
            'commission_rate': str(settings.BOOKING_COMMISSION_RATE),
            'description': f"{num_days} day{'s' if num_days != 1 else ''}" + (
                f" -- GHS {price}" if pricing_model == 'daily_rate'
                else f" -- {settings.BOOKING_COMMISSION_RATE}% commission after sale (GHS 10.00 activation fee)"
            ),
        })


class BookingInitiateView(View):
    """POST: Validate landing page booking form, create Paystack checkout session."""

    def post(self, request):
        try:
            data = json.loads(request.body)
        except json.JSONDecodeError:
            return JsonResponse({'error': 'Invalid JSON.'}, status=400)

        auth_method = data.get('auth_method', 'magic')

        # Validate required fields
        required = ['business_name', 'phone', 'email',
                     'start_date', 'end_date', 'start_time', 'end_time', 'pricing_model']
        if auth_method == 'password':
            required.extend(['username', 'password'])

        missing = [f for f in required if not data.get(f)]
        if missing:
            return JsonResponse({'error': f'Missing fields: {", ".join(missing)}'}, status=400)

        if not data.get('accept_terms'):
            return JsonResponse({'error': 'You must accept the terms and conditions.'}, status=400)

        username = data.get('username', '').strip()
        password = data.get('password', '')

        if auth_method == 'password':
            if User.objects.filter(username=username).exists():
                return JsonResponse({'error': 'Username already taken.'}, status=400)
        else:
            if not username:
                base_username = data['email'].split('@')[0]
                username = base_username
                count = 1
                while User.objects.filter(username=username).exists():
                    username = f"{base_username}_{count}"
                    count += 1

        # Parse and validate dates server-side
        try:
            start_at = timezone.make_aware(
                datetime.strptime(f"{data['start_date']} {data['start_time']}", "%Y-%m-%d %H:%M")
            )
            end_at = timezone.make_aware(
                datetime.strptime(f"{data['end_date']} {data['end_time']}", "%Y-%m-%d %H:%M")
            )
        except ValueError:
            return JsonResponse({'error': 'Invalid date/time format.'}, status=400)

        now = timezone.now()
        if start_at < now:
            return JsonResponse({'error': 'Cannot book a date/time in the past.'}, status=400)
        if end_at <= start_at:
            return JsonResponse({'error': 'End must be after start.'}, status=400)

        price, num_days = compute_booking_price(start_at, end_at, data['pricing_model'])

        metadata = {
            'type': 'booking_fee',
            'auth_method': auth_method,
            'username': username,
            'password': password if auth_method == 'password' else '',
            'business_name': data['business_name'],
            'phone': data['phone'],
            'email': data['email'],
            'start_at': start_at.isoformat(),
            'end_at': end_at.isoformat(),
            'pricing_model': data['pricing_model'],
            'num_days': num_days,
        }

        charge_amount = price if data['pricing_model'] == 'daily_rate' else Decimal('10.00')

        client = PaystackClient()
        callback_url = request.build_absolute_uri('/payments/booking/callback/')
        result = client.initialize_transaction(
            email=data['email'],
            amount_ghs=charge_amount,
            callback_url=callback_url,
            metadata=metadata,
        )

        if result['success']:
            Payment.objects.create(
                type='booking_fee',
                paystack_reference=result['reference'],
                amount=charge_amount,
                status='initiated',
                metadata=metadata,
            )
            return JsonResponse({
                'success': True,
                'authorization_url': result['authorization_url'],
                'reference': result['reference'],
            })

        return JsonResponse({'error': result.get('message', 'Payment initiation failed.')}, status=400)


@method_decorator(csrf_exempt, name='dispatch')
class PaystackWebhookView(View):
    """
    POST: Receives Paystack webhooks for booking fees and order checkouts.
    Implements the two-step verification rule:
    1. Verify webhook signature (HMAC SHA512)
    2. Server-to-server Transaction Verify
    """

    def post(self, request):
        signature = request.headers.get('X-Paystack-Signature', '')
        if not signature:
            return HttpResponse(status=400)

        expected = hmac.new(
            settings.PAYSTACK_SECRET_KEY.encode('utf-8'),
            msg=request.body,
            digestmod=hashlib.sha512,
        ).hexdigest()

        if not hmac.compare_digest(expected, signature):
            return HttpResponse(status=400)

        try:
            payload = json.loads(request.body)
        except json.JSONDecodeError:
            return HttpResponse(status=400)

        event = payload.get('event')
        if event != 'charge.success':
            return HttpResponse(status=200)

        reference = payload.get('data', {}).get('reference')
        if not reference:
            return HttpResponse(status=400)

        client = PaystackClient()
        verification = client.verify_transaction(reference)

        if not verification['success']:
            Payment.objects.filter(paystack_reference=reference).update(
                status='failed',
                raw_webhook_payload=payload,
            )
            return HttpResponse(status=200)

        try:
            payment = Payment.objects.get(paystack_reference=reference)
        except Payment.DoesNotExist:
            return HttpResponse(status=200)

        if payment.status == 'verified':
            return HttpResponse(status=200)

        payment.status = 'verified'
        payment.verified_at = timezone.now()
        payment.raw_webhook_payload = payload

        if payment.type in ['booking_fee', 'dashboard_sale_booking']:
            self._handle_booking_payment(payment)
        elif payment.type == 'order_checkout':
            self._handle_order_payment(payment)

        payment.save()
        return HttpResponse(status=200)

    def _handle_booking_payment(self, payment):
        """Atomically create User/Business/Sale on verified booking payment."""
        meta = payment.metadata
        with transaction.atomic():
            # Scenario A: Initial registration from landing page
            if 'email' in meta or 'username' in meta:
                email = meta.get('email', '')
                username = meta.get('username') or email.split('@')[0]
                user = User.objects.filter(email=email).first() or User.objects.filter(username=username).first()
                if not user:
                    if meta.get('auth_method') == 'password' and meta.get('password'):
                        user = User.objects.create_user(
                            username=username,
                            password=meta['password'],
                            email=email,
                        )
                    else:
                        user = User.objects.create_user(
                            username=username,
                            email=email,
                        )
                        user.set_unusable_password()
                        user.save()

                business, _ = Business.objects.get_or_create(
                    user=user,
                    defaults={
                        'name': meta['business_name'],
                        'phone': meta['phone'],
                        'email': meta['email'],
                    }
                )
            # Scenario B: Existing logged-in seller creating a new sale from dashboard
            elif 'business_id' in meta:
                business = Business.objects.get(id=meta['business_id'])
            else:
                return

            start_at = datetime.fromisoformat(meta['start_at'])
            end_at = datetime.fromisoformat(meta['end_at'])
            now = timezone.now()
            status = 'live' if start_at <= now else 'upcoming'

            sale, created = Sale.objects.get_or_create(
                booking_payment=payment,
                defaults={
                    'business': business,
                    'title': meta.get('title', f"{business.name} Sale"),
                    'start_at': start_at,
                    'end_at': end_at,
                    'status': status,
                    'pricing_model': meta.get('pricing_model', 'daily_rate'),
                    'booking_fee_amount': payment.amount,
                }
            )

            if created:
                # Create sale items if provided (dashboard creation)
                items_data = meta.get('items', [])
                for item in items_data:
                    try:
                        product = Product.objects.get(id=item['product_id'], business=business)
                        price = Decimal(str(item['price']))
                        stock = int(item['stock_total'])
                        discount = Decimal(str(item['discount_percent'])) if item.get('discount_percent') else None

                        SaleItem.objects.create(
                            sale=sale,
                            product=product,
                            price=price,
                            discount_percent=discount,
                            stock_total=stock,
                            stock_remaining=stock,
                        )
                    except Exception as e:
                        print(f"Error creating sale item: {e}")

            payment.save()

    def _handle_order_payment(self, payment):
        """Mark order paid on verified order payment (stock was pre-reserved at checkout)."""
        from orders.models import Order
        from sales.models import SaleItem
        from django.db.models import F

        order_id = payment.metadata.get('order_id')
        if not order_id:
            return

        with transaction.atomic():
            try:
                order = Order.objects.select_for_update().get(id=order_id)
            except Order.DoesNotExist:
                return

            if order.status == 'paid':
                return

            if order.status == 'pending':
                order.status = 'paid'
                order.payment = payment
                order.save(update_fields=['status', 'payment', 'updated_at'])
                from sales.tasks import send_order_confirmation
                send_order_confirmation(str(order.id))
            elif order.status == 'failed':
                # Order expired while user was paying. Try re-reserving stock.
                all_reserved = True
                items_to_revert = []
                for item in order.items.all():
                    updated = SaleItem.objects.filter(
                        id=item.sale_item_id,
                        stock_remaining__gte=item.quantity,
                    ).update(
                        stock_remaining=F('stock_remaining') - item.quantity
                    )
                    if updated > 0:
                        items_to_revert.append(item)
                    else:
                        all_reserved = False
                        break

                if all_reserved:
                    order.status = 'paid'
                    order.payment = payment
                    order.save(update_fields=['status', 'payment', 'updated_at'])
                    from sales.tasks import send_order_confirmation
                    send_order_confirmation(str(order.id))
                else:
                    # Revert any items we did temporarily decrement
                    for item in items_to_revert:
                        SaleItem.objects.filter(id=item.sale_item_id).update(
                            stock_remaining=F('stock_remaining') + item.quantity
                        )
                    order.payment = payment
                    order.save(update_fields=['payment', 'updated_at'])


class BookingCallbackView(View):
    """GET: Redirect target after Paystack checkout."""

    def get(self, request):
        reference = request.GET.get('reference', '')
        from django.shortcuts import render
        return render(request, 'booking/success.html', {
            'reference': reference,
        })


class BookingStatusView(View):
    """
    GET: Poll endpoint for booking payment confirmation status.
    Directly queries Paystack server-to-server to verify transaction
    in real time if webhooks are delayed or testing locally.
    """

    def get(self, request, reference):
        try:
            payment = Payment.objects.get(
                paystack_reference=reference,
            )
        except Payment.DoesNotExist:
            return JsonResponse({'status': 'not_found'}, status=404)

        if payment.status != 'verified':
            # Perform inline server-to-server verification check with Paystack
            client = PaystackClient()
            verification = client.verify_transaction(reference)

            if verification['success']:
                payment.status = 'verified'
                payment.verified_at = timezone.now()
                payment.save()

                webhook_handler = PaystackWebhookView()
                if payment.type in ['booking_fee', 'dashboard_sale_booking']:
                    webhook_handler._handle_booking_payment(payment)
                elif payment.type == 'order_checkout':
                    webhook_handler._handle_order_payment(payment)

        if payment.status == 'verified':
            return JsonResponse({
                'status': 'verified',
                'redirect_url': '/dashboard/sales/',
            })

        return JsonResponse({'status': payment.status})
