"""
Marketplace public API views — browsing, search, product detail, cart, wishlist, reviews.

All views here are for the public-facing marketplace (authenticated user context).
These DO NOT touch bundle purchase flows or distributor logic.

URL prefix: /api/v1/web/marketplace/
"""

import re
from datetime import timedelta
from decimal import Decimal
from uuid import uuid4

from rest_framework.views import APIView
from rest_framework.generics import (
    ListAPIView, RetrieveAPIView, CreateAPIView, DestroyAPIView, UpdateAPIView
)
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework import status, filters, serializers
from django_filters.rest_framework import DjangoFilterBackend
from django.db import transaction
from django.db.models import Q, Avg, Count, Prefetch, F
from django.shortcuts import get_object_or_404
from django.http import Http404
from django.utils import timezone

from apps.business.products.models import Product
from apps.business.orders.models import Order
from apps.business.orders.services.order_service import is_digital_product
from apps.business.schema_compat import defer_missing_product_part_payment_fields
from apps.business.wallet.models import Wallet, SuperCoinWallet
from apps.business.wallet.services.wallet_service import WalletService
from apps.masterdata.platform_setting import PlatformSetting
from apps.payments.models import PaymentTransaction
from apps.platform.settings.models import QRConfig
from apps.business.marketplace.models_part1 import (
    Category, SubCategory, Brand, ProductVariant, Vendor,
)
from apps.business.marketplace.models_part2 import (
    ShoppingCart, CartItem, Wishlist, WishlistItem,
    FlashOffer, CouponCode, CouponUsage, OrderItem, OrderShipment, OrderStatusHistory,
)
from apps.business.marketplace.models_part3 import (
    ProductReview, PincodeServiceability, DeliveryEstimation,
)
from apps.business.marketplace.serializers import (
    CategorySerializer,
    BrandSerializer,
    MarketplaceProductCardSerializer,
    MarketplaceProductDetailSerializer,
    ProductReviewSerializer,
    CreateReviewSerializer,
    CartSerializer,
    CartItemSerializer,
    AddToCartSerializer,
    UpdateCartItemSerializer,
    WishlistItemSerializer,
    FlashOfferSerializer,
    CouponValidateSerializer,
    PincodeServiceabilitySerializer,
)


# ---------------------------------------------------------------------------
# Category & brand browsing
# ---------------------------------------------------------------------------

class CategoryListView(ListAPIView):
    """GET /marketplace/categories/ — list all active categories with subcategories."""
    permission_classes = [AllowAny]
    serializer_class = CategorySerializer
    queryset = Category.objects.filter(is_active=True).prefetch_related(
        'subcategories'
    ).order_by('display_order', 'name')


class BrandListView(ListAPIView):
    """GET /marketplace/brands/ — list all active brands."""
    permission_classes = [AllowAny]
    serializer_class = BrandSerializer
    queryset = Brand.objects.filter(is_active=True).order_by('name')


# ---------------------------------------------------------------------------
# Product listing with filtering, sorting, search
# ---------------------------------------------------------------------------

