
from datetime import timedelta

from django.db.models import Q
from django.utils import timezone
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status, permissions
from apps.core.utils import error_response
from apps.business.orders.serializers import PurchaseRequestSerializer
from apps.business.products.models import Product
from apps.business.distributor.models import DistributorID
from apps.business.orders.models import Order
from apps.accounts.models import User
from apps.business.orders.services.order_service import (
    execute_purchase,
    create_pending_payment_order,
    calculate_part_payment_breakdown,
    settle_part_payment_order,
    _record_part_payment_log,
    MANUAL_PAYMENT_METHODS,
    REFERENCE_REQUIRED_PAYMENT_METHODS,
)
import logging
from apps.core.events import emit_event
from apps.platform.accounts.throttles import PurchaseRateThrottle
from apps.geo.services.franchise_service import create_company_to_store_transfer, resolve_assignment_for_user


def _apply_order_context(order, request, product):
    """Snapshot address + GST context onto an order and save."""
    billing_addr = request.data.get('billing_address')
    shipping_addr = request.data.get('shipping_address')
    buyer_gst = (request.data.get('buyer_gst_number') or '').strip().upper() or None
    buyer_company = (request.data.get('buyer_company_name') or '').strip() or None
    place_supply = (request.data.get('place_of_supply') or '').strip() or None
    if billing_addr:
        order.billing_address = billing_addr
    if shipping_addr:
        order.shipping_address = shipping_addr
    dispatch_addr = getattr(product, 'default_dispatch_address', None)
    if dispatch_addr:
        order.dispatch_address = {
            'label': dispatch_addr.label,
            'contact_name': dispatch_addr.contact_name,
            'address_line1': dispatch_addr.address_line1,
            'address_line2': dispatch_addr.address_line2,
            'city': dispatch_addr.city,
            'state': dispatch_addr.state,
            'country': dispatch_addr.country,
            'postcode': dispatch_addr.postcode,
            'phone': dispatch_addr.phone,
        }
    order.buyer_gst_number = buyer_gst
    order.buyer_company_name = buyer_company
    order.place_of_supply = place_supply
    order.save(update_fields=[
        'billing_address', 'shipping_address', 'dispatch_address',
        'buyer_gst_number', 'buyer_company_name', 'place_of_supply',
    ])


def _resolve_referral_user(referral_id):
    referral_id = (referral_id or '').strip()
    if not referral_id:
        return None

    user_qs = User.objects.filter(
        Q(referral_code__iexact=referral_id) |
        Q(mobile__iexact=referral_id)
    )

    # Also allow resolving by distributor code (TW...) for compatibility.
    distributor_owner_ids = DistributorID.objects.filter(distributor_code__iexact=referral_id).values_list('user_id', flat=True)
    if distributor_owner_ids:
        user_qs = user_qs | User.objects.filter(id__in=distributor_owner_ids)

    if referral_id.isdigit():
        user_qs = user_qs | User.objects.filter(id=referral_id)

    return user_qs.first()


def _recent_repeat_orders(user, product, selected_distributor):
    cutoff = timezone.now() - timedelta(hours=24)
    return (
        Order.objects.filter(
            user=user,
            product=product,
            status='COMPLETED',
            created_at__gte=cutoff,
        )
        .filter(
            Q(distributor=selected_distributor) |
            Q(distributor__sponsor_distributor=selected_distributor)
        )
        .order_by('-created_at')
    )


class PurchasePrecheckAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    throttle_classes = [PurchaseRateThrottle]

    def post(self, request):
        user = request.user
        product_id = request.data.get('product_id')
        distributor_type = request.data.get('distributor_type') or 'own'
        referral_id = (request.data.get('referral_id') or '').strip()
        selected_distributor_id = request.data.get('sponsor_distributor_id') or request.data.get('referrer_distributor_id')

        if not product_id:
            return error_response(
                message="Product ID is required.",
                code="PRODUCT_ID_REQUIRED",
                status_code=status.HTTP_400_BAD_REQUEST
            )

        try:
            product = Product.objects.select_related('opportunity_bundle').get(id=product_id, is_active=True)
        except Product.DoesNotExist:
            return error_response(
                message="Product not found",
                code="PRODUCT_NOT_FOUND",
                status_code=status.HTTP_404_NOT_FOUND
            )

        # Distributor selection is only required for products linked to an opportunity bundle
        is_bundle_product = bool(getattr(product, 'opportunity_bundle_id', None))

        if is_bundle_product and not selected_distributor_id:
            return error_response(
                message="Distributor ID is required.",
                code="DISTRIBUTOR_ID_REQUIRED",
                status_code=status.HTTP_400_BAD_REQUEST
            )

        selected_distributor = None
        if selected_distributor_id:
            try:
                selected_distributor = DistributorID.objects.select_related('product__opportunity_bundle', 'user').get(
                    id=selected_distributor_id,
                    is_active=True,
                )
            except DistributorID.DoesNotExist:
                return error_response(
                    message="Selected distributor ID not found.",
                    code="DISTRIBUTOR_NOT_FOUND",
                    status_code=status.HTTP_404_NOT_FOUND
                )

            if distributor_type == 'own':
                if selected_distributor.user_id != user.id:
                    return error_response(
                        message="Selected distributor ID must belong to you.",
                        code="INVALID_DISTRIBUTOR_SELECTION",
                        status_code=status.HTTP_400_BAD_REQUEST
                    )
            # distributor_type == 'referrer': any valid active distributor is allowed

            product_scheme_id = getattr(product, 'opportunity_bundle_id', None)
            selected_scheme_id = getattr(getattr(selected_distributor, 'product', None), 'opportunity_bundle_id', None)
            if product_scheme_id and selected_scheme_id != product_scheme_id:
                return error_response(
                    message="Selected distributor does not belong to the product opportunity bundle.",
                    code="SCHEME_MISMATCH",
                    status_code=status.HTTP_400_BAD_REQUEST
                )

        if selected_distributor:
            recent_orders = _recent_repeat_orders(user, product, selected_distributor)
            latest_recent = recent_orders.first()
            recent_count = recent_orders.count()
        else:
            latest_recent = None
            recent_count = 0

        show_warning = latest_recent is not None

        return Response(
            {
                'success': True,
                'show_warning': show_warning,
                'message': (
                    'You purchased the same product under the same distributor within the last 24 hours. '
                    'Do you want to continue?'
                    if show_warning
                    else ''
                ),
                'count_last_24h': recent_count,
                'last_order': (
                    {
                        'order_no': latest_recent.reference_id,
                        'created_at': latest_recent.created_at,
                        'status': latest_recent.status,
                    }
                    if latest_recent
                    else None
                ),
            },
            status=status.HTTP_200_OK,
        )

class PurchaseAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    throttle_classes = [PurchaseRateThrottle]
    def post(self, request):
        serializer = PurchaseRequestSerializer(data=request.data, context={'request': request})
        if not serializer.is_valid():
            return error_response(
                message="Validation failed",
                code="VALIDATION_ERROR",
                fields=serializer.errors,
                status_code=status.HTTP_400_BAD_REQUEST
            )
        # Input validation: product_id and client_reference_id must be present
        if 'product_id' not in serializer.validated_data or not serializer.validated_data['product_id']:
            return error_response(
                message="Product ID is required.",
                code="PRODUCT_ID_REQUIRED",
                status_code=status.HTTP_400_BAD_REQUEST
            )
        if 'client_reference_id' not in serializer.validated_data or not serializer.validated_data['client_reference_id']:
            return error_response(
                message="Client reference ID is required.",
                code="CLIENT_REFERENCE_ID_REQUIRED",
                status_code=status.HTTP_400_BAD_REQUEST
            )
        user = request.user
        # Ownership validation: ensure user is authenticated and not purchasing for another user
        if not user or not user.is_authenticated:
            return error_response(
                message="Authentication required.",
                code="AUTH_REQUIRED",
                status_code=status.HTTP_401_UNAUTHORIZED
            )
        if 'user' in request.data and str(request.data['user']) != str(user.id):
            return error_response(
                message="You can only purchase for your own account.",
                code="OWNERSHIP_ERROR",
                status_code=status.HTTP_403_FORBIDDEN
            )
        try:
            product = Product.objects.get(id=serializer.validated_data['product_id'])
        except Product.DoesNotExist:
            return error_response(
                message="Product not found",
                code="PRODUCT_NOT_FOUND",
                status_code=status.HTTP_404_NOT_FOUND
            )
        sponsor_distributor = serializer.validated_data.get('selected_sponsor_distributor')
        if sponsor_distributor is None:
            sponsor_id = serializer.validated_data.get('sponsor_distributor_id')
            if sponsor_id:
                try:
                    sponsor_distributor = DistributorID.objects.get(id=sponsor_id)
                except DistributorID.DoesNotExist:
                    return error_response(
                        message="Sponsor distributor not found",
                        code="SPONSOR_NOT_FOUND",
                        status_code=status.HTTP_404_NOT_FOUND
                    )
        client_reference_id = serializer.validated_data.get('client_reference_id')
        use_voucher = serializer.validated_data.get('use_voucher', False)
        payment_method = serializer.validated_data.get('payment_method', 'main_wallet')
        payment_mode = (serializer.validated_data.get('payment_mode') or 'FULL').upper()
        use_super_coins = serializer.validated_data.get('use_super_coins', False)
        super_coin_amount = serializer.validated_data.get('super_coin_amount')
        qr_reference = serializer.validated_data.get('qr_reference', '')
        distributor_type = serializer.validated_data.get('distributor_type')
        repeat_purchase_confirmed = bool(serializer.validated_data.get('repeat_purchase_confirmed', False))

        # Backend enforcement for repeat same-product + same-distributor consent.
        if sponsor_distributor is not None:
            recent_orders = _recent_repeat_orders(user, product, sponsor_distributor)
            if recent_orders.exists() and not repeat_purchase_confirmed:
                latest_recent = recent_orders.first()
                return error_response(
                    message=(
                        'You purchased the same product under the same distributor within the last 24 hours. '
                        'Please confirm to continue.'
                    ),
                    code='REPEAT_PURCHASE_CONFIRMATION_REQUIRED',
                    fields={
                        'repeat_purchase_confirmed': 'Required for this repeat purchase.',
                        'count_last_24h': recent_orders.count(),
                        'last_order_no': getattr(latest_recent, 'reference_id', None),
                    },
                    status_code=status.HTTP_400_BAD_REQUEST,
                )
        
        # Validate transaction reference only for methods that need one.
        if payment_method in REFERENCE_REQUIRED_PAYMENT_METHODS and not qr_reference:
            return error_response(
                message="Transaction reference is required for this payment method.",
                code="TRANSACTION_REFERENCE_REQUIRED",
                status_code=status.HTTP_400_BAD_REQUEST
            )
        
        part_cfg = None
        purchase_amount = product.base_cost
        if payment_mode == 'PART':
            if use_voucher:
                return error_response(
                    message='TWM coins/voucher usage is not allowed for part payment.',
                    code='PART_PAYMENT_INVALID_PAYMENT_METHOD',
                    status_code=status.HTTP_400_BAD_REQUEST,
                )
            if payment_method == 'repurchase_wallet':
                return error_response(
                    message='Repurchase wallet is not allowed for part payment.',
                    code='PART_PAYMENT_INVALID_PAYMENT_METHOD',
                    status_code=status.HTTP_400_BAD_REQUEST,
                )
            try:
                part_cfg = calculate_part_payment_breakdown(product, product.base_cost)
            except Exception as e:
                return error_response(
                    message=str(e),
                    code="PART_PAYMENT_INVALID",
                    status_code=status.HTTP_400_BAD_REQUEST,
                )
            purchase_amount = part_cfg['initial_amount']

        # ── Manual payment methods: create PAYMENT_REVIEW order, skip distributions
        if payment_method in MANUAL_PAYMENT_METHODS:
            try:
                order = create_pending_payment_order(
                    user=user,
                    product=product,
                    sponsor_distributor=sponsor_distributor,
                    client_reference_id=client_reference_id,
                    payment_method=payment_method,
                    qr_reference=qr_reference,
                    amount=purchase_amount,
                    payment_mode=payment_mode,
                    part_payment_config=part_cfg,
                )
                _apply_order_context(order, request, product)
            except Exception as e:
                logging.error(f"Pending payment order creation failed: {e}", exc_info=True)
                return error_response(
                    message=str(e),
                    code="PURCHASE_FAILED",
                    status_code=status.HTTP_400_BAD_REQUEST,
                )
            return Response({
                "success": True,
                "data": {
                    'order_id': str(order.id),
                    'reference_id': order.reference_id,
                    'status': order.status,
                    'payment_method': payment_method,
                    'qr_reference': qr_reference,
                    'amount': str(order.amount),
                    'payment_mode': payment_mode,
                    'part_total_amount': str(order.part_total_amount) if order.part_total_amount else None,
                    'part_paid_amount': str(order.part_paid_amount),
                    'part_pending_amount': str(order.part_pending_amount),
                    'part_payment_due_at': order.part_payment_due_at.isoformat() if order.part_payment_due_at else None,
                    'pending_approval': True,
                    'message': (
                        'Cash payment confirmation pending. Your order will be activated once admin approves payment.'
                        if payment_method == 'cash_payment'
                        else 'Your payment reference has been submitted. Your order will be activated once payment is verified by admin.'
                    ),
                },
            }, status=status.HTTP_201_CREATED)

        # ── Wallet / instant payment methods: execute immediately
        try:
            order, distributor, distribution_result, reused_distributor = execute_purchase(
                user,
                product,
                sponsor_distributor,
                client_reference_id,
                amount=purchase_amount,
                payment_method=payment_method,
                use_voucher=use_voucher,
                use_super_coins=use_super_coins,
                super_coin_amount=super_coin_amount,
            )

            if payment_mode == 'PART' and part_cfg is not None:
                now = timezone.now()
                order.payment_mode = 'PART'
                order.status = 'PARTIAL_PAID'
                order.ownership_status = 'PARTIAL_OWNED'
                order.is_availability_blocked = True
                order.part_payment_percentage = part_cfg['part_percentage']
                order.part_total_amount = part_cfg['total_amount']
                order.part_paid_amount = part_cfg['initial_amount']
                order.part_pending_amount = part_cfg['pending_amount']
                order.part_payment_started_at = now
                order.part_payment_due_at = now + timedelta(days=part_cfg['cooling_days'])
                order.part_payment_conversion_product_id = part_cfg['conversion_product_id']
                order.save(update_fields=[
                    'payment_mode',
                    'status',
                    'ownership_status',
                    'is_availability_blocked',
                    'part_payment_percentage',
                    'part_total_amount',
                    'part_paid_amount',
                    'part_pending_amount',
                    'part_payment_started_at',
                    'part_payment_due_at',
                    'part_payment_conversion_product',
                    'updated_at',
                ])
                _record_part_payment_log(
                    order,
                    'PART_PAYMENT_ACTIVATED',
                    actor=user,
                    metadata={
                        'initial_amount': str(order.part_paid_amount),
                        'pending_amount': str(order.part_pending_amount),
                        'payment_method': payment_method,
                    },
                )

            _apply_order_context(order, request, product)
            assignment = resolve_assignment_for_user(user)
            if assignment:
                order.franchise_assignment = assignment
                order.delivery_store = assignment.store
                order.save(update_fields=['franchise_assignment', 'delivery_store'])
            if assignment and assignment.store:
                create_company_to_store_transfer(order, assignment=assignment)
        except Exception as e:
            logging.error(f"Purchase failed: {e}", exc_info=True)
            return error_response(
                message=str(e),
                code="PURCHASE_FAILED",
                status_code=status.HTTP_400_BAD_REQUEST
            )
        logging.info(f"purchase_completed: order_id={order.id} distributor_id={getattr(distributor, 'id', None)} user_id={user.id}")
        emit_event(
            "purchase_completed",
            {
                "user_id": user.id,
                "distributor_id": getattr(distributor, 'id', None),
                "amount": str(order.amount),
                "voucher_amount": str(getattr(order, 'voucher_amount', 0)),
                "cash_amount": str(order.cash_amount),
                "reference_id": order.reference_id,
                "payment_method": payment_method,
            }
        )
        response = {
            'order_id': str(order.id),
            'reference_id': getattr(order, 'reference_id', None),
            'status': order.status,
            'distributor_id': getattr(distributor, 'id', None),
            'distributor_code': getattr(distributor, 'distributor_code', None),
            'global_position': getattr(distributor, 'global_position', None),
            'amount': str(order.amount),
            'voucher_amount': str(getattr(order, 'voucher_amount', 0)),
            'super_coin_amount': str(getattr(order, 'super_coin_amount', 0)),
            'cash_amount': str(order.cash_amount),
            'payment_mode': order.payment_mode,
            'ownership_status': order.ownership_status,
            'part_total_amount': str(order.part_total_amount) if order.part_total_amount else None,
            'part_paid_amount': str(order.part_paid_amount),
            'part_pending_amount': str(order.part_pending_amount),
            'part_payment_due_at': order.part_payment_due_at.isoformat() if order.part_payment_due_at else None,
            'distributor_type': distributor_type,
            'opportunity_bundle_name': getattr(getattr(product, 'opportunity_bundle', None), 'name', None),
            'distribution_split': [
                {
                    'type': item.get('type'),
                    'value_type': item.get('value_type'),
                    'amount': str(item.get('amount')),
                }
                for item in (distribution_result or {}).get('breakdown', [])
            ],
            'distributor_reused': reused_distributor,
            'franchise_assignment_id': str(order.franchise_assignment_id) if order.franchise_assignment_id else None,
            'delivery_store_id': str(order.delivery_store_id) if order.delivery_store_id else None,
            'delivery_store_name': getattr(order.delivery_store, 'name', None),
        }
        return Response({"success": True, "data": response}, status=status.HTTP_201_CREATED)


