"""
Referral bonus and team bonus services.
"""
from decimal import Decimal

from django.contrib.auth import get_user_model
from django.db.models import Q, Sum
from django.utils import timezone

from wallet.models import Wallet, Transaction, TransactionStatus, TransactionType
from .models import (
    ReferralCommission,
    ReferralEarning,
    ReferralEarningType,
    ReferralProgramSettings,
)
from .rules import (
    MAXIMUM_QUALIFYING_DEPOSIT,
    MINIMUM_QUALIFYING_DEPOSIT,
    REFERRER_BONUS_PERCENTAGE,
    REFERRED_USER_BONUS_PERCENTAGE,
    TEAM_LEVELS,
)

User = get_user_model()


def get_referral_program_settings():
    """
    Return the active referral program settings or in-code defaults.
    """
    settings = ReferralProgramSettings.objects.order_by('id').first()
    if settings:
        return settings

    return ReferralProgramSettings(
        referrer_bonus_percentage=REFERRER_BONUS_PERCENTAGE,
        referred_user_bonus_percentage=REFERRED_USER_BONUS_PERCENTAGE,
        minimum_deposit_amount=MINIMUM_QUALIFYING_DEPOSIT,
        maximum_deposit_amount=MAXIMUM_QUALIFYING_DEPOSIT,
    )


def get_minimum_qualifying_deposit():
    """
    Return the current minimum qualifying deposit amount.
    """
    return get_referral_program_settings().minimum_deposit_amount


def get_maximum_qualifying_deposit():
    """
    Return the current maximum qualifying deposit amount.
    """
    return get_referral_program_settings().maximum_deposit_amount


def get_referrer_bonus_percentage():
    """
    Return the current direct referrer bonus percentage.
    """
    return get_referral_program_settings().referrer_bonus_percentage


def get_referred_user_bonus_percentage():
    """
    Return the current referred-user bonus percentage.
    """
    return get_referral_program_settings().referred_user_bonus_percentage


def get_team_levels():
    """
    Return configured team bonus levels.
    """
    configured_levels = list(ReferralCommission.objects.order_by('level'))
    levels = [level for level in configured_levels if level.is_active]
    if configured_levels:
        return [
            {
                'level': level.level,
                'required_members': level.required_members,
                'bonus_amount': level.bonus_amount,
                'commission': level,
            }
            for level in levels
        ]
    return TEAM_LEVELS


def get_qualified_direct_referrals(user):
    """
    Return direct referrals who have approved deposits totaling at least the minimum.
    """
    minimum_deposit = get_minimum_qualifying_deposit()
    return user.referred_users.annotate(
        approved_total=Sum('deposits__amount', filter=Q(deposits__status='APPROVED'))
    ).filter(approved_total__gte=minimum_deposit)


def get_qualified_direct_referral_count(user):
    """
    Return the count of qualified direct referrals.
    """
    return get_qualified_direct_referrals(user).count()


def get_team_progress(user):
    """
    Return team progress data for referral dashboard views.
    """
    qualified_count = get_qualified_direct_referral_count(user)
    levels = []
    current_level = 0
    next_level = None

    for level_rule in get_team_levels():
        achieved = qualified_count >= level_rule['required_members']
        if achieved:
            current_level = level_rule['level']
        elif not next_level:
            next_level = level_rule

        levels.append({
            **level_rule,
            'achieved': achieved,
            'remaining_members': max(level_rule['required_members'] - qualified_count, 0),
        })

    return {
        'qualified_count': qualified_count,
        'current_level': current_level,
        'next_level': next_level,
        'levels': levels,
    }


def get_team_level_summary(user):
    """
    Return current/next level details for dashboard summaries.
    """
    team_progress = get_team_progress(user)
    current_level_detail = next(
        (level for level in team_progress['levels'] if level['level'] == team_progress['current_level']),
        None,
    )
    next_level = team_progress['next_level']
    latest_team_bonus = ReferralEarning.objects.filter(
        user=user,
        is_credited=True,
        earning_type=ReferralEarningType.TEAM_BONUS,
    ).order_by('-level_achieved', '-created_at').first()

    if next_level and next_level['required_members']:
        progress_percent = min(
            int((team_progress['qualified_count'] / next_level['required_members']) * 100),
            100,
        )
    elif current_level_detail:
        progress_percent = 100
    else:
        progress_percent = 0

    return {
        **team_progress,
        'current_level_detail': current_level_detail,
        'latest_team_bonus': latest_team_bonus,
        'progress_percent': progress_percent,
    }


