"""
Sponsor Income / Direct Incentive summary service.

Business logic:
  - A user earns Direct Incentive when a new DistributorID is created under one of
    their DistributorIDs (i.e. the new ID's sponsor_distributor points to one of theirs).
  - WalletLedger entries are written with source_type='DIRECT_INCENTIVE' and
    reference_id='DIRECT_INCENTIVE_{trigger_distributor_id}'.
  - First 2 positions under a distributor ID make the owner withdrawal-eligible.
  - From the 3rd position onward, Power Stream activates for that distributor ID.
"""

import logging
from decimal import Decimal
import uuid

from django.db.models import Count, Q, Sum
from django.utils import timezone

from apps.business.distributor.models import DistributorID
from apps.business.schemes.services.distribution_service import calculate_distribution, resolve_distribution_base_amount
from apps.business.orders.models import Order
from apps.business.wallet.models import WalletLedger
from apps.business.wallet.models import UserVoucherCode
from apps.business.schema_compat import defer_missing_product_part_payment_fields
from apps.business.withdrawals.models import WithdrawalRequest
from apps.business.distributor.services.hybrid_service import (
    get_current_owner_period,
    refresh_qualification_for_distributor,
)

logger = logging.getLogger(__name__)


SQLITE_SAFE_IN_CHUNK = 500


def _user_display_name(user):
    name = f"{user.first_name} {user.last_name}".strip()
    return name or user.email or user.mobile or str(user.id)


def _chunked(values, size=SQLITE_SAFE_IN_CHUNK):
    for i in range(0, len(values), size):
        yield values[i:i + size]


def _extract_trigger_uuid_from_reference(reference_id):
    """
    Safely extract the trigger DistributorID UUID from direct-incentive reference IDs.

    Handles historical/mixed formats like:
      - DIRECT_INCENTIVE_<uuid>
      - DIRECT_INCENTIVE_<orderRef>_<uuid>
    Returns a canonical UUID string or None.
    """
    if not reference_id or not reference_id.startswith('DIRECT_INCENTIVE_'):
        return None

    suffix = reference_id[len('DIRECT_INCENTIVE_'):]
    if not suffix:
        return None

    parts = suffix.split('_')
    # Prefer right-most token first to support formats ending with UUID.
    for token in reversed(parts):
        try:
            return str(uuid.UUID(token))
        except (ValueError, TypeError, AttributeError):
            continue

    return None


def _get_direct_incentive_breakdown(trigger_dist, credited_main_entry, repurchase_entry):
    """
    Build direct incentive breakdown aligned with distribution logic.

    Gross = DIRECT_INCENTIVE component from opportunity_bundle distribution for trigger product base_cost.
    Total deduction = 25% of gross.
    Net incentive = gross - deduction.
    """
    zero_breakdown = {
        'gross_amount': Decimal('0.00'),
        'tds_amount': Decimal('0.00'),
        'repurchase_amount': Decimal('0.00'),
        'welfare_amount': Decimal('0.00'),
        'technology_amount': Decimal('0.00'),
        'operations_amount': Decimal('0.00'),
        'total_deduction': Decimal('0.00'),
        'net_incentive': Decimal('0.00'),
        'credited_main_wallet': Decimal('0.00'),
        'credited_repurchase_wallet': Decimal('0.00'),
    }

    if not trigger_dist or not getattr(trigger_dist, 'product', None):
        return zero_breakdown

    try:
        base_amount = resolve_distribution_base_amount(trigger_dist.product, trigger_dist.product.base_cost)
        distribution_result = calculate_distribution(trigger_dist.product, base_amount)
        direct_entry = next(
            (entry for entry in distribution_result.get('breakdown', []) if entry.get('type') == 'DIRECT_INCENTIVE'),
            None,
        )
        if not direct_entry:
            return zero_breakdown

        gross = Decimal(str(direct_entry.get('amount', 0))).quantize(Decimal('0.01'))
        tds_amount = (gross * Decimal('0.05')).quantize(Decimal('0.01'))
        repurchase_amount = (gross * Decimal('0.05')).quantize(Decimal('0.01'))
        welfare_amount = (gross * Decimal('0.05')).quantize(Decimal('0.01'))
        technology_amount = (gross * Decimal('0.05')).quantize(Decimal('0.01'))
        operations_amount = (gross * Decimal('0.05')).quantize(Decimal('0.01'))
        total_deduction = (tds_amount + repurchase_amount + welfare_amount + technology_amount + operations_amount).quantize(Decimal('0.01'))
        net_incentive = (gross - total_deduction).quantize(Decimal('0.01'))
    except Exception:
        gross = Decimal('0.00')
        tds_amount = Decimal('0.00')
        repurchase_amount = Decimal('0.00')
        welfare_amount = Decimal('0.00')
        technology_amount = Decimal('0.00')
        operations_amount = Decimal('0.00')
        total_deduction = Decimal('0.00')
        net_incentive = Decimal('0.00')

    credited_main_wallet = Decimal(str(getattr(credited_main_entry, 'amount', 0) or 0)).quantize(Decimal('0.01'))
    credited_repurchase_wallet = Decimal(str(getattr(repurchase_entry, 'amount', 0) or 0)).quantize(Decimal('0.01'))

    return {
        'gross_amount': gross,
        'tds_amount': tds_amount,
        'repurchase_amount': repurchase_amount,
        'welfare_amount': welfare_amount,
        'technology_amount': technology_amount,
        'operations_amount': operations_amount,
        'total_deduction': total_deduction,
        'net_incentive': net_incentive,
        'credited_main_wallet': credited_main_wallet,
        'credited_repurchase_wallet': credited_repurchase_wallet,
    }


