# payments/management/commands/fix_duplicate_wallet_usage_today.py
from decimal import Decimal
from datetime import date, datetime

from django.core.management.base import BaseCommand, CommandError
from django.db import transaction
from django.db.models import F

from accounts.models import User
from payments.models import ViralPeWallet, ViralPeWalletUsage


def signed_amount(usage: ViralPeWalletUsage) -> Decimal:
    """credit -> +amount, debit -> -amount"""
    amt = Decimal(usage.amount_used)
    return amt if usage.transaction_type == "credit" else (amt * Decimal("-1"))


class Command(BaseCommand):
    help = (
        "Detects duplicate wallet usage entries for a user on a given date (ignoring time), "
        "and deletes exactly one latest record while reversing its impact on the wallet, "
        "only if wallet won't go negative. Default date=today. Dry-run by default."
    )

    def add_arguments(self, parser):
        parser.add_argument(
            "--mobile",
            required=True,
            help="User mobile number to check (e.g., 9000000001)",
        )
        parser.add_argument(
            "--date",
            dest="on_date",
            default=None,
            help="Date in YYYY-MM-DD; defaults to today.",
        )
        parser.add_argument(
            "--apply",
            action="store_true",
            help="Actually perform the deletion + wallet reversal. Omit for dry-run.",
        )

    def handle(self, *args, **opts):
        mobile = opts["mobile"]
        on_date_str = opts.get("on_date") or date.today().isoformat()
        apply = opts["apply"]

        # parse date
        try:
            target_date = datetime.strptime(on_date_str, "%Y-%m-%d").date()
        except ValueError:
            raise CommandError("Invalid --date. Use YYYY-MM-DD")

        # fetch user
        try:
            user = User.objects.get(mobile_number=mobile)
        except User.DoesNotExist:
            raise CommandError(f"User with mobile {mobile} not found.")

        usages = (
            ViralPeWalletUsage.objects
            .filter(user=user, used_on__date=target_date)
            .order_by("used_on", "id")
        )

        self.stdout.write(f"User: {user.id} / {mobile}")
        self.stdout.write(f"Date: {target_date.isoformat()}")
        self.stdout.write(f"Found {usages.count()} usage record(s) on this date.\n")

        for u in usages:
            self.stdout.write(
                f" - usage#{u.id} [{u.transaction_type}] amt={u.amount_used} "
                f"purpose={u.purpose_code or ''} order_id={u.order_id or ''} "
                f"used_on={u.used_on.strftime('%H:%M:%S')} (signed={signed_amount(u)})"
            )

        if usages.count() < 2:
            self.stdout.write("\nNo duplicates by date (ignoring time). Nothing to do.")
            return

        # Strategy: delete exactly ONE record (the latest) and reverse its impact.
        candidate = usages.last()
        s = signed_amount(candidate)

        # Current wallet
        wallet = ViralPeWallet.objects.filter(user=user).first()
        current_balance = wallet.balance if wallet else Decimal("0.00")

        # Reverse delta: deleting the usage means NEGATING its signed impact
        reverse_delta = (s * Decimal("-1"))
        projected_balance = current_balance + reverse_delta

        self.stdout.write("\nCandidate to delete (latest on the date):")
        self.stdout.write(
            f" -> usage#{candidate.id} [{candidate.transaction_type}] "
            f"amt={candidate.amount_used} (signed={s})"
        )
        self.stdout.write(f"Current wallet balance: {current_balance}")
        self.stdout.write(f"Reverse delta on delete: {reverse_delta} "
                          f"(wallet would become {projected_balance})")

        if projected_balance < 0:
            self.stdout.write(
                "\nSKIP: Deleting this record would make wallet negative. "
                "Marking for different manual action."
            )
            return

        if not apply:
            self.stdout.write("\nDRY RUN: Would delete the candidate and set balance to "
                              f"{projected_balance}. (Use --apply to execute.)")
            return

        # APPLY: Do it atomically and double-check concurrently
        try:
            with transaction.atomic():
                # lock wallet row
                wallet, _ = ViralPeWallet.objects.select_for_update().get_or_create(user=user)
                fresh_balance = wallet.balance

                # recompute with fresh balance (safety under concurrency)
                new_projected = fresh_balance + reverse_delta
                if new_projected < 0:
                    raise CommandError(
                        f"Concurrent change detected: cannot delete usage#{candidate.id} "
                        f"as wallet would go negative (now {fresh_balance} -> {new_projected})."
                    )

                # adjust wallet, then delete
                wallet.balance = F("balance") + reverse_delta
                wallet.save(update_fields=["balance"])
                candidate.delete()

            # read back
            wallet.refresh_from_db(fields=["balance"])
            self.stdout.write(
                f"\nAPPLIED: Deleted usage#{candidate.id}. "
                f"New wallet balance: {wallet.balance}"
            )
        except Exception as e:
            raise CommandError(f"Failed to apply changes: {e}")



