"""
One-customer data fix: MALIM ANWAR (Takhar).

Problem:
  Jul 22, 2026 balance payment 5,900 AFG is listed on the statement but was never
  applied to sale.received_amount, so Paid / You Owe stay too high.

This command ONLY touches that customer. It applies the missing gap between
customer balance payments and amounts already on sales (same currency).
"""

from decimal import Decimal

from django.core.management.base import BaseCommand
from django.db import transaction

from shop_management.models.data.customer import Customer
from shop_management.models.data.customer_balance_payment import CustomerBalancePayment
from shop_management.models.data.sale import Sale
from shop_management.serializers.data.customer_balance_payment import (
    CustomerBalancePaymentSerializer,
)


class Command(BaseCommand):
    help = "Apply missing Jul 22 payment to MALIM ANWAR sales only (statement Paid/You Owe fix)."

    def add_arguments(self, parser):
        parser.add_argument(
            "--customer-name",
            default="MALIM ANWAR",
            help='Customer name (default: "MALIM ANWAR")',
        )
        parser.add_argument(
            "--shop-name",
            default="Takhar",
            help='Shop/branch name contains (default: "Takhar")',
        )
        parser.add_argument("--dry-run", action="store_true")

    def handle(self, *args, **options):
        name = (options["customer_name"] or "").strip()
        shop_name = (options["shop_name"] or "").strip()
        dry_run = options["dry_run"]

        qs = Customer.objects.filter(name__iexact=name).select_related("shop")
        if shop_name:
            qs = qs.filter(shop__name__icontains=shop_name)

        customers = list(qs)
        if not customers:
            # fallback: contains match
            qs = Customer.objects.filter(name__icontains=name).select_related("shop")
            if shop_name:
                qs = qs.filter(shop__name__icontains=shop_name)
            customers = list(qs)

        if not customers:
            self.stderr.write(self.style.ERROR(f'No customer found for "{name}" / shop "{shop_name}"'))
            return
        if len(customers) > 1:
            for c in customers:
                self.stderr.write(f"  id={c.id} name={c.name!r} shop={c.shop.name if c.shop else None}")
            self.stderr.write(self.style.ERROR("Multiple customers matched; refine --customer-name / --shop-name"))
            return

        customer = customers[0]
        self.stdout.write(
            f"Customer #{customer.id} {customer.name!r} @ {customer.shop.name if customer.shop else '—'}"
        )
        before_owed = customer.get_total_owed_by_currency()
        self.stdout.write(f"You Owe (before): {before_owed}")

        payments = list(
            CustomerBalancePayment.objects.filter(customer=customer)
            .select_related("currency")
            .order_by("payment_date", "id")
        )
        sales = list(
            Sale.objects.filter(customer=customer)
            .select_related("currency")
            .order_by("sale_date", "id")
        )

        # Sum payments and sale paid by currency id
        paid_from_payments = {}
        for bp in payments:
            if not bp.currency_id:
                continue
            paid_from_payments[bp.currency_id] = paid_from_payments.get(bp.currency_id, Decimal("0")) + Decimal(
                str(bp.amount or 0)
            ) + Decimal(str(getattr(bp, "discount", 0) or 0))
            cur = bp.currency.title if bp.currency else "?"
            self.stdout.write(
                f"  payment #{bp.id} {bp.payment_date} {bp.amount} {cur} | {bp.notes or ''}"
            )

        paid_on_sales = {}
        for sale in sales:
            if not sale.currency_id:
                continue
            total_paid = sale.total_paid or {}
            key = sale.currency.title if sale.currency else None
            amount = Decimal(str(total_paid.get(key, 0) or 0))
            paid_on_sales[sale.currency_id] = paid_on_sales.get(sale.currency_id, Decimal("0")) + amount
            self.stdout.write(
                f"  sale #{sale.id} {sale.sale_date} recv={sale.received_amount} "
                f"paid={total_paid} remain={sale.total_remained_amount}"
            )

        currency_ids = set(paid_from_payments) | set(paid_on_sales)
        if not currency_ids:
            self.stderr.write(self.style.ERROR("No payments/sales with currency found."))
            return

        total_applied = Decimal("0")
        with transaction.atomic():
            for currency_id in sorted(currency_ids):
                from_payments = paid_from_payments.get(currency_id, Decimal("0"))
                on_sales = paid_on_sales.get(currency_id, Decimal("0"))
                gap = from_payments - on_sales
                cur_title = next(
                    (bp.currency.title for bp in payments if bp.currency_id == currency_id and bp.currency),
                    str(currency_id),
                )
                self.stdout.write(
                    f"Currency {cur_title}: payments={from_payments} on_sales={on_sales} gap={gap}"
                )
                if gap <= 0:
                    continue

                # Apply gap using the same logic as collecting a payment (oldest unpaid sales first)
                class _FakePayment:
                    pass

                fake = _FakePayment()
                fake.customer_id = customer.id
                fake.currency_id = currency_id
                fake.amount = gap
                fake.discount = Decimal("0")
                fake.customer = customer

                if dry_run:
                    remaining = gap
                    for sale in Sale.objects.filter(
                        customer_id=customer.id, currency_id=currency_id
                    ).order_by("sale_date", "id"):
                        if remaining <= 0:
                            break
                        remained_map = sale.total_remained_amount or {}
                        currency_key = sale.currency.title if sale.currency else None
                        sale_remaining = Decimal(str(remained_map.get(currency_key, 0) or 0))
                        if sale_remaining <= 0:
                            continue
                        apply_amount = min(sale_remaining, remaining)
                        self.stdout.write(
                            f"  [dry-run] would apply {apply_amount} to sale #{sale.id} "
                            f"(recv {sale.received_amount} -> {Decimal(sale.received_amount or 0) + apply_amount})"
                        )
                        remaining -= apply_amount
                    total_applied += gap - max(remaining, Decimal("0"))
                else:
                    leftover = CustomerBalancePaymentSerializer._apply_payment_to_sales(fake)
                    applied = gap - Decimal(str(leftover or 0))
                    total_applied += applied
                    self.stdout.write(f"  applied {applied} (leftover {leftover})")

            if dry_run:
                transaction.set_rollback(True)

        # Refresh owed after apply
        customer = Customer.objects.get(pk=customer.id)
        after_owed = customer.get_total_owed_by_currency()
        self.stdout.write(f"You Owe (after): {after_owed}")
        self.stdout.write(
            self.style.SUCCESS(
                f"Done. customer={customer.name!r} applied={total_applied} dry_run={dry_run}"
            )
        )