def _map_withdrawal_status(user):
    latest = WithdrawalRequest.objects.filter(user=user).order_by('-created_at').first()
    if not latest:
        return {
            'withdrawal_option': True,
            'withdrawal_status': 'AWAITING_WITHDRAWAL',
            'withdrawal_status_label': 'Awaiting Withdrawal',
        }

    if latest.status == WithdrawalRequest.Status.PENDING:
        return {
            'withdrawal_option': False,
            'withdrawal_status': 'WAITING_FOR_APPROVAL',
            'withdrawal_status_label': 'Waiting for Approval',
        }

    if latest.status == WithdrawalRequest.Status.APPROVED:
        return {
            'withdrawal_option': False,
            'withdrawal_status': 'WAITING_FOR_APPROVAL',
            'withdrawal_status_label': 'Waiting for Approval',
        }

    if latest.status == WithdrawalRequest.Status.PAID:
        return {
            'withdrawal_option': True,
            'withdrawal_status': 'AWAITING_WITHDRAWAL',
            'withdrawal_status_label': 'Awaiting Withdrawal',
        }

    if latest.status == WithdrawalRequest.Status.REJECTED:
        return {
            'withdrawal_option': True,
            'withdrawal_status': 'AWAITING_WITHDRAWAL',
            'withdrawal_status_label': 'Awaiting Withdrawal',
        }

    return {
        'withdrawal_option': True,
        'withdrawal_status': 'AWAITING_WITHDRAWAL',
        'withdrawal_status_label': 'Awaiting Withdrawal',
    }


def _is_coin_utilization_complete_for_distributor(user, distributor):
    """
    For TWM_COINS voucher products with expiry configured, require full utilization
    of awarded TWM coins before marking withdrawal eligible.

    Returns:
      - True for non-TWM products or when no unutilized awarded coin codes remain.
      - False when awarded coin codes still have remaining value.
    """
    product = getattr(distributor, 'product', None)
    if not product:
        return True

    if getattr(product, 'product_type', None) != 'TWM_COINS':
        return True

    if not getattr(product, 'twm_coin_expiry_days', None):
        return True

    order = Order.objects.filter(distributor=distributor, user=user, status='COMPLETED').only('reference_id').first()
    if not order:
        return True

    source_reference = f"{order.reference_id}-VCH"
    has_unutilized = UserVoucherCode.objects.filter(
        user=user,
        source_reference_id=source_reference,
        remaining_value__gt=Decimal('0.00'),
    ).exclude(status=UserVoucherCode.Status.EXPIRED).exists()
    return not has_unutilized


