import logging
from datetime import timedelta
from decimal import Decimal, ROUND_HALF_UP

from django.db import transaction
from django.db.models import Q, Sum, Count
from django.utils import timezone

from apps.business.distributor.models import DistributorID
from apps.business.power_stream.models import (
    PowerStreamBonusRecord,
    PowerStreamConfig,
    PowerStreamMonthlyClaim,
)
from apps.business.wallet.models import UserVoucherCode
from apps.business.wallet.services.wallet_service import WalletService

logger = logging.getLogger(__name__)

EXPIRY_DAYS = 30


def _apply_bonus_filters(queryset, *, opportunity_bundle_id=None, earner_distributor_id=None):
    if opportunity_bundle_id:
        queryset = queryset.filter(opportunity_bundle_id=opportunity_bundle_id)
    if earner_distributor_id:
        queryset = queryset.filter(earner_distributor_id=earner_distributor_id)
    return queryset


def _q(amount):
    return Decimal(str(amount)).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)


def _is_coin_utilization_complete_for_distributor(user, distributor):
    """
    For TWM_COINS voucher products with expiry configured, withdrawal eligibility
    additionally requires that awarded coins are fully utilized.
    """
    from apps.business.orders.models import Order

    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 process_power_stream_bonus(distributor_id):
    """
    Called when a new DistributorID is created.
    Walks up the upline chain (levels 1-10 starting at the 3rd-position upline).
    For each eligible upline with >=2 direct referrals, creates a PENDING
    PowerStreamBonusRecord.  Does NOT credit wallets immediately — user claims monthly.
    Returns list of created record dicts for logging/testing.
    """
    results = []
    if not distributor_id or not distributor_id.sponsor_distributor:
        return results

    sponsor = distributor_id.sponsor_distributor
    if not sponsor or not sponsor.sponsor_distributor:
        return results

    opportunity_bundle = getattr(getattr(distributor_id, 'product', None), 'opportunity_bundle', None)
    if not opportunity_bundle:
        return results

    with transaction.atomic():
        current = sponsor.sponsor_distributor  # level-1 earner (U3)
        visited = set()
        now = timezone.now()

        for level in range(1, 11):
            if not current or current.id in visited:
                break
            visited.add(current.id)

            direct_count = DistributorID.objects.filter(
                sponsor_distributor=current, is_active=True
            ).count()
            if direct_count < 3:
                current = current.sponsor_distributor
                continue

            config = PowerStreamConfig.objects.filter(opportunity_bundle=opportunity_bundle, level=level).first()
            if not config or config.amount <= 0:
                current = current.sponsor_distributor
                continue

            gross = _q(config.amount)
            deduction = _q(gross * Decimal('0.25'))
            net = _q(gross - deduction)
            reference_id = f"PSB_{distributor_id.id}_{level}"

            if PowerStreamBonusRecord.objects.filter(reference_id=reference_id).exists():
                current = current.sponsor_distributor
                continue

            PowerStreamBonusRecord.objects.create(
                earner=current.user,
                earner_distributor=current,
                trigger_distributor=distributor_id,
                opportunity_bundle=opportunity_bundle,
                level=level,
                gross_amount=gross,
                net_amount=net,
                status=PowerStreamBonusRecord.Status.PENDING,
                expires_at=now + timedelta(days=EXPIRY_DAYS),
                reference_id=reference_id,
            )
            results.append({
                'level': level,
                'user_id': str(current.user_id),
                'gross': str(gross),
                'net': str(net),
                'reference_id': reference_id,
            })
            logger.info(
                "[POWER_STREAM] Pending record: earner=%s level=%d gross=%s trigger=%s",
                current.user_id, level, gross, distributor_id.id,
            )
            current = current.sponsor_distributor

    return results