def _sync_reward_balances(user, amount_delta):
    """
    Apply referral-related balance deltas to wallet and user summary fields.
    """
    wallet, created = Wallet.objects.get_or_create(user=user)
    wallet.balance += amount_delta
    wallet.total_earnings += amount_delta
    wallet.referral_earnings += amount_delta

    if wallet.balance < 0:
        wallet.balance = Decimal('0.00')
    if wallet.total_earnings < 0:
        wallet.total_earnings = Decimal('0.00')
    if wallet.referral_earnings < 0:
        wallet.referral_earnings = Decimal('0.00')

    wallet.save()

    user.total_earnings += amount_delta
    user.referral_earnings += amount_delta

    if user.total_earnings < 0:
        user.total_earnings = Decimal('0.00')
    if user.referral_earnings < 0:
        user.referral_earnings = Decimal('0.00')

    user.save(update_fields=['total_earnings', 'referral_earnings', 'updated_at'])
    return wallet


def _reward_reference_id(earning):
    """
    Build a stable transaction reference for a referral earning.
    """
    if earning.earning_type == ReferralEarningType.TEAM_BONUS:
        return f'TEAM-BONUS-{earning.user_id}-L{earning.level_achieved}'
    return f'REF-BONUS-{earning.id}'


def _reward_description(earning):
    """
    Build a user-facing description for a referral earning transaction.
    """
    if earning.earning_type == ReferralEarningType.REFERRER_BONUS:
        return f'{earning.percentage_applied}% referral bonus from {earning.referred_user.username} deposit'
    if earning.earning_type == ReferralEarningType.REFERRED_USER_BONUS:
        return f'{earning.percentage_applied}% referral reward on your deposit via {earning.referred_user.username}'
    return f'Level {earning.level_achieved} team bonus credited'


def _sync_reward_transaction(earning):
    """
    Create or update the transaction linked to a referral earning.
    """
    wallet, created = Wallet.objects.get_or_create(user=earning.user)
    transaction, created = Transaction.objects.get_or_create(
        wallet=wallet,
        transaction_type=TransactionType.REFERRAL_BONUS,
        reference_id=_reward_reference_id(earning),
        defaults={
            'amount': earning.amount,
            'status': TransactionStatus.COMPLETED,
            'description': _reward_description(earning),
            'completed_at': timezone.now(),
        }
    )

    if not created:
        transaction.amount = earning.amount
        transaction.status = TransactionStatus.COMPLETED
        transaction.description = _reward_description(earning)
        transaction.completed_at = timezone.now()
        transaction.save()


def _cancel_reward_transaction(earning):
    """
    Mark the transaction linked to a referral earning as cancelled.
    """
    Transaction.objects.filter(
        wallet__user=earning.user,
        transaction_type=TransactionType.REFERRAL_BONUS,
        reference_id=_reward_reference_id(earning),
    ).update(
        status=TransactionStatus.CANCELLED,
        completed_at=timezone.now(),
        description=f'{_reward_description(earning)} (reversed)',
    )


def _credit_or_reactivate_earning(
    *,
    user,
    referred_user,
    deposit,
    earning_type,
    amount,
    commission=None,
    percentage_applied=None,
    level_achieved=None,
    notes='',
):
    """
    Credit a referral earning once and reactivate it if it was previously reversed.
    """
    earning_queryset = ReferralEarning.objects.filter(
        user=user,
        earning_type=earning_type,
        level_achieved=level_achieved,
    )

    if earning_type == ReferralEarningType.TEAM_BONUS:
        earning = earning_queryset.order_by('id').first()
    else:
        earning = earning_queryset.filter(deposit=deposit).order_by('id').first()

    if not earning:
        earning = ReferralEarning(
            user=user,
            referred_user=referred_user,
            deposit=deposit,
            earning_type=earning_type,
            level_achieved=level_achieved,
        )

    if earning.is_credited:
        return earning

    earning.referred_user = referred_user
    earning.commission = commission
    earning.amount = amount
    earning.deposit_amount = deposit.amount if deposit else Decimal('0.00')
    earning.percentage_applied = percentage_applied
    earning.notes = notes
    earning.is_credited = True
    earning.save()

    _sync_reward_balances(user, amount)
    _sync_reward_transaction(earning)
    return earning