class MarketplaceProductListView(ListAPIView):
    """
    GET /marketplace/products/
    Query params:
      - q: text search (name, description, tags)
      - category: category slug
      - subcategory: subcategory slug
      - brand: brand slug
      - vendor: vendor id
      - min_price, max_price: price range filter
      - min_rating: minimum average rating
      - in_stock: true/false
      - wallet_eligible: true/false (allow_main_wallet=true)
      - supercoin_eligible: true/false (allow_supercoins=true)
      - type: BUNDLE | RETAIL | ALL (default ALL)
      - sort: price_asc | price_desc | rating | newest | name
      - page, page_size
    """
    permission_classes = [AllowAny]
    serializer_class = MarketplaceProductCardSerializer

    def get_queryset(self):
        # Include ACTIVE marketplace products AND all active bundle products
        qs = Product.objects.filter(
            is_active=True,
        ).filter(
            Q(marketplace_status='ACTIVE') | Q(opportunity_bundle__isnull=False)
        ).select_related(
            'marketplace_category', 'brand', 'vendor', 'opportunity_bundle'
        ).prefetch_related('images')

        params = self.request.query_params

        # Text search
        q = params.get('q', '').strip()
        if q:
            qs = qs.filter(
                Q(name__icontains=q) |
                Q(description__icontains=q) |
                Q(sku__icontains=q)
            )

        # Category filter
        cat_slug = params.get('category', '').strip()
        if cat_slug:
            qs = qs.filter(marketplace_category__slug=cat_slug)

        # Subcategory filter (through category)
        subcat_slug = params.get('subcategory', '').strip()
        if subcat_slug:
            qs = qs.filter(marketplace_category__subcategories__slug=subcat_slug)

        # Brand filter
        brand_slug = params.get('brand', '').strip()
        if brand_slug:
            qs = qs.filter(brand__slug=brand_slug)

        # Vendor filter
        vendor_id = params.get('vendor', '').strip()
        if vendor_id:
            qs = qs.filter(vendor__id=vendor_id)

        # Price range
        min_price = params.get('min_price')
        max_price = params.get('max_price')
        if min_price:
            try:
                qs = qs.filter(base_cost__gte=float(min_price))
            except ValueError:
                pass
        if max_price:
            try:
                qs = qs.filter(base_cost__lte=float(max_price))
            except ValueError:
                pass

        # Rating filter
        min_rating = params.get('min_rating')
        if min_rating:
            try:
                qs = qs.filter(average_rating__gte=float(min_rating))
            except ValueError:
                pass

        # Wallet eligibility
        if params.get('wallet_eligible') == 'true':
            qs = qs.filter(allow_main_wallet=True)

        # Super coin eligibility
        if params.get('supercoin_eligible') == 'true':
            qs = qs.filter(allow_supercoins=True)

        # Bundle vs Retail filter
        product_type = params.get('type', 'ALL').upper()
        if product_type == 'BUNDLE':
            qs = qs.filter(opportunity_bundle__isnull=False)
        elif product_type == 'RETAIL':
            qs = qs.filter(opportunity_bundle__isnull=True)

        # Sorting
        sort = params.get('sort', 'newest')
        sort_map = {
            'price_asc': 'base_cost',
            'price_desc': '-base_cost',
            'rating': '-average_rating',
            'newest': '-created_at',
            'name': 'name',
        }
        qs = qs.order_by(sort_map.get(sort, '-created_at'))

        return qs

    def list(self, request, *args, **kwargs):
        queryset = self.get_queryset()
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True, context={'request': request})
            return self.get_paginated_response(serializer.data)
        serializer = self.get_serializer(queryset, many=True, context={'request': request})
        return Response(serializer.data)