def get_sponsor_income_summary(user):
    """
    Returns a structured summary of sponsor (direct incentive) income for the user:
      - Total earnings, this-month earnings
      - Total direct referrals count
      - Per-distributor-ID breakdown (positions, eligibility, income)
    """
    now = timezone.now()
    start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)

    # Main-wallet DIRECT_INCENTIVE credits for this user
    di_qs = WalletLedger.objects.filter(
        user=user,
        source_type=WalletLedger.SourceType.DIRECT_INCENTIVE,
        transaction_type=WalletLedger.TransactionType.CREDIT,
        wallet_type=WalletLedger.WalletType.EARNINGS,
    )

    agg = di_qs.aggregate(
        total=Sum('amount'),
        this_month=Sum('amount', filter=Q(created_at__gte=start_of_month)),
    )
    total_earned = agg['total'] or Decimal('0.00')
    this_month_earned = agg['this_month'] or Decimal('0.00')

    # All of the user's distributor IDs
    user_distributor_ids = list(
        defer_missing_product_part_payment_fields(
            DistributorID.objects.filter(user=user).select_related('product__opportunity_bundle'),
            relation_prefix='product',
        )
    )
    user_distributor_id_values = [d.id for d in user_distributor_ids]

    # Total direct referrals (across all of the user's distributor IDs)
    total_direct_referrals = 0
    for id_chunk in _chunked(user_distributor_id_values):
        total_direct_referrals += DistributorID.objects.filter(
            sponsor_distributor_id__in=id_chunk
        ).count()

    # Pre-compute sponsor-income totals per sponsor distributor using UUID extraction.
    # This supports both old and new reference formats.
    sponsor_income_by_distributor_id = {}
    direct_income_rows = list(di_qs.values('reference_id', 'amount'))
    trigger_uuid_set = set()
    for row in direct_income_rows:
        trigger_uuid = _extract_trigger_uuid_from_reference(row.get('reference_id'))
        if trigger_uuid:
            trigger_uuid_set.add(trigger_uuid)

    trigger_to_sponsor = {}
    if trigger_uuid_set:
        for chunk in _chunked(list(trigger_uuid_set)):
            for trigger_id, sponsor_id in DistributorID.objects.filter(id__in=chunk).values_list('id', 'sponsor_distributor_id'):
                trigger_to_sponsor[str(trigger_id)] = str(sponsor_id) if sponsor_id else None

    for row in direct_income_rows:
        trigger_uuid = _extract_trigger_uuid_from_reference(row.get('reference_id'))
        if not trigger_uuid:
            continue
        sponsor_id = trigger_to_sponsor.get(trigger_uuid)
        if not sponsor_id:
            continue
        sponsor_income_by_distributor_id[sponsor_id] = sponsor_income_by_distributor_id.get(sponsor_id, Decimal('0.00')) + (row.get('amount') or Decimal('0.00'))

    # Per-distributor-ID breakdown
    distributor_breakdown = []
    for d in user_distributor_ids:
        # All referrals registered under this distributor ID, ordered by position
        referral_qs = DistributorID.objects.filter(
            sponsor_distributor=d
        ).select_related('user').order_by('global_position')

        positions_count = referral_qs.count()
        active_positions = referral_qs.filter(is_active=True).count()

        # Income earned as sponsor via this specific distributor ID
        earned_via_this = sponsor_income_by_distributor_id.get(str(d.id), Decimal('0.00'))

        coin_utilization_complete = _is_coin_utilization_complete_for_distributor(user, d)
        period = get_current_owner_period(d)
        qual = refresh_qualification_for_distributor(d)
        period_directs = int(getattr(qual, 'achieved_directs', 0) or 0)
        power_stream_active = period_directs >= 3

        distributor_breakdown.append({
            'id': str(d.id),
            'code': d.distributor_code,
            'product_name': d.product.name if d.product else None,
            'opportunity_bundle_name': (
                d.product.opportunity_bundle.name
                if d.product and hasattr(d.product, 'opportunity_bundle') and d.product.opportunity_bundle
                else None
            ),
            'is_active': d.is_active,
            'positions_count': positions_count,
            'active_positions': active_positions,
            'ownership_period_id': str(period.id),
            'ownership_effective_from': period.ownership_effective_from.isoformat() if period.ownership_effective_from else None,
            'ownership_effective_to': period.ownership_effective_to.isoformat() if period.ownership_effective_to else None,
            'owner_period_direct_recruits': period_directs,
            'withdrawal_eligible': bool(getattr(qual, 'withdrawal_eligible', False)) and coin_utilization_complete,
            'coin_utilization_complete': coin_utilization_complete,
            'power_stream_active': power_stream_active,
            'total_income_earned': str(earned_via_this),
        })

    return {
        'total_earned': str(total_earned),
        'this_month_earned': str(this_month_earned),
        'total_direct_referrals': total_direct_referrals,
        'total_distributor_ids': len(user_distributor_ids),
        'active_distributor_ids': sum(1 for d in user_distributor_ids if d.is_active),
        'distributor_breakdown': distributor_breakdown,
    }