def _reverse_earning(earning, reversal_note):
    """
    Reverse a credited referral earning.
    """
    if not earning.is_credited:
        return

    _sync_reward_balances(earning.user, -earning.amount)
    earning.is_credited = False
    earning.notes = f'{earning.notes} | {reversal_note}'.strip(' |')
    earning.save(update_fields=['is_credited', 'notes'])
    _cancel_reward_transaction(earning)


def apply_referral_rewards_for_approved_deposit(deposit):
    """
    Apply the direct 10%/5% bonuses and any unlocked team bonuses for an approved deposit.
    """
    minimum_deposit = get_minimum_qualifying_deposit()
    maximum_deposit = get_maximum_qualifying_deposit()

    if deposit.amount < minimum_deposit or deposit.amount > maximum_deposit:
        return

    referred_user = deposit.user
    referrer = referred_user.referred_by

    if not referrer:
        return

    referrer_bonus_percentage = get_referrer_bonus_percentage()
    referred_user_bonus_percentage = get_referred_user_bonus_percentage()
    referrer_bonus = (deposit.amount * referrer_bonus_percentage) / Decimal('100')
    referred_user_bonus = (deposit.amount * referred_user_bonus_percentage) / Decimal('100')

    _credit_or_reactivate_earning(
        user=referrer,
        referred_user=referred_user,
        deposit=deposit,
        earning_type=ReferralEarningType.REFERRER_BONUS,
        amount=referrer_bonus,
        percentage_applied=referrer_bonus_percentage,
        notes=f'Referrer bonus from deposit #{deposit.id}',
    )

    _credit_or_reactivate_earning(
        user=referred_user,
        referred_user=referrer,
        deposit=deposit,
        earning_type=ReferralEarningType.REFERRED_USER_BONUS,
        amount=referred_user_bonus,
        percentage_applied=referred_user_bonus_percentage,
        notes=f'Referred user bonus on deposit #{deposit.id}',
    )

    qualified_count = get_qualified_direct_referral_count(referrer)
    for level_rule in get_team_levels():
        if qualified_count < level_rule['required_members']:
            continue

        _credit_or_reactivate_earning(
            user=referrer,
            referred_user=None,
            deposit=deposit,
            earning_type=ReferralEarningType.TEAM_BONUS,
            amount=level_rule['bonus_amount'],
            commission=level_rule.get('commission'),
            level_achieved=level_rule['level'],
            notes=(
                f"Level {level_rule['level']} team bonus for "
                f"{level_rule['required_members']} qualified members"
            ),
        )


def reverse_referral_rewards_for_deposit(deposit):
    """
    Reverse all referral rewards tied to a deposit when its approval is removed.
    """
    direct_earnings = ReferralEarning.objects.filter(
        deposit=deposit,
        earning_type__in=[
            ReferralEarningType.REFERRER_BONUS,
            ReferralEarningType.REFERRED_USER_BONUS,
        ],
        is_credited=True,
    )

    for earning in direct_earnings:
        _reverse_earning(earning, f'Reversed because deposit #{deposit.id} is no longer approved')

    referrer = deposit.user.referred_by
    if not referrer:
        return

    qualified_count = get_qualified_direct_referral_count(referrer)
    for level_rule in get_team_levels():
        if qualified_count >= level_rule['required_members']:
            continue

        team_earning = ReferralEarning.objects.filter(
            user=referrer,
            earning_type=ReferralEarningType.TEAM_BONUS,
            level_achieved=level_rule['level'],
            is_credited=True,
        ).order_by('id').first()

        if team_earning:
            _reverse_earning(
                team_earning,
                (
                    f"Level {level_rule['level']} reversed because qualified team "
                    f"members dropped below {level_rule['required_members']}"
                ),
            )
