from django.db import transaction
from decimal import Decimal
from datetime import datetime, timedelta
from uuid import uuid4
from django.utils import timezone
from apps.business.distributor.services.distributor_service import create_distributor_id, process_direct_incentive
from apps.business.schemes.services.distribution_service import calculate_distribution, resolve_distribution_base_amount
from apps.business.pool.services.pool_service import create_pool_entry
from apps.business.rewards.services.level_service import process_level_rewards
from apps.business.power_stream.services.power_stream_service import process_power_stream_bonus
from apps.business.power_stream.tasks import process_power_stream_bonus_task
from apps.business.rewards.tasks import process_level_rewards_task
from apps.business.distributor.tasks import process_auto_id_generation_task
from apps.business.products.models import Product
from apps.business.distributor.models import DistributorID
from apps.business.binary_tree.services.binary_service import get_binary_ancestor_ids
from apps.business.wallet.models import Wallet
from apps.business.wallet.services.wallet_service import WalletService



from ..models import Order, OrderPartPaymentLog
import logging


def is_digital_product(product):
    """Digital products are auto-fulfilled (no physical dispatch required)."""
    if not product:
        return False
    return bool(
        getattr(product, 'digital_product', False)
        or getattr(product, 'category', None) == Product.Category.VOUCHER
        or getattr(product, 'product_type', None) in {
            Product.ProductType.PREMIUM_VOUCHER,
            Product.ProductType.TWM_COINS,
        }
    )


def _enqueue_task_safe(task, *args):
    """Best-effort async dispatch: do not fail purchase flow if broker is down."""
    try:
        task.delay(*args)
    except Exception as exc:
        logging.warning(
            "Async task dispatch failed for %s args=%s error=%s",
            getattr(task, 'name', str(task)),
            args,
            exc,
        )


def _generate_order_reference_id():
    """Build order number as ORD-YYMMDDHHMM-XXXXXXXX (uppercase)."""
    return f"ORD-{datetime.utcnow():%y%m%d%H%M}-{uuid4().hex[:8].upper()}"


def _get_reward_recalc_target_ids(distributor):
    return get_binary_ancestor_ids(distributor)


def _quantize_amount(value):
    return Decimal(str(value or 0)).quantize(Decimal('0.01'))


def calculate_part_payment_breakdown(product, total_amount):
    total = _quantize_amount(total_amount)
    # Determine initial payment amount. Priority:
    # 1) explicit `part_payment_initial_amount` on product
    # 2) conversion product's base_cost (if configured)
    # 3) fallback to percentage-based configuration
    explicit_initial = getattr(product, 'part_payment_initial_amount', None)
    conversion_product = getattr(product, 'part_payment_conversion_product', None)

    if explicit_initial is not None:
        initial = _quantize_amount(explicit_initial)
        # derive percentage for reporting
        try:
            part_pct = int((initial / total * Decimal('100')).quantize(Decimal('1')))
        except Exception:
            part_pct = int(getattr(product, 'part_payment_percentage', 50) or 50)
    elif conversion_product is not None and getattr(conversion_product, 'base_cost', None) is not None:
        initial = _quantize_amount(getattr(conversion_product, 'base_cost'))
        try:
            part_pct = int((initial / total * Decimal('100')).quantize(Decimal('1')))
        except Exception:
            part_pct = int(getattr(product, 'part_payment_percentage', 50) or 50)
    else:
        part_pct = int(getattr(product, 'part_payment_percentage', 50) or 50)
        part_pct = max(1, min(99, part_pct))
        initial = _quantize_amount((total * Decimal(part_pct)) / Decimal('100'))
    if initial <= Decimal('0'):
        raise Exception('Part payment amount resolved to zero.')
    pending = _quantize_amount(total - initial)
    if pending <= Decimal('0'):
        raise Exception('Part payment requires a remaining pending amount.')
    return {
        'total_amount': total,
        'part_percentage': part_pct,
        'initial_amount': initial,
        'pending_amount': pending,
        'cooling_days': int(getattr(product, 'part_payment_cooling_days', 180) or 180),
        'conversion_product_id': (getattr(conversion_product, 'id', None) if conversion_product is not None else getattr(product, 'part_payment_conversion_product_id', None)),
    }


