from decimal import Decimal, ROUND_HALF_EVEN
from typing import Optional, Iterable

from django.core.management.base import BaseCommand
from django.contrib.auth import get_user_model
from django.db import transaction
from django.db.models import Sum, Case, When, F, DecimalField, Q, Value as V

from payments.models import ViralPeWallet, ViralPeWalletUsage  # noqa: F401


QDEC = Decimal  # alias


def q2(x) -> Decimal:
    return QDEC(str(x)).quantize(QDEC("0.00"), rounding=ROUND_HALF_EVEN)


def compute_expected_balance(user) -> Decimal:
    """
    expected = SUM(credits) - SUM(debits) over ViralPeWalletUsage for this user.
    Missing sums → treated as 0.
    """
    agg = (
        ViralPeWalletUsage.objects
        .filter(user=user)
        .aggregate(
            credits=Sum(
                Case(
                    When(transaction_type="credit", then=F("amount_used")),
                    default=V(0),
                    output_field=DecimalField(max_digits=12, decimal_places=2),
                )
            ),
            debits=Sum(
                Case(
                    When(transaction_type="debit", then=F("amount_used")),
                    default=V(0),
                    output_field=DecimalField(max_digits=12, decimal_places=2),
                )
            ),
        )
    )
    credits = q2(agg["credits"] or 0)
    debits = q2(agg["debits"] or 0)
    return q2(credits - debits)


def reconcile_user(user, apply: bool, tolerance: Decimal) -> dict:
    """
    Returns a summary dict for this user: {
      'mobile', 'current_balance', 'expected_balance', 'difference', 'fixed'
    }
    """
    try:
        wallet = ViralPeWallet.objects.get(user=user)
    except ViralPeWallet.DoesNotExist:
        return {
            "mobile": getattr(user, "mobile_number", str(user.pk)),
            "current_balance": None,
            "expected_balance": q2(0),
            "difference": None,
            "fixed": False,
            "note": "No wallet record",
        }

    expected = compute_expected_balance(user)
    current = q2(wallet.balance)
    diff = q2(expected - current)
    within_tol = abs(diff) <= tolerance

    fixed = False
    if apply and not within_tol:
        # Strong consistency: lock and re-check inside txn
        with transaction.atomic():
            w = (
                ViralPeWallet.objects
                .select_for_update()
                .get(pk=wallet.pk)
            )
            # Recompute with lock to reduce race windows
            re_expected = compute_expected_balance(user)
            re_current = q2(w.balance)
            re_diff = q2(re_expected - re_current)

            if abs(re_diff) > tolerance:
                w.balance = re_expected
                w.save(update_fields=["balance", "last_updated"])
                fixed = True
                current = re_expected     # reflect post-fix
                diff = q2(0)

    return {
        "mobile": getattr(user, "mobile_number", str(user.pk)),
        "current_balance": str(current),
        "expected_balance": str(expected),
        "difference": str(diff),
        "fixed": fixed,
    }


class Command(BaseCommand):
    help = (
        "Reconcile ViralPeWallet.balance with ViralPeWalletUsage credits/debits.\n"
        "Dry-run by default. Use --apply to write corrections."
    )

    def add_arguments(self, parser):
        parser.add_argument(
            "--mobile",
            type=str,
            help="Reconcile a single user by mobile_number (default: all users).",
        )
        parser.add_argument(
            "--apply",
            action="store_true",
            help="Apply fixes (otherwise dry-run).",
        )
        parser.add_argument(
            "--tolerance",
            type=str,
            default="0.00",
            help="Tolerance in ₹ to ignore tiny differences (default 0.00). Example: 0.01",
        )

    def handle(self, *args, **options):
        User = get_user_model()
        apply = bool(options["apply"])
        tolerance = q2(options["tolerance"])

        if options.get("mobile"):
            users = User.objects.filter(mobile_number=options["mobile"])
        else:
            # Only users who have a wallet or usage records
            users = User.objects.filter(
                Q(viralpewallet__isnull=False) |
                Q(wallet_usages__isnull=False)
            ).distinct()

        total = users.count()
        fixed_count = 0
        mismatches = 0

        # Output header
        self.stdout.write(
            f"Reconciling {total} user(s) | mode: {'APPLY' if apply else 'DRY-RUN'} | "
            f"tolerance: ₹{tolerance}"
        )
        self.stdout.write("-" * 80)

        for idx, user in enumerate(users.iterator(), 1):
            summary = reconcile_user(user, apply=apply, tolerance=tolerance)
            line = (
                f"[{idx}/{total}] {summary['mobile']}: "
                f"current=₹{summary['current_balance']} "
                f"expected=₹{summary['expected_balance']} "
                f"diff=₹{summary['difference']} "
                f"{'FIXED' if summary['fixed'] else ''}"
            )
            if summary.get("note"):
                line += f" ({summary['note']})"
            self.stdout.write(line)

            # analytics
            try:
                if q2(summary["difference"]) != q2(0):
                    mismatches += 1
                if summary["fixed"]:
                    fixed_count += 1
            except Exception:
                pass

        self.stdout.write("-" * 80)
        self.stdout.write(
            f"Done. Users processed: {total} | mismatches: {mismatches} | "
            f"fixed: {fixed_count} | mode: {'APPLY' if apply else 'DRY-RUN'}"
        )