class MarketplaceProductDetailView(RetrieveAPIView):
    """GET /marketplace/products/<slug>/ — full product detail for PDP."""
    permission_classes = [AllowAny]
    serializer_class = MarketplaceProductDetailSerializer
    lookup_field = 'slug'

    def get_queryset(self):
        return Product.objects.filter(
            is_active=True,
        ).select_related('marketplace_category', 'brand', 'vendor', 'opportunity_bundle')

    def get_object(self):
        """Resolve product by current slug, with fallback for legacy load-test slugs."""
        try:
            return super().get_object()
        except Http404:
            slug = str(self.kwargs.get(self.lookup_field, '')).strip().lower()
            legacy_match = re.match(r'^tw-load-product-(\d+)$', slug)
            if not legacy_match:
                raise

            sequence = int(legacy_match.group(1))
            suffix = f'-{sequence:04d}'

            # First try deterministic suffix mapping from migrated slugs.
            by_suffix = self.get_queryset().filter(
                marketplace_status='ACTIVE',
                slug__endswith=suffix,
            ).order_by('-updated_at').first()
            if by_suffix:
                return by_suffix

            # Last fallback: preserve original ordinal position in active marketplace list.
            ordered = self.get_queryset().filter(marketplace_status='ACTIVE').order_by('created_at', 'id')
            index = sequence - 1
            if index < 0 or index >= ordered.count():
                raise
            return ordered[index]

    def retrieve(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = self.get_serializer(instance, context={'request': request})
        return Response(serializer.data)


class FeaturedProductsView(APIView):
    """
    GET /marketplace/featured/
    Returns: bundles, trending, new_arrivals, flash_offers, recommended sections.
    """
    permission_classes = [AllowAny]

    def get(self, request):
        base_qs = defer_missing_product_part_payment_fields(Product.objects.filter(
            is_active=True,
        ).filter(
            Q(marketplace_status='ACTIVE') | Q(opportunity_bundle__isnull=False)
        ).select_related('marketplace_category', 'brand', 'vendor', 'opportunity_bundle').prefetch_related('images'))

        ctx = {'request': request}

        # Opportunity bundle products
        bundle_products = base_qs.filter(
            opportunity_bundle__isnull=False
        ).order_by('-created_at')[:8]

        # Retail / marketplace products
        retail_products = base_qs.filter(
            opportunity_bundle__isnull=True
        ).order_by('-created_at')[:12]

        # Top rated
        top_rated = base_qs.filter(
            average_rating__gte=4
        ).order_by('-average_rating')[:8]

        # New arrivals
        new_arrivals = base_qs.order_by('-created_at')[:12]

        # Flash offers
        from django.utils import timezone
        now = timezone.now()
        flash_offers = defer_missing_product_part_payment_fields(FlashOffer.objects.filter(
            is_active=True,
            valid_from__lte=now,
            valid_until__gte=now,
        ).select_related('product'), relation_prefix='product')[:6]

        return Response({
            'bundle_products': MarketplaceProductCardSerializer(bundle_products, many=True, context=ctx).data,
            'retail_products': MarketplaceProductCardSerializer(retail_products, many=True, context=ctx).data,
            'top_rated': MarketplaceProductCardSerializer(top_rated, many=True, context=ctx).data,
            'new_arrivals': MarketplaceProductCardSerializer(new_arrivals, many=True, context=ctx).data,
            'flash_offers': FlashOfferSerializer(flash_offers, many=True, context=ctx).data,
            'categories': CategorySerializer(
                Category.objects.filter(is_active=True).order_by('display_order')[:10], many=True
            ).data,
            'brands': BrandSerializer(
                Brand.objects.filter(is_active=True)[:10], many=True
            ).data,
        })


# ---------------------------------------------------------------------------
# Product reviews
# ---------------------------------------------------------------------------

class ProductReviewListView(ListAPIView):
    """GET /marketplace/products/<product_id>/reviews/"""
    permission_classes = [AllowAny]
    serializer_class = ProductReviewSerializer

    def get_queryset(self):
        product_id = self.kwargs['product_id']
        return ProductReview.objects.filter(
            product_id=product_id,
            moderation_status='APPROVED',
        ).order_by('-created_at')


class ProductReviewCreateView(CreateAPIView):
    """POST /marketplace/products/<product_id>/reviews/"""
    permission_classes = [IsAuthenticated]
    serializer_class = CreateReviewSerializer

    def get_serializer_context(self):
        ctx = super().get_serializer_context()
        ctx['request'] = self.request
        return ctx


# ---------------------------------------------------------------------------
# Cart management
# ---------------------------------------------------------------------------

def _get_or_create_cart(user):
    cart, _ = ShoppingCart.objects.get_or_create(user=user)
    return cart


def _build_address_snapshot(address: dict | None):
    if not address:
        return None
    return {
        'label': address.get('label'),
        'name': address.get('name'),
        'address_line1': address.get('address_line1'),
        'address_line2': address.get('address_line2'),
        'city': address.get('city'),
        'state': address.get('state'),
        'country': address.get('country'),
        'postcode': address.get('postcode'),
        'phone': address.get('phone'),
    }


class RetailCheckoutRequestSerializer(serializers.Serializer):
    billing_address = serializers.DictField(required=False, allow_null=True)
    shipping_address = serializers.DictField(required=False, allow_null=True)
    same_as_billing = serializers.BooleanField(required=False, default=True)
    payment_methods = serializers.ListField(
        child=serializers.CharField(), required=False, allow_empty=True, default=list
    )
    payment_method = serializers.CharField(required=False, allow_blank=True)
    use_super_coins = serializers.BooleanField(required=False, default=False)
    super_coin_amount = serializers.DecimalField(max_digits=12, decimal_places=2, required=False, allow_null=True)
    coupon_code = serializers.CharField(required=False, allow_blank=True, allow_null=True)
    transaction_reference = serializers.CharField(required=False, allow_blank=True, allow_null=True)
    qr_reference = serializers.CharField(required=False, allow_blank=True, allow_null=True)
    payment_transaction_id = serializers.UUIDField(required=False, allow_null=True)
    buyer_gst_number = serializers.CharField(required=False, allow_blank=True, allow_null=True, max_length=15)
    buyer_company_name = serializers.CharField(required=False, allow_blank=True, allow_null=True, max_length=255)


class RetailCheckoutView(APIView):
    """POST /marketplace/checkout/ — place a retail order from the user's cart."""
    permission_classes = [IsAuthenticated]

    MANUAL_METHODS = {'qr_scan', 'bank_transfer', 'phonepe', 'cash_payment', 'razorpay'}
    WALLET_METHODS = {'main_wallet', 'repurchase_wallet', 'super_coins'}

    def _manual_method_enabled(self, method):
        config = QRConfig.objects.filter(is_active=True).first()
        if method == 'cash_payment':
            return True
        if method == 'razorpay':
            return bool(
                config
                and config.enable_razorpay
                and (
                    PlatformSetting.get_value('razorpay_test_key_id', '')
                    or PlatformSetting.get_value('razorpay_live_key_id', '')
                )
            )
        if method == 'qr_scan':
            return bool(config and config.enable_qr_scan)
        if method == 'bank_transfer':
            return bool(config and config.enable_bank_transfer)
        if method == 'phonepe':
            return bool(config and config.enable_phonepe)
        return False

    def post(self, request):
        serializer = RetailCheckoutRequestSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data

        cart = _get_or_create_cart(request.user)
        cart_items = list(cart.items.select_related('product', 'variant'))
        if not cart_items:
            return Response({'detail': 'Cart is empty.'}, status=status.HTTP_400_BAD_REQUEST)

        allowed_payment_methods = {choice[0] for choice in Order.PAYMENT_METHOD_CHOICES}
        selected_methods = []
        for method in data.get('payment_methods') or []:
            normalized = str(method).strip().lower()
            if normalized and normalized not in selected_methods:
                selected_methods.append(normalized)

        selected_payment = (data.get('payment_method') or '').strip().lower()
        if selected_payment and selected_payment not in selected_methods:
            selected_methods.append(selected_payment)

        invalid_methods = [method for method in selected_methods if method not in allowed_payment_methods]
        if invalid_methods:
            return Response({'detail': f'Invalid payment method: {invalid_methods[0]}'}, status=status.HTTP_400_BAD_REQUEST)

        selected_wallet_methods = [method for method in selected_methods if method in self.WALLET_METHODS]
        selected_manual_methods = [method for method in selected_methods if method in self.MANUAL_METHODS]

        if len(selected_manual_methods) > 1:
            return Response({'detail': 'Choose only one external payment method.'}, status=status.HTTP_400_BAD_REQUEST)

        selected_manual_method = selected_manual_methods[0] if selected_manual_methods else None
        if selected_payment and selected_payment in self.MANUAL_METHODS:
            selected_manual_method = selected_payment

        if not selected_wallet_methods and not selected_manual_method:
            return Response({'detail': 'Choose at least one payment option.'}, status=status.HTTP_400_BAD_REQUEST)

        billing_snapshot = _build_address_snapshot(data.get('billing_address'))
        shipping_snapshot = _build_address_snapshot(data.get('shipping_address')) or billing_snapshot
        if not billing_snapshot or not shipping_snapshot:
            return Response({'detail': 'Billing and shipping address are required.'}, status=status.HTTP_400_BAD_REQUEST)

        subtotal = Decimal('0.00')
        line_items = []
        for item in cart_items:
            unit_price = Decimal(str(item.unit_price or item.product.mrp or item.product.base_cost))
            line_total = unit_price * item.quantity
            subtotal += line_total
            line_items.append((item, unit_price, line_total))

        cart_wallet_eligibility = {
            'main_wallet': all(item.product.allow_main_wallet for item in cart_items),
            'repurchase_wallet': all(item.product.allow_repurchase for item in cart_items),
            'super_coins': all(item.product.allow_supercoins for item in cart_items),
        }
        for wallet_method in selected_wallet_methods:
            if not cart_wallet_eligibility.get(wallet_method, False):
                return Response(
                    {'detail': f'{wallet_method.replace("_", " ").title()} is not allowed for all cart items.'},
                    status=status.HTTP_400_BAD_REQUEST,
                )

        if selected_manual_method and not self._manual_method_enabled(selected_manual_method):
            return Response(
                {'detail': f'{selected_manual_method.replace("_", " ").title()} is not enabled right now.'},
                status=status.HTTP_400_BAD_REQUEST,
            )

        shipping_charge = Decimal('0.00') if subtotal > Decimal('499.00') else Decimal('49.00')

        coupon_discount = Decimal('0.00')
        coupon = None
        coupon_code = (data.get('coupon_code') or '').strip().upper()
        if coupon_code:
            now = timezone.now()
            try:
                coupon = CouponCode.objects.get(
                    code=coupon_code,
                    is_active=True,
                    valid_from__lte=now,
                    valid_until__gte=now,
                )
            except CouponCode.DoesNotExist:
                return Response({'detail': 'Invalid or expired coupon.'}, status=status.HTTP_400_BAD_REQUEST)

            if coupon.min_order_amount and subtotal < coupon.min_order_amount:
                return Response({'detail': f'Minimum order amount is ₹{coupon.min_order_amount}.'}, status=status.HTTP_400_BAD_REQUEST)

            user_usage = CouponUsage.objects.filter(coupon=coupon, user=request.user).count()
            if coupon.max_usage_per_user and user_usage >= coupon.max_usage_per_user:
                return Response({'detail': 'Coupon already used.'}, status=status.HTTP_400_BAD_REQUEST)

            if coupon.discount_type == 'PERCENTAGE':
                coupon_discount = (coupon.discount_value / Decimal('100')) * subtotal
                if coupon.max_discount:
                    coupon_discount = min(coupon_discount, coupon.max_discount)
            else:
                coupon_discount = min(coupon.discount_value, subtotal)

        gross_total = max(Decimal('0.00'), subtotal - coupon_discount + shipping_charge)
        requested_super_coins = Decimal(str(data.get('super_coin_amount') or '0'))
        if requested_super_coins < 0:
            return Response({'detail': 'Super coin amount cannot be negative.'}, status=status.HTTP_400_BAD_REQUEST)

        use_super_coins = bool(data.get('use_super_coins') or 'super_coins' in selected_wallet_methods)
        super_coin_used = Decimal('0.00')

        with transaction.atomic():
            wallet, _ = Wallet.objects.select_for_update().get_or_create(user=request.user)
            super_wallet, _ = SuperCoinWallet.objects.select_for_update().get_or_create(user=request.user)

            if use_super_coins and gross_total > Decimal('0.00'):
                requested = requested_super_coins if requested_super_coins > Decimal('0.00') else gross_total
                super_coin_used = min(gross_total, super_wallet.balance, requested)

            payable_after_super = max(Decimal('0.00'), gross_total - super_coin_used)
            main_wallet_used = Decimal('0.00')
            repurchase_wallet_used = Decimal('0.00')

            remaining_after_wallets = payable_after_super
            if 'main_wallet' in selected_wallet_methods and remaining_after_wallets > Decimal('0.00'):
                main_wallet_used = min(wallet.main_balance, remaining_after_wallets)
                remaining_after_wallets = max(Decimal('0.00'), remaining_after_wallets - main_wallet_used)

            if 'repurchase_wallet' in selected_wallet_methods and remaining_after_wallets > Decimal('0.00'):
                repurchase_wallet_used = min(wallet.repurchase_balance, remaining_after_wallets)
                remaining_after_wallets = max(Decimal('0.00'), remaining_after_wallets - repurchase_wallet_used)

            manual_payment = selected_manual_method in {'qr_scan', 'bank_transfer', 'phonepe', 'cash_payment'} and remaining_after_wallets > Decimal('0.00')
            transaction_reference = (data.get('transaction_reference') or data.get('qr_reference') or '').strip()

            if selected_manual_method in {'qr_scan', 'bank_transfer', 'phonepe'} and remaining_after_wallets > Decimal('0.00') and not transaction_reference:
                return Response({'detail': 'Transaction reference is required for this payment method.'}, status=status.HTTP_400_BAD_REQUEST)

            if not selected_manual_method and remaining_after_wallets > Decimal('0.00'):
                return Response(
                    {'detail': 'Selected wallets do not cover the payable amount. Choose an external payment method.'},
                    status=status.HTTP_400_BAD_REQUEST,
                )

            payment_transaction = None
            if selected_manual_method == 'razorpay' and remaining_after_wallets > Decimal('0.00'):
                payment_transaction_id = data.get('payment_transaction_id')
                if not payment_transaction_id:
                    return Response({'detail': 'Razorpay payment must be completed before placing the order.'}, status=status.HTTP_400_BAD_REQUEST)
                payment_transaction = PaymentTransaction.objects.select_for_update().filter(
                    id=payment_transaction_id,
                    user=request.user,
                    provider='razorpay',
                    status='success',
                ).first()
                if not payment_transaction:
                    return Response({'detail': 'Verified Razorpay payment not found.'}, status=status.HTTP_400_BAD_REQUEST)
                if Decimal(str(payment_transaction.amount)) != remaining_after_wallets:
                    return Response({'detail': 'Razorpay payment amount does not match the remaining payable amount.'}, status=status.HTTP_400_BAD_REQUEST)

            order_status = 'PAYMENT_REVIEW' if manual_payment else 'COMPLETED'
            order_reference = f"RET-{timezone.now():%y%m%d%H%M}-{uuid4().hex[:8].upper()}"
            client_reference_id = f"retail-{uuid4().hex}"

            product = cart_items[0].product
            order_payment_method = (
                selected_manual_method
                or ('main_wallet' if main_wallet_used > Decimal('0.00') else None)
                or ('repurchase_wallet' if repurchase_wallet_used > Decimal('0.00') else None)
                or ('super_coins' if super_coin_used > Decimal('0.00') else 'main_wallet')
            )
            order = Order.objects.create(
                user=request.user,
                product=product,
                amount=gross_total,
                voucher_amount=coupon_discount,
                super_coin_amount=super_coin_used,
                cash_amount=remaining_after_wallets,
                status=order_status,
                reference_id=order_reference,
                client_reference_id=client_reference_id,
                payment_method=order_payment_method,
                order_type='RETAIL',
                billing_address=billing_snapshot,
                shipping_address=shipping_snapshot,
                buyer_gst_number=(data.get('buyer_gst_number') or '').strip().upper() or None,
                buyer_company_name=(data.get('buyer_company_name') or '').strip() or None,
                qr_reference=transaction_reference or None,
                payment_transaction=payment_transaction,
            )

            if order.status == 'COMPLETED' and is_digital_product(product):
                order.franchise_fulfillment_status = 'COLLECTED'
                order.save(update_fields=['franchise_fulfillment_status'])

            if coupon:
                CouponUsage.objects.create(coupon=coupon, user=request.user, order=order)
                CouponCode.objects.filter(id=coupon.id).update(usage_count=F('usage_count') + 1)

            if super_coin_used > Decimal('0.00'):
                WalletService.debit_supercoins(
                    user=request.user,
                    amount=super_coin_used,
                    source_type='RETAIL_CHECKOUT',
                    reference_id=f'{order.reference_id}-SC',
                    metadata={'order_id': str(order.id)},
                )

            if main_wallet_used > Decimal('0.00'):
                WalletService.debit_main_wallet(
                    user=request.user,
                    amount=main_wallet_used,
                    source_type='RETAIL_CHECKOUT',
                    reference_id=f'{order.reference_id}-MAIN',
                )

            if repurchase_wallet_used > Decimal('0.00'):
                WalletService.debit_repurchase_wallet(
                    user=request.user,
                    amount=repurchase_wallet_used,
                    source_type='RETAIL_CHECKOUT',
                    reference_id=f'{order.reference_id}-REPURCHASE',
                )

            if order.status != 'PAYMENT_REVIEW':
                order.status = 'COMPLETED'
            if remaining_after_wallets <= Decimal('0.00'):
                order.status = 'PAYMENT_REVIEW'
                if not manual_payment:
                    order.status = 'COMPLETED'
            order.cash_amount = remaining_after_wallets

            order.save(update_fields=['cash_amount', 'status', 'voucher_amount', 'super_coin_amount', 'billing_address', 'shipping_address', 'buyer_gst_number', 'buyer_company_name', 'qr_reference', 'order_type', 'payment_method', 'payment_transaction'])

            total_quantity = 0
            for item, unit_price, line_total in line_items:
                total_quantity += item.quantity
                fulfillment_mode = 'VENDOR_FULFILLED' if getattr(item.product, 'vendor_id', None) else 'COMPANY_FULFILLED'
                OrderItem.objects.create(
                    order=order,
                    product=item.product,
                    variant=item.variant,
                    quantity=item.quantity,
                    unit_price=unit_price,
                    total_price=line_total,
                    fulfillment_mode=fulfillment_mode,
                    vendor=getattr(item.product, 'vendor', None),
                    item_status='CONFIRMED' if order.status == 'COMPLETED' else 'PENDING',
                )

            OrderShipment.objects.get_or_create(
                order=order,
                defaults={
                    'shipping_method': 'STANDARD',
                    'shipping_amount': shipping_charge,
                    'estimated_delivery': (timezone.now() + timedelta(days=4)).date(),
                    'status': 'PENDING',
                },
            )

            OrderStatusHistory.objects.create(
                order=order,
                status='PAYMENT_REVIEW' if order.status == 'PAYMENT_REVIEW' else 'COMPLETED',
                description=(
                    'Retail order placed and awaiting payment review.'
                    if order.status == 'PAYMENT_REVIEW'
                    else 'Retail order placed and completed.'
                ),
                recorded_by='SYSTEM',
            )

            cart.items.all().delete()
            cart.promotions.all().delete()

            response_payload = {
                'order_id': str(order.id),
                'reference_id': order.reference_id,
                'status': order.status,
                'payment_method': order.payment_method,
                'order_total': str(order.amount),
                'cash_amount': str(order.cash_amount),
                'super_coin_amount': str(order.super_coin_amount),
                'main_wallet_amount': str(main_wallet_used),
                'repurchase_wallet_amount': str(repurchase_wallet_used),
                'coupon_discount': str(coupon_discount),
                'shipping_charge': str(shipping_charge),
                'item_count': total_quantity,
                'pending_approval': order.status == 'PAYMENT_REVIEW',
                'message': (
                    (
                        'Cash payment confirmation pending. Admin will approve and activate the order.'
                        if order.payment_method == 'cash_payment'
                        else 'Your payment has been submitted for review. Admin will approve and activate the order.'
                    ) if order.status == 'PAYMENT_REVIEW'
                    else 'Order placed successfully.'
                ),
            }

            return Response({'success': True, 'data': response_payload}, status=status.HTTP_201_CREATED)


class CartView(APIView):
    """GET /marketplace/cart/ — retrieve user's cart."""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        cart = _get_or_create_cart(request.user)
        return Response(CartSerializer(cart).data)


class CartAddItemView(APIView):
    """POST /marketplace/cart/add/ — add item to cart."""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        serializer = AddToCartSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        data = serializer.validated_data

        product = get_object_or_404(Product, id=data['product_id'], is_active=True)
        variant = None
        if data.get('variant_id'):
            variant = get_object_or_404(ProductVariant, id=data['variant_id'], product=product, is_active=True)

        # Capture a stable unit price snapshot at add-to-cart time.
        unit_price = None
        if variant and variant.selling_price:
            unit_price = variant.selling_price
        elif getattr(product, 'selling_price', None):
            unit_price = product.selling_price
        elif product.mrp:
            unit_price = product.mrp
        else:
            unit_price = product.base_cost

        cart = _get_or_create_cart(request.user)
        item, created = CartItem.objects.get_or_create(
            cart=cart, product=product, variant=variant,
            defaults={
                'quantity': data['quantity'],
                'unit_price': unit_price,
                'price_expires_at': timezone.now() + timedelta(hours=1),
            },
        )
        if not created:
            item.quantity = min(item.quantity + data['quantity'], 100)
            item.save()

        return Response(CartSerializer(cart).data, status=status.HTTP_200_OK)


class CartUpdateItemView(APIView):
    """PATCH /marketplace/cart/items/<item_id>/ — update quantity (0 = remove)."""
    permission_classes = [IsAuthenticated]

    def patch(self, request, item_id):
        serializer = UpdateCartItemSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        qty = serializer.validated_data['quantity']

        cart = _get_or_create_cart(request.user)
        item = get_object_or_404(CartItem, id=item_id, cart=cart)

        if qty == 0:
            item.delete()
        else:
            item.quantity = qty
            item.save()

        return Response(CartSerializer(cart).data)


class CartClearView(APIView):
    """DELETE /marketplace/cart/ — clear cart."""
    permission_classes = [IsAuthenticated]

    def delete(self, request):
        cart = _get_or_create_cart(request.user)
        cart.items.all().delete()
        return Response({'detail': 'Cart cleared.'}, status=status.HTTP_200_OK)


# ---------------------------------------------------------------------------
# Wishlist
# ---------------------------------------------------------------------------

class WishlistView(APIView):
    """GET /marketplace/wishlist/ — retrieve wishlist."""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        wishlist, _ = Wishlist.objects.get_or_create(user=request.user)
        items = wishlist.items.select_related('product').all()
        return Response(WishlistItemSerializer(items, many=True).data)


class WishlistToggleView(APIView):
    """POST /marketplace/wishlist/toggle/ — add or remove product from wishlist."""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        product_id = request.data.get('product_id')
        if not product_id:
            return Response({'detail': 'product_id required.'}, status=status.HTTP_400_BAD_REQUEST)

        product = get_object_or_404(Product, id=product_id, is_active=True)
        wishlist, _ = Wishlist.objects.get_or_create(user=request.user)
        item = wishlist.items.filter(product=product).first()

        if item:
            item.delete()
            return Response({'action': 'removed', 'product_id': str(product_id)})

        WishlistItem.objects.create(wishlist=wishlist, product=product)
        return Response({'action': 'added', 'product_id': str(product_id)})


class WishlistStatusView(APIView):
    """GET /marketplace/wishlist/status/?product_ids=id1,id2 — bulk wishlist status."""
    permission_classes = [IsAuthenticated]

    def get(self, request):
        product_ids = request.query_params.get('product_ids', '')
        ids = [i.strip() for i in product_ids.split(',') if i.strip()]
        if not ids:
            return Response({})

        wishlist = Wishlist.objects.filter(user=request.user).first()
        if not wishlist:
            return Response({pid: False for pid in ids})

        wishlisted = set(
            str(pid) for pid in wishlist.items.filter(
                product_id__in=ids
            ).values_list('product_id', flat=True)
        )
        return Response({pid: (pid in wishlisted) for pid in ids})


# ---------------------------------------------------------------------------
# Flash offers
# ---------------------------------------------------------------------------

class FlashOfferListView(ListAPIView):
    """GET /marketplace/flash-offers/ — active flash offers."""
    permission_classes = [AllowAny]
    serializer_class = FlashOfferSerializer

    def get_queryset(self):
        from django.utils import timezone
        now = timezone.now()
        return FlashOffer.objects.filter(
            is_active=True,
            start_datetime__lte=now,
            end_datetime__gte=now,
        ).select_related('product')[:20]


# ---------------------------------------------------------------------------
# Coupon validation
# ---------------------------------------------------------------------------

class CouponValidateView(APIView):
    """POST /marketplace/coupons/validate/ — validate coupon code."""
    permission_classes = [IsAuthenticated]

    def post(self, request):
        serializer = CouponValidateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        code_str = serializer.validated_data['code'].upper()
        cart_total = serializer.validated_data['cart_total']

        from django.utils import timezone
        now = timezone.now()
        try:
            coupon = CouponCode.objects.get(
                code=code_str,
                is_active=True,
                valid_from__lte=now,
                valid_until__gte=now,
            )
        except CouponCode.DoesNotExist:
            return Response({'valid': False, 'detail': 'Invalid or expired coupon.'}, status=400)

        # Check minimum order amount
        if coupon.min_order_amount and cart_total < coupon.min_order_amount:
            return Response({
                'valid': False,
                'detail': f'Minimum order amount is ₹{coupon.min_order_amount}.'
            }, status=400)

        # Check usage limit per user
        user_usage = CouponUsage.objects.filter(coupon=coupon, user=request.user).count()
        if coupon.max_usage_per_user and user_usage >= coupon.max_usage_per_user:
            return Response({'valid': False, 'detail': 'Coupon already used.'}, status=400)

        # Calculate discount
        from decimal import Decimal
        if coupon.discount_type == 'PERCENTAGE':
            discount = (coupon.discount_value / Decimal('100')) * cart_total
            if coupon.max_discount:
                discount = min(discount, coupon.max_discount)
        else:
            discount = min(coupon.discount_value, cart_total)

        return Response({
            'valid': True,
            'code': coupon.code,
            'discount_type': coupon.discount_type,
            'discount_value': str(coupon.discount_value),
            'discount_amount': str(discount.quantize(Decimal('0.01'))),
            'description': coupon.description,
        })


# ---------------------------------------------------------------------------
# Pincode serviceability
# ---------------------------------------------------------------------------

class PincodeServiceabilityView(APIView):
    """GET /marketplace/serviceability/?pincode=110001"""
    permission_classes = [AllowAny]

    def get(self, request):
        pincode = request.query_params.get('pincode', '').strip()
        if not pincode:
            return Response({'detail': 'pincode required.'}, status=400)

        try:
            svc = PincodeServiceability.objects.get(pincode=pincode)
            return Response(PincodeServiceabilitySerializer(svc).data)
        except PincodeServiceability.DoesNotExist:
            return Response({
                'pincode': pincode,
                'is_serviceable': False,
                'estimated_days_min': None,
                'estimated_days_max': None,
                'cod_available': False,
            })


class ProductDeliveryEstimationView(APIView):
    """GET /marketplace/products/<product_id>/delivery/?pincode=110001"""
    permission_classes = [AllowAny]

    def get(self, request, product_id):
        pincode = request.query_params.get('pincode', '').strip()
        if not pincode:
            return Response({'detail': 'pincode required.'}, status=400)

        product = get_object_or_404(Product, id=product_id, is_active=True)

        try:
            est = DeliveryEstimation.objects.get(product=product, pincode=pincode)
            return Response({
                'is_serviceable': est.is_serviceable,
                'estimated_days_min': est.estimated_days_min,
                'estimated_days_max': est.estimated_days_max,
                'delivery_charge': str(est.delivery_charge) if est.delivery_charge else '0.00',
                'cod_available': est.cod_available,
                'franchise_available': est.franchise_available,
            })
        except DeliveryEstimation.DoesNotExist:
            # Fallback to general pincode serviceability
            try:
                svc = PincodeServiceability.objects.get(pincode=pincode)
                return Response({
                    'is_serviceable': svc.is_serviceable,
                    'estimated_days_min': svc.estimated_days_min,
                    'estimated_days_max': svc.estimated_days_max,
                    'delivery_charge': '0.00',
                    'cod_available': svc.cod_available,
                    'franchise_available': False,
                })
            except PincodeServiceability.DoesNotExist:
                return Response({
                    'is_serviceable': True,  # default: assume serviceable
                    'estimated_days_min': 5,
                    'estimated_days_max': 7,
                    'delivery_charge': '0.00',
                    'cod_available': False,
                    'franchise_available': False,
                })