def _record_part_payment_log(order, event, actor=None, notes='', metadata=None):
    OrderPartPaymentLog.objects.create(
        order=order,
        event=event,
        actor=actor,
        notes=notes or '',
        metadata=metadata or {},
    )


def _apply_distribution_for_existing_distributor(order, amount):
    product = order.product
    distributor = order.distributor
    if not distributor:
        raise Exception('Distributor context is missing for this order.')

    distribution_result = None
    if getattr(product, 'opportunity_bundle_id', None):
        distribution_base_amount = resolve_distribution_base_amount(product, amount)
        distribution_result = calculate_distribution(product, distribution_base_amount)
        process_direct_incentive(distributor, distribution_result, reference_id=order.reference_id)
        create_pool_entry(distributor, distribution_result)
        _enqueue_task_safe(process_power_stream_bonus_task, distributor.id)
        for reward_target_id in _get_reward_recalc_target_ids(distributor):
            _enqueue_task_safe(process_level_rewards_task, reward_target_id)
        _enqueue_task_safe(process_auto_id_generation_task, distributor.id)

    return distribution_result


def settle_part_payment_order(
    order,
    *,
    payment_method='main_wallet',
    use_super_coins=False,
    super_coin_amount=None,
    actor=None,
):
    if order.payment_mode != 'PART':
        raise Exception('Order is not a part-payment order.')
    if order.part_pending_amount <= Decimal('0'):
        raise Exception('No pending amount remains for this order.')
    if order.part_payment_due_at and timezone.now() > order.part_payment_due_at:
        raise Exception('Cooling period elapsed. This part payment can no longer be settled.')
    if payment_method == 'repurchase_wallet':
        raise Exception('Repurchase wallet is not allowed for part payment settlement.')

    with transaction.atomic():
        pending_amount = _quantize_amount(order.part_pending_amount)
        super_coin_used = Decimal('0')
        cash_amount = pending_amount

        if use_super_coins or payment_method == 'super_coins' or super_coin_amount:
            from apps.business.wallet.models import SuperCoinWallet

            super_wallet, _ = SuperCoinWallet.objects.select_for_update().get_or_create(user=order.user)
            requested_super_coin = (
                Decimal(str(super_coin_amount))
                if super_coin_amount is not None
                else cash_amount
            )
            if requested_super_coin < Decimal('0'):
                raise Exception('Super coin amount cannot be negative.')

            super_coin_used = min(cash_amount, super_wallet.balance, requested_super_coin)
            cash_amount = cash_amount - super_coin_used

            if payment_method == 'super_coins' and cash_amount > Decimal('0'):
                raise Exception('Insufficient Super Coins for full settlement.')

        if super_coin_used > Decimal('0'):
            WalletService.debit_supercoins(
                user=order.user,
                amount=super_coin_used,
                source_type='PURCHASE',
                reference_id=f"{order.reference_id}-SC-SETTLE",
                metadata={'order_id': str(order.id)},
            )

        if payment_method == 'main_wallet' and cash_amount > Decimal('0'):
            WalletService.debit_main_wallet(
                user=order.user,
                amount=cash_amount,
                source_type='PURCHASE',
                reference_id=f"{order.reference_id}-SETTLE",
            )
        elif payment_method == 'super_coins' and cash_amount > Decimal('0'):
            raise Exception('Super Coins payment selected but residual cash amount exists.')

        distribution_result = _apply_distribution_for_existing_distributor(order, pending_amount)

        order.part_paid_amount = _quantize_amount(order.part_paid_amount + pending_amount)
        order.part_pending_amount = Decimal('0.00')
        order.part_payment_completed_at = timezone.now()
        order.is_availability_blocked = False
        order.ownership_status = 'FULL_OWNED'
        order.status = 'COMPLETED'
        order.super_coin_amount = _quantize_amount(order.super_coin_amount + super_coin_used)
        order.cash_amount = _quantize_amount(order.cash_amount + cash_amount)
        order.save(update_fields=[
            'part_paid_amount',
            'part_pending_amount',
            'part_payment_completed_at',
            'is_availability_blocked',
            'ownership_status',
            'status',
            'super_coin_amount',
            'cash_amount',
            'updated_at',
        ])

        _record_part_payment_log(
            order,
            'FULL_PAYMENT_SETTLED',
            actor=actor,
            metadata={
                'settled_amount': str(pending_amount),
                'payment_method': payment_method,
                'super_coin_amount': str(super_coin_used),
                'cash_amount': str(cash_amount),
            },
        )

        return order, distribution_result

