import logging
from django.db import transaction
from django.db import models
from django.utils import timezone
from decimal import Decimal
import uuid

from ..models import SuperCoinTransaction, SuperCoinWallet, UserVoucherCode, Wallet, WalletLedger
from django.core.exceptions import ValidationError

class WalletService:
    @staticmethod
    def _normalize_amount(amount):
        if isinstance(amount, Decimal):
            return amount
        return Decimal(str(amount))

    @staticmethod
    def _generate_voucher_code():
        return f"VCH-{uuid.uuid4().hex[:12].upper()}"

    @staticmethod
    def _expire_voucher_codes(user, wallet):
        now = timezone.now()
        expired_qs = UserVoucherCode.objects.filter(
            user=user,
            wallet=wallet,
            status__in=[UserVoucherCode.Status.ACTIVE, UserVoucherCode.Status.PARTIAL],
            expires_at__isnull=False,
            expires_at__lt=now,
        )
        if not expired_qs.exists():
            return

        expired_total = Decimal('0.00')
        for code in expired_qs.select_for_update():
            expired_total += code.remaining_value
            code.remaining_value = Decimal('0.00')
            code.status = UserVoucherCode.Status.EXPIRED
            code.save(update_fields=['remaining_value', 'status', 'updated_at'])

        if expired_total > Decimal('0.00'):
            wallet.voucher_balance = max(Decimal('0.00'), wallet.voucher_balance - expired_total)
            wallet.save(update_fields=['voucher_balance'])

    @staticmethod
    def validate_wallet_consistency(user):
        """
        Sums all wallet ledger entries for the user and compares with wallet balances.
        Logs error and flags user/account if mismatch.
        Returns a dict with consistency status and details.
        """
        wallet = Wallet.objects.filter(user=user).first()
        if not wallet:
            logging.error(f"[WALLET CONSISTENCY] No wallet found for user_id={user.id}")
            return {"consistent": False, "error": "No wallet found", "user_id": user.id}

        # Sum all main wallet ledger entries
        main_credits = WalletLedger.objects.filter(user=user, wallet_type=WalletLedger.WalletType.EARNINGS, transaction_type=WalletLedger.TransactionType.CREDIT).aggregate(total=models.Sum('amount'))['total'] or Decimal('0.00')
        main_debits = WalletLedger.objects.filter(user=user, wallet_type=WalletLedger.WalletType.EARNINGS, transaction_type=WalletLedger.TransactionType.DEBIT).aggregate(total=models.Sum('amount'))['total'] or Decimal('0.00')
        main_expected = main_credits - main_debits

        # Sum all repurchase wallet ledger entries
        repurchase_credits = WalletLedger.objects.filter(user=user, wallet_type=WalletLedger.WalletType.REPURCHASE, transaction_type=WalletLedger.TransactionType.CREDIT).aggregate(total=models.Sum('amount'))['total'] or Decimal('0.00')
        repurchase_debits = WalletLedger.objects.filter(user=user, wallet_type=WalletLedger.WalletType.REPURCHASE, transaction_type=WalletLedger.TransactionType.DEBIT).aggregate(total=models.Sum('amount'))['total'] or Decimal('0.00')
        repurchase_expected = repurchase_credits - repurchase_debits

        main_match = main_expected == wallet.main_balance
        repurchase_match = repurchase_expected == wallet.repurchase_balance

        result = {
            "user_id": user.id,
            "main_ledger_sum": str(main_expected),
            "main_balance": str(wallet.main_balance),
            "main_consistent": main_match,
            "repurchase_ledger_sum": str(repurchase_expected),
            "repurchase_balance": str(wallet.repurchase_balance),
            "repurchase_consistent": repurchase_match,
        }

        if not main_match or not repurchase_match:
            logging.error(f"[WALLET CONSISTENCY ERROR] user_id={user.id} main_ledger_sum={main_expected} main_balance={wallet.main_balance} repurchase_ledger_sum={repurchase_expected} repurchase_balance={wallet.repurchase_balance}")
            # Optionally flag user/account here (e.g., set a flag on user or wallet)
            result["consistent"] = False
        else:
            result["consistent"] = True
        return result

    @staticmethod
    @transaction.atomic
    def credit_main_wallet(user, amount, source_type, reference_id, distributor_id=None):
        from apps.core.events import emit_event
        amount = WalletService._normalize_amount(amount)
        if amount <= 0:
            raise ValidationError("Amount must be greater than zero.")
        wallet, _ = Wallet.objects.select_for_update().get_or_create(user=user)
        if WalletLedger.objects.filter(reference_id=reference_id, source_type=source_type).exists():
            raise ValidationError("Duplicate transaction detected.")
        ledger = WalletLedger.objects.create(
            user=user,
            wallet=wallet,
            transaction_type=WalletLedger.TransactionType.CREDIT,
            wallet_type=WalletLedger.WalletType.EARNINGS,
            source_type=source_type,
            amount=amount,
            reference_id=reference_id
            # ...existing fields...
        )
        emit_event(
            "reward_credited",
            {
                "user_id": user.id,
                "distributor_id": distributor_id,
                "amount": str(amount),
                "reference_id": reference_id,
            }
        )
        wallet.main_balance = wallet.main_balance + amount
        wallet.save(update_fields=['main_balance'])
        return wallet


    @staticmethod
    @transaction.atomic
    def credit_repurchase_wallet(user, amount, source_type, reference_id, distributor_id=None):
        from apps.core.events import emit_event
        amount = WalletService._normalize_amount(amount)
        if amount <= 0:
            raise ValidationError("Amount must be greater than zero.")
        wallet, _ = Wallet.objects.select_for_update().get_or_create(user=user)
        if WalletLedger.objects.filter(reference_id=reference_id, source_type=source_type).exists():
            raise ValidationError("Duplicate transaction detected.")
        ledger = WalletLedger.objects.create(
            user=user,
            wallet=wallet,
            transaction_type=WalletLedger.TransactionType.CREDIT,
            wallet_type=WalletLedger.WalletType.REPURCHASE,
            source_type=source_type,
            amount=amount,
            reference_id=reference_id
            # ...existing fields...
        )
        emit_event(
            "reward_credited",
            {
                "user_id": user.id,
                "distributor_id": distributor_id,
                "amount": str(amount),
                "reference_id": reference_id,
            }
        )
        wallet.repurchase_balance = wallet.repurchase_balance + amount
        wallet.save(update_fields=['repurchase_balance'])
        return wallet

    @staticmethod
    @transaction.atomic
    def debit_main_wallet(user, amount, source_type, reference_id):
        amount = WalletService._normalize_amount(amount)
        if amount <= 0:
            raise ValidationError("Amount must be greater than zero.")
        wallet, _ = Wallet.objects.select_for_update().get_or_create(user=user)
        if WalletLedger.objects.filter(reference_id=reference_id, source_type=source_type).exists():
            raise ValidationError("Duplicate transaction detected.")
        if wallet.main_balance < amount:
            raise ValidationError("Insufficient main balance.")
        WalletLedger.objects.create(
            user=user,
            wallet=wallet,
            transaction_type=WalletLedger.TransactionType.DEBIT,
            wallet_type=WalletLedger.WalletType.EARNINGS,
            source_type=source_type,
            amount=amount,
            reference_id=reference_id
        )
        wallet.main_balance = wallet.main_balance - amount
        wallet.save(update_fields=['main_balance'])
        return wallet

    @staticmethod
    @transaction.atomic
    def debit_repurchase_wallet(user, amount, source_type, reference_id):
        amount = WalletService._normalize_amount(amount)
        if amount <= 0:
            raise ValidationError("Amount must be greater than zero.")
        wallet, _ = Wallet.objects.select_for_update().get_or_create(user=user)
        if WalletLedger.objects.filter(reference_id=reference_id, source_type=source_type).exists():
            raise ValidationError("Duplicate transaction detected.")
        if wallet.repurchase_balance < amount:
            raise ValidationError("Insufficient repurchase balance.")
        WalletLedger.objects.create(
            user=user,
            wallet=wallet,
            transaction_type=WalletLedger.TransactionType.DEBIT,
            wallet_type=WalletLedger.WalletType.REPURCHASE,
            source_type=source_type,
            amount=amount,
            reference_id=reference_id
        )
        wallet.repurchase_balance = wallet.repurchase_balance - amount
        wallet.save(update_fields=['repurchase_balance'])
        return wallet

    @staticmethod
    @transaction.atomic
    def credit_voucher_wallet(user, amount, source_type, reference_id, *, expires_at=None, remarks='', metadata=None):
        amount = WalletService._normalize_amount(amount)
        if amount <= 0:
            raise ValidationError("Amount must be greater than zero.")

        wallet, _ = Wallet.objects.select_for_update().get_or_create(user=user)
        if WalletLedger.objects.filter(reference_id=reference_id, source_type=source_type).exists():
            raise ValidationError("Duplicate transaction detected.")

        WalletLedger.objects.create(
            user=user,
            wallet=wallet,
            transaction_type=WalletLedger.TransactionType.CREDIT,
            wallet_type=WalletLedger.WalletType.VOUCHER,
            source_type=source_type,
            amount=amount,
            reference_id=reference_id,
            remarks=remarks or '',
            metadata=metadata or {},
        )

        code = WalletService._generate_voucher_code()
        while UserVoucherCode.objects.filter(code=code).exists():
            code = WalletService._generate_voucher_code()

        UserVoucherCode.objects.create(
            user=user,
            wallet=wallet,
            code=code,
            total_value=amount,
            remaining_value=amount,
            source_reference_id=reference_id,
            expires_at=expires_at,
            status=UserVoucherCode.Status.ACTIVE,
        )

        wallet.voucher_balance = wallet.voucher_balance + amount
        wallet.save(update_fields=['voucher_balance'])
        return wallet

    @staticmethod
    @transaction.atomic
    def debit_voucher_wallet(user, amount, source_type, reference_id):
        amount = WalletService._normalize_amount(amount)
        if amount <= 0:
            raise ValidationError("Amount must be greater than zero.")

        wallet, _ = Wallet.objects.select_for_update().get_or_create(user=user)
        WalletService._expire_voucher_codes(user, wallet)
        if WalletLedger.objects.filter(reference_id=reference_id, source_type=source_type).exists():
            raise ValidationError("Duplicate transaction detected.")
        if wallet.voucher_balance < amount:
            raise ValidationError("Insufficient voucher balance.")

        remaining_to_redeem = amount
        codes = (
            UserVoucherCode.objects.select_for_update()
            .filter(
                user=user,
                wallet=wallet,
                status__in=[UserVoucherCode.Status.ACTIVE, UserVoucherCode.Status.PARTIAL],
                remaining_value__gt=Decimal('0.00'),
                expires_at__isnull=True,
            )
            .order_by('created_at', 'id')
        )

        dated_codes = (
            UserVoucherCode.objects.select_for_update()
            .filter(
                user=user,
                wallet=wallet,
                status__in=[UserVoucherCode.Status.ACTIVE, UserVoucherCode.Status.PARTIAL],
                remaining_value__gt=Decimal('0.00'),
                expires_at__isnull=False,
                expires_at__gte=timezone.now(),
            )
            .order_by('expires_at', 'created_at', 'id')
        )

        codes = list(codes) + list(dated_codes)

        for code in codes:
            if remaining_to_redeem <= Decimal('0.00'):
                break

            redeem_from_code = min(code.remaining_value, remaining_to_redeem)
            code.remaining_value = code.remaining_value - redeem_from_code
            if code.remaining_value <= Decimal('0.00'):
                code.remaining_value = Decimal('0.00')
                code.status = UserVoucherCode.Status.USED
            else:
                code.status = UserVoucherCode.Status.PARTIAL
            code.save(update_fields=['remaining_value', 'status', 'updated_at'])
            remaining_to_redeem = remaining_to_redeem - redeem_from_code

        if remaining_to_redeem > Decimal('0.00'):
            raise ValidationError("Voucher code utilization mismatch. Please retry.")

        WalletLedger.objects.create(
            user=user,
            wallet=wallet,
            transaction_type=WalletLedger.TransactionType.DEBIT,
            wallet_type=WalletLedger.WalletType.VOUCHER,
            source_type=source_type,
            amount=amount,
            reference_id=reference_id,
        )

        wallet.voucher_balance = wallet.voucher_balance - amount
        wallet.save(update_fields=['voucher_balance'])
        return wallet

    @staticmethod
    @transaction.atomic
    def credit_supercoins(user, amount, source_type, reference_id, metadata=None):
        amount = WalletService._normalize_amount(amount)
        if amount <= 0:
            raise ValidationError("Amount must be greater than zero.")

        wallet, _ = SuperCoinWallet.objects.select_for_update().get_or_create(user=user)
        if SuperCoinTransaction.objects.filter(reference_id=reference_id, source_type=source_type).exists():
            raise ValidationError("Duplicate transaction detected.")

        SuperCoinTransaction.objects.create(
            user=user,
            wallet=wallet,
            transaction_type=SuperCoinTransaction.TransactionType.CREDIT,
            source_type=source_type,
            amount=amount,
            reference_id=reference_id,
            metadata=metadata or {},
        )

        wallet.balance = wallet.balance + amount
        wallet.save(update_fields=['balance', 'updated_at'])
        return wallet

    @staticmethod
    @transaction.atomic
    def debit_supercoins(user, amount, source_type, reference_id, metadata=None):
        amount = WalletService._normalize_amount(amount)
        if amount <= 0:
            raise ValidationError("Amount must be greater than zero.")

        wallet, _ = SuperCoinWallet.objects.select_for_update().get_or_create(user=user)
        if SuperCoinTransaction.objects.filter(reference_id=reference_id, source_type=source_type).exists():
            raise ValidationError("Duplicate transaction detected.")
        if wallet.balance < amount:
            raise ValidationError("Insufficient super coins balance.")

        SuperCoinTransaction.objects.create(
            user=user,
            wallet=wallet,
            transaction_type=SuperCoinTransaction.TransactionType.DEBIT,
            source_type=source_type,
            amount=amount,
            reference_id=reference_id,
            metadata=metadata or {},
        )

        wallet.balance = wallet.balance - amount
        wallet.save(update_fields=['balance', 'updated_at'])
        return wallet

    @staticmethod
    def credit_twm_coins_wallet(user, amount, source_type, reference_id):
        """Non-breaking alias for renamed Voucher Wallet -> TWM Coins."""
        return WalletService.credit_voucher_wallet(user, amount, source_type, reference_id)

    @staticmethod
    def debit_twm_coins_wallet(user, amount, source_type, reference_id):
        """Non-breaking alias for renamed Voucher Wallet -> TWM Coins."""
        return WalletService.debit_voucher_wallet(user, amount, source_type, reference_id)

    @staticmethod
    def credit(user, amount, source_type, reference_id, metadata=None):
        """Compatibility wrapper for older reward code paths."""
        return WalletService.credit_main_wallet(user, amount, source_type, reference_id)

    @staticmethod
    def debit(user, amount, source_type, reference_id, metadata=None):
        """Compatibility wrapper for older debit code paths."""
        return WalletService.debit_main_wallet(user, amount, source_type, reference_id)