# Usage

# Dry run (no changes, just prints what it would do):

# python manage.py fix_duplicate_wallet_usage_today --mobile 9000000007


# Pick a specific date:

# python manage.py fix_duplicate_wallet_usage_today --mobile 9000000007 --date 2025-10-24


# Apply the fix (actually delete + adjust wallet):

# python manage.py fix_duplicate_wallet_usage_today --mobile 9000000007 --apply



# from decimal import Decimal
# from datetime import date
# from django.db import transaction
# from django.db.models import F
# from accounts.models import User
# from payments.models import ViralPeWallet, ViralPeWalletUsage

# def signed_amount(u):
#     amt = Decimal(u.amount_used)
#     return amt if u.transaction_type == "credit" else (amt * Decimal("-1"))

# def fix_duplicate_today(mobile: str, apply=False, on_date=None):
#     d = on_date or date.today()
#     try:
#         user = User.objects.get(mobile_number=mobile)
#     except User.DoesNotExist:
#         print(f"User with mobile {mobile} not found.")
#         return

#     qs = ViralPeWalletUsage.objects.filter(user=user, used_on__date=d).order_by("used_on", "id")
#     print(f"User: {user.id} / {mobile}  Date: {d}  Count: {qs.count()}")
#     for u in qs:
#         print(f" - usage#{u.id} [{u.transaction_type}] amt={u.amount_used} used_on={u.used_on} signed={signed_amount(u)}")

#     if qs.count() < 2:
#         print("No duplicates by date. Nothing to do.")
#         return

#     cand = qs.last()
#     s = signed_amount(cand)
#     wallet = ViralPeWallet.objects.filter(user=user).first()
#     bal = wallet.balance if wallet else Decimal("0.00")
#     reverse_delta = (s * Decimal("-1"))
#     projected = bal + reverse_delta

#     print(f"\nCandidate: usage#{cand.id} [{cand.transaction_type}] amt={cand.amount_used} signed={s}")
#     print(f"Current balance: {bal} | Reverse delta: {reverse_delta} | Projected: {projected}")

#     if projected < 0:
#         print("SKIP: Deleting would make wallet negative. Take manual action.")
#         return

#     if not apply:
#         print("DRY RUN: Would delete this record and set balance to", projected)
#         return

#     try:
#         with transaction.atomic():
#             w, _ = ViralPeWallet.objects.select_for_update().get_or_create(user=user)
#             fresh = w.balance
#             new_projected = fresh + reverse_delta
#             if new_projected < 0:
#                 print("Concurrent change: now would go negative; abort.")
#                 return
#             w.balance = F("balance") + reverse_delta
#             w.save(update_fields=["balance"])
#             cand.delete()
#         w.refresh_from_db(fields=["balance"])
#         print(f"APPLIED: Deleted usage#{cand.id}. New balance: {w.balance}")
#     except Exception as e:
#         print("ERROR:", e)

# # Example:
# # fix_duplicate_today("9000000007", apply=False)  # dry run
# # fix_duplicate_today("9000000007", apply=True)   # apply