def execute_purchase(
    user,
    product,
    sponsor_distributor,
    client_reference_id,
    amount=None,
    payment_method="main_wallet",
    use_voucher=False,
    use_super_coins=False,
    super_coin_amount=None,
):
    """
    Orchestrates purchase: creates order, ensures idempotency, creates distributor, runs all engines in order.
    Returns (order, distributor, distribution_result, reused_distributor)
    """
    with transaction.atomic():
        purchase_amount = Decimal(str(amount or product.base_cost))
        if purchase_amount <= Decimal("0"):
            raise Exception("Purchase amount must be greater than zero.")

        voucher_amount = Decimal("0")
        super_coin_used = Decimal("0")
        cash_amount = purchase_amount

        if use_voucher:
            if product.category == Product.Category.VOUCHER:
                raise Exception("Vouchers cannot be used to purchase voucher products.")

            wallet, _ = Wallet.objects.select_for_update().get_or_create(user=user)
            max_voucher_allowed = purchase_amount * Decimal("0.50")
            voucher_amount = min(max_voucher_allowed, wallet.voucher_balance)
            if voucher_amount <= Decimal("0"):
                raise Exception("No voucher balance available for this purchase.")
            cash_amount = purchase_amount - voucher_amount

            # Wallet methods not allowed for the remaining cash when voucher is used,
            # but skip the restriction when coins cover everything (cash_amount == 0).
            if cash_amount > Decimal("0") and payment_method in ["main_wallet", "repurchase_wallet"]:
                raise Exception("Voucher purchases require real-money payment for the remaining amount.")

        if use_super_coins or payment_method == "super_coins" or super_coin_amount:
            from apps.business.wallet.models import SuperCoinWallet

            super_wallet, _ = SuperCoinWallet.objects.select_for_update().get_or_create(user=user)
            remaining_after_voucher = cash_amount
            requested_super_coin = (
                Decimal(str(super_coin_amount))
                if super_coin_amount is not None
                else remaining_after_voucher
            )
            if requested_super_coin < Decimal("0"):
                raise Exception("Super coin amount cannot be negative.")

            super_coin_used = min(remaining_after_voucher, super_wallet.balance, requested_super_coin)
            cash_amount = remaining_after_voucher - super_coin_used

            if payment_method == "super_coins" and cash_amount > Decimal("0"):
                raise Exception("Insufficient Super Coins for full payment. Use another payment method for balance amount.")

        # Idempotency: check for existing order
        order = Order.objects.filter(user=user, product=product, client_reference_id=client_reference_id).first()
        if order:
            logging.info(f"Idempotent purchase: returning existing order {order.reference_id}")
            if getattr(product, 'opportunity_bundle_id', None):
                base_amount = resolve_distribution_base_amount(product, Decimal(str(order.amount)))
                distribution_result = calculate_distribution(product, base_amount)
            else:
                distribution_result = None
            return order, order.distributor, distribution_result, True

        # Create order (pending)
        reference_id = _generate_order_reference_id()
        while Order.objects.filter(reference_id=reference_id).exists():
            reference_id = _generate_order_reference_id()

        order = Order.objects.create(
            user=user,
            product=product,
            amount=purchase_amount,
            voucher_amount=voucher_amount,
            super_coin_amount=super_coin_used,
            cash_amount=cash_amount,
            status="PENDING",
            reference_id=reference_id,
            client_reference_id=client_reference_id
        )

        if super_coin_used > Decimal("0"):
            WalletService.debit_supercoins(
                user=user,
                amount=super_coin_used,
                source_type="PURCHASE",
                reference_id=f"{order.reference_id}-SC",
                metadata={"product_id": str(product.id)},
            )

        if payment_method == "main_wallet" and cash_amount > Decimal("0"):
            WalletService.debit_main_wallet(
                user=user,
                amount=cash_amount,
                source_type="PURCHASE",
                reference_id=order.reference_id,
            )
        elif payment_method == "super_coins" and cash_amount > Decimal("0"):
            raise Exception("Super Coins payment selected but residual cash amount exists.")
        elif payment_method == "repurchase_wallet" and cash_amount > Decimal("0"):
            WalletService.debit_repurchase_wallet(
                user=user,
                amount=cash_amount,
                source_type="PURCHASE",
                reference_id=order.reference_id,
            )

        if voucher_amount > Decimal("0"):
            WalletService.debit_voucher_wallet(
                user=user,
                amount=voucher_amount,
                source_type="VOUCHER_REDEMPTION",
                reference_id=order.reference_id,
            )

        # Always create a fresh distributor ID per successful purchase.
        reused_distributor = False
        distributor = create_distributor_id(user, product, sponsor_distributor)

        order.distributor = distributor
        # 2. Calculate & run distribution (only for bundle-linked products)
        if getattr(product, 'opportunity_bundle_id', None):
            distribution_base_amount = resolve_distribution_base_amount(product, purchase_amount)
            distribution_result = calculate_distribution(product, distribution_base_amount)
            # 3. Direct incentive
            process_direct_incentive(distributor, distribution_result, reference_id=order.reference_id)
            # 4. Pool entry
            create_pool_entry(distributor, distribution_result)
            # 5. Power stream
            _enqueue_task_safe(process_power_stream_bonus_task, distributor.id)
            # 6. Level rewards for the affected sponsor chain.
            for reward_target_id in _get_reward_recalc_target_ids(distributor):
                _enqueue_task_safe(process_level_rewards_task, reward_target_id)
            # 7. Auto ID (async, if needed)
            _enqueue_task_safe(process_auto_id_generation_task, distributor.id)
        else:
            distribution_result = None

        if product.category == Product.Category.VOUCHER:
            if product.product_type == Product.ProductType.TWM_COINS and product.twm_coin_amount:
                # TWM Coins product: credit the configured coin amount with optional expiry
                from datetime import date, timedelta
                expires_at = None
                if product.twm_coin_expiry_days and product.twm_coin_expiry_days > 0:
                    expires_at = date.today() + timedelta(days=product.twm_coin_expiry_days)
                WalletService.credit_voucher_wallet(
                    user=user,
                    amount=Decimal(str(product.twm_coin_amount)),
                    source_type="TWM_COINS_PURCHASE",
                    reference_id=f"{order.reference_id}-VCH",
                    expires_at=expires_at,
                )
            elif cash_amount > Decimal("0"):
                # Standard VOUCHER: credit the cash paid back as voucher balance
                WalletService.credit_voucher_wallet(
                    user=user,
                    amount=cash_amount,
                    source_type="VOUCHER_PURCHASE",
                    reference_id=f"{order.reference_id}-VCH",
                )

        order.status = "COMPLETED"
        if is_digital_product(product):
            order.franchise_fulfillment_status = 'COLLECTED'
            order.save(update_fields=["status", "distributor", 'franchise_fulfillment_status'])
        else:
            order.save(update_fields=["status", "distributor"])
        logging.info(f"Order completed: {order.reference_id}")
        return order, distributor, distribution_result, reused_distributor