def get_sponsor_income_ledger(user, page=1, page_size=20):
    """
    Returns paginated direct incentive ledger entries enriched with referral info.
    """
    qs = WalletLedger.objects.filter(
        user=user,
        source_type=WalletLedger.SourceType.DIRECT_INCENTIVE,
        transaction_type=WalletLedger.TransactionType.CREDIT,
        wallet_type=WalletLedger.WalletType.EARNINGS,
    ).order_by('-created_at')

    total_count = qs.count()
    offset = (page - 1) * page_size
    entries = list(qs[offset: offset + page_size])

    withdrawal_meta = _map_withdrawal_status(user)

    # Batch-fetch trigger distributor IDs to enrich entries efficiently
    trigger_id_map = {}
    for entry in entries:
        trigger_uuid = _extract_trigger_uuid_from_reference(entry.reference_id)
        if trigger_uuid:
            trigger_id_map[entry.id] = trigger_uuid

    if trigger_id_map:
        trigger_ids = list(trigger_id_map.values())
        trigger_dists = {
            str(d.id): d
            for d in DistributorID.objects.filter(
                id__in=trigger_ids
            ).select_related('user', 'product__opportunity_bundle', 'sponsor_distributor__product__opportunity_bundle')
        }
    else:
        trigger_dists = {}

    repurchase_entry_map = {
        entry.reference_id.removesuffix('_RP'): entry
        for entry in WalletLedger.objects.filter(
            user=user,
            source_type=WalletLedger.SourceType.DIRECT_INCENTIVE,
            transaction_type=WalletLedger.TransactionType.CREDIT,
            wallet_type=WalletLedger.WalletType.REPURCHASE,
            reference_id__in=[f"{entry.reference_id}_RP" for entry in entries],
        )
    }

    rows = []
    for entry in entries:
        trigger_id_str = trigger_id_map.get(entry.id)
        trigger_dist = trigger_dists.get(trigger_id_str) if trigger_id_str else None
        referral_user = trigger_dist.user if trigger_dist else None
        sponsor_dist = trigger_dist.sponsor_distributor if trigger_dist else None
        repurchase_entry = repurchase_entry_map.get(entry.reference_id)
        breakdown = _get_direct_incentive_breakdown(trigger_dist, entry, repurchase_entry)

        opportunity_bundle_name = None
        opportunity_bundle_id = None
        if trigger_dist and trigger_dist.product and trigger_dist.product.opportunity_bundle:
            opportunity_bundle_name = trigger_dist.product.opportunity_bundle.name
            opportunity_bundle_id = str(trigger_dist.product.opportunity_bundle_id)

        rows.append({
            'id': str(entry.id),
            'amount': str(breakdown['gross_amount']),
            'gross_amount': str(breakdown['gross_amount']),
            'total_deduction': str(breakdown['total_deduction']),
            'net_incentive': str(breakdown['net_incentive']),
            'deduction_breakdown': {
                'tds_percent': '5.000',
                'tds_amount': str(breakdown['tds_amount']),
                'repurchase_percent': '5.000',
                'repurchase_amount': str(breakdown['repurchase_amount']),
                'welfare_percent': '5.000',
                'welfare_amount': str(breakdown['welfare_amount']),
                'technology_percent': '5.000',
                'technology_amount': str(breakdown['technology_amount']),
                'operations_percent': '5.000',
                'operations_amount': str(breakdown['operations_amount']),
            },
            'credited_main_wallet': str(breakdown['credited_main_wallet']),
            'credited_repurchase_wallet': str(breakdown['credited_repurchase_wallet']),
            'created_at': entry.created_at.isoformat(),
            'reference_id': entry.reference_id,
            # Referral: the person who joined under the user's distributor ID
            'referral_name': _user_display_name(referral_user) if referral_user else 'Unknown',
            'referral_distributor_code': trigger_dist.distributor_code if trigger_dist else None,
            # Which of the user's distributor IDs acted as sponsor
            'sponsor_distributor_code': sponsor_dist.distributor_code if sponsor_dist else None,
            'opportunity_bundle_id': opportunity_bundle_id,
            'opportunity_bundle_name': opportunity_bundle_name,
            'withdrawal_option': withdrawal_meta['withdrawal_option'],
            'withdrawal_status': withdrawal_meta['withdrawal_status'],
            'withdrawal_status_label': withdrawal_meta['withdrawal_status_label'],
        })

    has_next = (offset + page_size) < total_count
    has_prev = page > 1

    return {
        'count': total_count,
        'page': page,
        'page_size': page_size,
        'has_next': has_next,
        'has_previous': has_prev,
        'results': rows,
    }