def get_user_power_stream_summary(user, opportunity_bundle_id=None, earner_distributor_id=None):
    """
    Returns comprehensive power stream stats for the user dashboard.
    """
    from apps.business.orders.models import Order

    now = timezone.now()
    current_month = now.strftime('%Y-%m')
    start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)

    # Distributor IDs owned by user with position counts
    distributor_ids = list(DistributorID.objects.filter(user=user).select_related('product__opportunity_bundle'))
    dist_summaries = []
    for d in distributor_ids:
        pos_count = DistributorID.objects.filter(sponsor_distributor=d, is_active=True).count()
        coin_utilization_complete = _is_coin_utilization_complete_for_distributor(user, d)
        dist_summaries.append({
            'id': str(d.id),
            'code': d.distributor_code,
            'global_position': d.global_position,
            'is_active': d.is_active,
            'positions_count': pos_count,
            'coin_utilization_complete': coin_utilization_complete,
            'withdrawal_eligible': (pos_count >= 2) and coin_utilization_complete,
            'product_name': d.product.name if d.product else None,
            'opportunity_bundle_id': str(d.product.opportunity_bundle_id) if d.product and getattr(d.product, 'opportunity_bundle_id', None) 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
            ),
        })

    selected_distributor = None
    if earner_distributor_id:
        selected_distributor = next((d for d in distributor_ids if str(d.id) == str(earner_distributor_id)), None)
        if not selected_distributor:
            raise Exception("Selected distributor ID does not belong to the user.")

    available_schemes = []
    seen_scheme_ids = set()
    for row in dist_summaries:
        key = row['opportunity_bundle_id'] or '__none__'
        if key in seen_scheme_ids:
            continue
        seen_scheme_ids.add(key)
        available_schemes.append({
            'opportunity_bundle_id': row['opportunity_bundle_id'],
            'opportunity_bundle_name': row['opportunity_bundle_name'] or 'No opportunity_bundle',
        })

    max_positions = max((s['positions_count'] for s in dist_summaries), default=0)
    withdrawal_eligible = any(s['withdrawal_eligible'] for s in dist_summaries)
    power_stream_eligible = max_positions >= 3
    ambassador_eligible = max_positions >= 100

    # Aggregate bonus records
    bonuses = PowerStreamBonusRecord.objects.filter(earner=user)
    filtered_bonuses = _apply_bonus_filters(
        bonuses,
        opportunity_bundle_id=opportunity_bundle_id,
        earner_distributor_id=earner_distributor_id,
    )
    agg = bonuses.aggregate(
        gross_all=Sum('gross_amount'),
        net_all=Sum('net_amount'),
        pending_net=Sum('net_amount', filter=Q(status=PowerStreamBonusRecord.Status.PENDING)),
        claimed_net=Sum('net_amount', filter=Q(status=PowerStreamBonusRecord.Status.CLAIMED)),
        forfeited_net=Sum('net_amount', filter=Q(
            status__in=[PowerStreamBonusRecord.Status.EXPIRED, PowerStreamBonusRecord.Status.FORFEITED]
        )),
    )

    valid_pending = bonuses.filter(
        status=PowerStreamBonusRecord.Status.PENDING,
        expires_at__gt=now,
    ).aggregate(total=Sum('net_amount'))['total'] or Decimal('0.00')

    filtered_pending = filtered_bonuses.filter(
        status=PowerStreamBonusRecord.Status.PENDING,
        expires_at__gt=now,
    ).aggregate(total=Sum('net_amount'))['total'] or Decimal('0.00')

    # Monthly status
    last_claim = PowerStreamMonthlyClaim.objects.filter(user=user).order_by('-claim_month').first()
    already_claimed_this_month = PowerStreamMonthlyClaim.objects.filter(
        user=user, claim_month=current_month
    ).exists()

    own_purchase_count = Order.objects.filter(
        user=user, status='COMPLETED', created_at__gte=start_of_month
    ).count()
    referral_purchase_count = DistributorID.objects.filter(
        sponsor_distributor__user=user, is_active=True, created_at__gte=start_of_month
    ).count()
    own_purchase = own_purchase_count > 0
    referral_purchase = referral_purchase_count > 0
    activity_ok = (own_purchase_count + referral_purchase_count) >= 2

    can_claim = (
        power_stream_eligible
        and not already_claimed_this_month
        and activity_ok
        and valid_pending > Decimal('0.00')
    )

    # Level breakdown
    level_rows = (
        filtered_bonuses
        .values('level')
        .annotate(
            earned_count=Count('id'),
            earned_gross=Sum('gross_amount'),
            earned_net=Sum('net_amount'),
            claimed_net=Sum('net_amount', filter=Q(status=PowerStreamBonusRecord.Status.CLAIMED)),
            pending_net=Sum('net_amount', filter=Q(status=PowerStreamBonusRecord.Status.PENDING)),
        )
        .order_by('level')
    )
    level_breakdown = [
        {
            'level': r['level'],
            'earned_count': r['earned_count'],
            'earned_gross': str(r['earned_gross'] or 0),
            'earned_net': str(r['earned_net'] or 0),
            'claimed_net': str(r['claimed_net'] or 0),
            'pending_net': str(r['pending_net'] or 0),
        }
        for r in level_rows
    ]

    active_filter = {
        'opportunity_bundle_id': str(opportunity_bundle_id) if opportunity_bundle_id else None,
        'earner_distributor_id': str(earner_distributor_id) if earner_distributor_id else None,
        'earner_distributor_code': selected_distributor.distributor_code if selected_distributor else None,
        'opportunity_bundle_name': (
            selected_distributor.product.opportunity_bundle.name
            if selected_distributor and getattr(selected_distributor.product, 'opportunity_bundle', None)
            else next((item['opportunity_bundle_name'] for item in available_schemes if item['opportunity_bundle_id'] == str(opportunity_bundle_id)), None)
        ),
        'is_filtered': bool(opportunity_bundle_id or earner_distributor_id),
    }

    return {
        'distributor_ids': dist_summaries,
        'available_schemes': available_schemes,
        'active_filter': active_filter,
        'eligibility': {
            'max_positions': max_positions,
            'withdrawal_eligible': withdrawal_eligible,
            'power_stream_eligible': power_stream_eligible,
            'ambassador_eligible': ambassador_eligible,
        },
        'earnings': {
            'total_gross': str(agg['gross_all'] or 0),
            'total_net': str(agg['net_all'] or 0),
            'total_pending_valid': str(valid_pending),
            'total_claimed': str(agg['claimed_net'] or 0),
            'total_forfeited': str(agg['forfeited_net'] or 0),
        },
        'monthly': {
            'current_month': current_month,
            'last_claim_month': last_claim.claim_month if last_claim else None,
            'last_claim_amount': str(last_claim.total_net) if last_claim else None,
            'already_claimed_this_month': already_claimed_this_month,
            'activity_ok': activity_ok,
            'own_purchase_this_month': own_purchase,
            'referral_purchase_this_month': referral_purchase,
            'activity_count_this_month': own_purchase_count + referral_purchase_count,
            'can_claim': can_claim,
            'claimable_amount': str(filtered_pending if active_filter['is_filtered'] else valid_pending),
            'claimable_amount_total': str(valid_pending),
            'claimable_amount_filtered': str(filtered_pending),
            'filtered_records_count': filtered_bonuses.count(),
        },
        'level_breakdown': level_breakdown,
    }