class CompletePartPaymentAPIView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    throttle_classes = [PurchaseRateThrottle]

    def post(self, request, order_id):
        order = Order.objects.select_related('product', 'user').filter(id=order_id, user=request.user).first()
        if not order:
            return error_response(
                message='Order not found',
                code='NOT_FOUND',
                status_code=status.HTTP_404_NOT_FOUND,
            )

        if order.payment_mode != 'PART':
            return error_response(
                message='This order is not a part-payment order.',
                code='INVALID_STATE',
                status_code=status.HTTP_400_BAD_REQUEST,
            )

        if order.part_pending_amount <= 0:
            return error_response(
                message='No pending amount remains for this order.',
                code='INVALID_STATE',
                status_code=status.HTTP_400_BAD_REQUEST,
            )

        payment_method = (request.data.get('payment_method') or 'main_wallet').strip()
        use_super_coins = bool(request.data.get('use_super_coins', False))
        super_coin_amount = request.data.get('super_coin_amount')

        if payment_method not in {'main_wallet', 'super_coins'}:
            return error_response(
                message='Only main wallet or super coins are supported for part-payment completion.',
                code='VALIDATION_ERROR',
                status_code=status.HTTP_400_BAD_REQUEST,
            )

        try:
            order, distribution_result = settle_part_payment_order(
                order,
                payment_method=payment_method,
                use_super_coins=use_super_coins,
                super_coin_amount=super_coin_amount,
                actor=request.user,
            )
        except Exception as e:
            return error_response(
                message=str(e),
                code='PART_PAYMENT_SETTLEMENT_FAILED',
                status_code=status.HTTP_400_BAD_REQUEST,
            )

        return Response({
            'success': True,
            'data': {
                'order_id': str(order.id),
                'reference_id': order.reference_id,
                'status': order.status,
                'ownership_status': order.ownership_status,
                'part_total_amount': str(order.part_total_amount) if order.part_total_amount else None,
                'part_paid_amount': str(order.part_paid_amount),
                'part_pending_amount': str(order.part_pending_amount),
                'distribution_split': [
                    {
                        'type': item.get('type'),
                        'value_type': item.get('value_type'),
                        'amount': str(item.get('amount')),
                    }
                    for item in (distribution_result or {}).get('breakdown', [])
                ],
            },
        }, status=status.HTTP_200_OK)