# ── Manual payment (QR / Bank / PhonePe) pending-approval flow ───────────────

MANUAL_PAYMENT_METHODS = {'qr_scan', 'bank_transfer', 'phonepe', 'cash_payment'}
REFERENCE_REQUIRED_PAYMENT_METHODS = {'qr_scan', 'bank_transfer', 'phonepe'}


def create_pending_payment_order(
    user,
    product,
    sponsor_distributor,
    client_reference_id,
    payment_method,
    qr_reference,
    amount=None,
    payment_mode='FULL',
    part_payment_config=None,
):
    """
    Creates an Order with status=PAYMENT_REVIEW.
    No wallet debit, no distributor, no distributions — waits for admin approval.
    """
    with transaction.atomic():
        purchase_amount = Decimal(str(amount or product.base_cost))
        # Idempotency: if an identical PAYMENT_REVIEW order exists, return it
        existing = Order.objects.filter(
            user=user,
            product=product,
            client_reference_id=client_reference_id,
            status='PAYMENT_REVIEW',
        ).first()
        if existing:
            return existing

        order = Order.objects.create(
            user=user,
            product=product,
            amount=purchase_amount,
            cash_amount=purchase_amount,
            status='PAYMENT_REVIEW',
            reference_id=_generate_order_reference_id(),
            client_reference_id=client_reference_id,
            distributor=sponsor_distributor,   # temporarily store as "sponsor" context
            payment_method=payment_method,
            qr_reference=qr_reference,
            payment_mode=payment_mode,
        )

        if payment_mode == 'PART':
            cfg = part_payment_config or {}
            now = timezone.now()
            cooling_days = int(cfg.get('cooling_days') or getattr(product, 'part_payment_cooling_days', 180) or 180)
            order.ownership_status = 'PARTIAL_OWNED'
            order.is_availability_blocked = True
            order.part_payment_percentage = int(cfg.get('part_percentage') or getattr(product, 'part_payment_percentage', 50) or 50)
            order.part_total_amount = _quantize_amount(cfg.get('total_amount') or product.base_cost)
            order.part_paid_amount = _quantize_amount(cfg.get('initial_amount') or purchase_amount)
            order.part_pending_amount = _quantize_amount(cfg.get('pending_amount') or (order.part_total_amount - order.part_paid_amount))
            order.part_payment_started_at = now
            order.part_payment_due_at = now + timedelta(days=cooling_days)
            conversion_product_id = cfg.get('conversion_product_id') or getattr(product, 'part_payment_conversion_product_id', None)
            order.part_payment_conversion_product_id = conversion_product_id
            order.save(update_fields=[
                '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,
                'PAYMENT_REVIEW_SUBMITTED',
                metadata={
                    'initial_amount': str(order.part_paid_amount),
                    'pending_amount': str(order.part_pending_amount),
                    'cooling_due_at': order.part_payment_due_at.isoformat() if order.part_payment_due_at else None,
                },
            )
        logging.info(f"Pending payment order created: {order.reference_id} method={payment_method}")
        return order


def fulfill_pending_payment_order(order, reviewed_by):
    """
    Called by admin to APPROVE a PAYMENT_REVIEW order.
    Retail orders are activated directly; bundle orders still run the distributor pipeline.
    """
    if order.status != 'PAYMENT_REVIEW':
        raise Exception(f"Order is not in PAYMENT_REVIEW state (current: {order.status})")

    user = order.user
    product = order.product
    sponsor_distributor = order.distributor

    with transaction.atomic():
        from django.utils import timezone
        from datetime import timedelta

        if getattr(order, 'order_type', None) == 'RETAIL':
            from apps.business.marketplace.models_part2 import OrderShipment, OrderStatusHistory

            order.status = 'COMPLETED'
            if is_digital_product(product):
                order.franchise_fulfillment_status = 'COLLECTED'
            order.reviewed_by = reviewed_by
            order.reviewed_at = timezone.now()
            order.save(update_fields=['status', 'franchise_fulfillment_status', 'reviewed_by', 'reviewed_at'])

            if hasattr(order, 'items'):
                order.items.update(item_status='CONFIRMED')

            OrderShipment.objects.get_or_create(
                order=order,
                defaults={
                    'shipping_method': 'STANDARD',
                    'estimated_delivery': (timezone.now() + timedelta(days=4)).date(),
                    'status': 'PENDING',
                },
            )
            OrderStatusHistory.objects.create(
                order=order,
                status='COMPLETED',
                description='Payment approved by admin.',
                recorded_by='ADMIN',
            )
            logging.info(f"Retail payment approved: {order.reference_id} by {reviewed_by}")
            return order, None, None

        # Re-run the full purchase pipeline without wallet debit (already pending)
        purchase_amount = order.amount
        distribution_result = None

        distributor = create_distributor_id(user, product, sponsor_distributor)
        order.distributor = distributor

        if getattr(product, 'opportunity_bundle_id', None):
            distribution_base_amount = resolve_distribution_base_amount(product, purchase_amount)
            distribution_result = calculate_distribution(product, distribution_base_amount)
            process_direct_incentive(distributor, distribution_result, reference_id=order.reference_id)
            create_pool_entry(distributor, distribution_result)
            _enqueue_task_safe(process_power_stream_bonus_task, distributor.id)
            for reward_target_id in _get_reward_recalc_target_ids(distributor):
                _enqueue_task_safe(process_level_rewards_task, reward_target_id)
            _enqueue_task_safe(process_auto_id_generation_task, distributor.id)

        if order.payment_mode == 'PART':
            now = timezone.now()
            if not order.part_total_amount:
                order.part_total_amount = _quantize_amount(product.base_cost)
            if order.part_paid_amount <= Decimal('0'):
                order.part_paid_amount = _quantize_amount(purchase_amount)
            if order.part_pending_amount <= Decimal('0'):
                order.part_pending_amount = _quantize_amount(order.part_total_amount - order.part_paid_amount)
            if not order.part_payment_started_at:
                order.part_payment_started_at = now
            if not order.part_payment_due_at:
                cooling_days = int(getattr(product, 'part_payment_cooling_days', 180) or 180)
                order.part_payment_due_at = now + timedelta(days=cooling_days)
            if not order.part_payment_conversion_product_id:
                order.part_payment_conversion_product_id = getattr(product, 'part_payment_conversion_product_id', None)

            order.status = 'PARTIAL_PAID'
            order.ownership_status = 'PARTIAL_OWNED'
            order.is_availability_blocked = True
            _record_part_payment_log(
                order,
                'PART_PAYMENT_ACTIVATED',
                actor=reviewed_by,
                metadata={
                    'initial_amount': str(order.part_paid_amount),
                    'pending_amount': str(order.part_pending_amount),
                },
            )
        else:
            order.status = 'COMPLETED'
            if is_digital_product(product):
                order.franchise_fulfillment_status = 'COLLECTED'

        order.reviewed_by = reviewed_by
        order.reviewed_at = timezone.now()
        order.save(update_fields=[
            'status',
            'franchise_fulfillment_status',
            'distributor',
            'reviewed_by',
            'reviewed_at',
            'part_total_amount',
            'part_paid_amount',
            'part_pending_amount',
            'part_payment_started_at',
            'part_payment_due_at',
            'part_payment_conversion_product',
            'ownership_status',
            'is_availability_blocked',
            'updated_at',
        ])
        logging.info(f"Pending payment approved: {order.reference_id} by {reviewed_by}")
        return order, distributor, distribution_result