def process_monthly_claim(user):
    """
    Claims all valid PENDING power stream bonuses for this calendar month (once per month).
    Eligibility:
      - >=3 positions under any distributor ID (power_stream_eligible)
      - NOT already claimed this month
      - Own purchase OR direct referral purchase this month
      - At least one non-expired PENDING bonus record
    Credits the net amount to main wallet.
    """
    from apps.business.orders.models import Order

    now = timezone.now()
    current_month = now.strftime('%Y-%m')
    start_of_month = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)

    with transaction.atomic():
        if PowerStreamMonthlyClaim.objects.filter(user=user, claim_month=current_month).exists():
            raise Exception("Power stream bonus already claimed for this month.")

        max_positions = 0
        for d in DistributorID.objects.filter(user=user):
            count = DistributorID.objects.filter(sponsor_distributor=d, is_active=True).count()
            if count > max_positions:
                max_positions = count
        if max_positions < 3:
            raise Exception(
                f"Not eligible: need >=3 positions under a distributor ID (max: {max_positions})."
            )

        own_purchase_count = Order.objects.filter(
            user=user, status='COMPLETED', created_at__gte=start_of_month
        ).count()
        referral_count = DistributorID.objects.filter(
            sponsor_distributor__user=user, is_active=True, created_at__gte=start_of_month
        ).count()
        if own_purchase_count + referral_count < 2:
            raise Exception(
                "Activity requirement not met: need at least 2 product purchases "
                "(own or network) this month."
            )

        pending = list(
            PowerStreamBonusRecord.objects.select_for_update().filter(
                earner=user,
                status=PowerStreamBonusRecord.Status.PENDING,
                expires_at__gt=now,
            )
        )
        if not pending:
            raise Exception("No pending power stream bonuses available to claim.")

        total_gross = _q(sum(r.gross_amount for r in pending))
        total_net = _q(sum(r.net_amount for r in pending))
        claim_reference_id = f"PS_CLAIM_{user.id}_{current_month}"

        WalletService.credit_main_wallet(
            user=user,
            amount=total_net,
            source_type='POWER_STREAM',
            reference_id=claim_reference_id,
        )

        for record in pending:
            record.status = PowerStreamBonusRecord.Status.CLAIMED
            record.claimed_at = now
            record.claim_period = current_month
            record.save(update_fields=['status', 'claimed_at', 'claim_period', 'updated_at'])

        PowerStreamMonthlyClaim.objects.create(
            user=user,
            claim_month=current_month,
            total_gross=total_gross,
            total_net=total_net,
            records_count=len(pending),
            status=PowerStreamMonthlyClaim.Status.CLAIMED,
        )

        logger.info(
            "[POWER_STREAM_CLAIM] user=%s month=%s records=%d gross=%s net=%s",
            user.id, current_month, len(pending), total_gross, total_net,
        )
        return {
            'claim_month': current_month,
            'records_count': len(pending),
            'total_gross': str(total_gross),
            'total_net': str(total_net),
            'reference_id': claim_reference_id,
        }


def expire_overdue_bonuses():
    """Forfeit PENDING bonuses past their expires_at. Run daily via Celery beat."""
    now = timezone.now()
    count = PowerStreamBonusRecord.objects.filter(
        status=PowerStreamBonusRecord.Status.PENDING,
        expires_at__lte=now,
    ).update(status=PowerStreamBonusRecord.Status.FORFEITED, updated_at=now)
    logger.info("[POWER_STREAM_EXPIRY] Forfeited %d overdue records.", count)
    return count
