from collections import defaultdict
from decimal import Decimal, ROUND_HALF_UP

from django.core.management.base import BaseCommand
from django.db import transaction

from shop_management.models.data.shop import Shop
from shop_management.models.data.stock import Stock
from shop_management.models.data.stock_transfer import StockTransfer
from shop_management.views.data.stock import normalize_currency_code


TWOPLACES = Decimal("0.01")


def _money(value):
    return Decimal(str(value or 0)).quantize(TWOPLACES, rounding=ROUND_HALF_UP)


class Command(BaseCommand):
    help = (
        "Repair Takhar USD stock qty/value after Kabul→Takhar transfer cleanup. "
        "1) Restores SHORT lines up to transferred qty. "
        "2) Fixes over-reduced rows when date-scoped double-fix used incomplete transfer sums. "
        "3) Recalculates average_buy_price from transfer sale_price."
    )

    def add_arguments(self, parser):
        parser.add_argument(
            "--from-branch",
            default="Kabul Main Branch",
        )
        parser.add_argument(
            "--to-branch",
            default="Takhar Branch",
        )
        parser.add_argument(
            "--transfer-date",
            default="2026-08-05",
            help="Transfer batch date (YYYY-MM-DD).",
        )
        parser.add_argument(
            "--dry-run",
            action="store_true",
        )

    def handle(self, *args, **options):
        from_name = (options.get("from_branch") or "").strip()
        to_name = (options.get("to_branch") or "").strip()
        transfer_date = (options.get("transfer_date") or "").strip()
        dry_run = options.get("dry_run")

        source = Shop.objects.filter(name__iexact=from_name).first()
        dest = Shop.objects.filter(name__iexact=to_name).first()
        if not source or not dest:
            self.stderr.write(self.style.ERROR("Branch not found."))
            return

        before = self._usd_summary(dest.id)
        self.stdout.write(
            self.style.NOTICE(
                f"BEFORE {dest.name} USD value={before['value']} qty={before['qty']} rows={before['rows']}"
            )
        )

        # All accepted transfers Kabul→Takhar; filter USD in Python to avoid
        # MySQL collation errors on currency.title LIKE with non-latin text.
        all_transfers = (
            StockTransfer.objects.filter(
                status=StockTransfer.Status.ACCEPTED,
                supplier_branch=source,
                customer_branch=dest,
            )
            .select_related(
                "product",
                "currency",
                "target_stock",
                "target_stock__purchase_item__product",
                "target_stock__purchase_item__currency",
            )
            .order_by("id")
        )

        def _is_usd_transfer(tr):
            title = ""
            if tr.currency_id and tr.currency:
                title = tr.currency.title or ""
            elif tr.target_stock_id and tr.target_stock and tr.target_stock.currency:
                title = tr.target_stock.currency.title or ""
            return normalize_currency_code(title) == "USD"

        all_usd_transfers = [tr for tr in all_transfers if _is_usd_transfer(tr)]
        batch_transfers = [
            tr
            for tr in all_usd_transfers
            if tr.transfer_date and str(tr.transfer_date) == transfer_date
        ]

        # Group by product name: date-batch qty/value and all-time qty/value.
        batch = defaultdict(lambda: {"qty": Decimal("0"), "value": Decimal("0"), "ids": []})
        all_time = defaultdict(lambda: {"qty": Decimal("0"), "value": Decimal("0"), "ids": []})

        def _name(tr):
            if tr.target_stock and tr.target_stock.purchase_item and tr.target_stock.purchase_item.product:
                return str(tr.target_stock.purchase_item.product.name or "").strip()
            return str(tr.product.name or "").strip() if tr.product else ""

        for tr in all_usd_transfers:
            name = _name(tr)
            if not name:
                continue
            key = name.lower()
            qty = Decimal(str(tr.transfer_quantity or 0))
            price = Decimal(str(tr.sale_price or 0))
            all_time[key]["qty"] += qty
            all_time[key]["value"] += qty * price
            all_time[key]["ids"].append(tr.transfer_id or str(tr.id))
            all_time[key]["name"] = name

        for tr in batch_transfers:
            name = _name(tr)
            if not name:
                continue
            key = name.lower()
            qty = Decimal(str(tr.transfer_quantity or 0))
            price = Decimal(str(tr.sale_price or 0))
            batch[key]["qty"] += qty
            batch[key]["value"] += qty * price
            batch[key]["ids"].append(tr.transfer_id or str(tr.id))
            batch[key]["name"] = name

        # Active Takhar USD stocks by product name.
        stocks = (
            Stock.objects.filter(shop=dest, is_deleted=False, quantity__gt=0)
            .select_related(
                "purchase_item",
                "purchase_item__product",
                "purchase_item__currency",
                "purchase_item__purchase__currency",
            )
            .order_by("id")
        )
        stock_by_name = defaultdict(list)
        for stock in stocks:
            product = getattr(getattr(stock, "purchase_item", None), "product", None)
            if not product:
                continue
            code = normalize_currency_code(stock.currency.title if stock.currency else "")
            if code != "USD":
                continue
            stock_by_name[str(product.name or "").strip().lower()].append(stock)

        changed = 0
        keys = sorted(set(batch.keys()) | set(stock_by_name.keys()))

        for key in keys:
            if key not in batch and key not in all_time:
                continue
            rows = stock_by_name.get(key) or []
            if not rows:
                continue

            display = (batch.get(key) or all_time.get(key) or {}).get("name") or key
            stock_qty = sum(int(s.quantity or 0) for s in rows)
            batch_qty = int((batch.get(key) or {}).get("qty") or 0)
            all_qty = int((all_time.get(key) or {}).get("qty") or 0)
            transfer_qty = all_qty if all_qty > 0 else batch_qty
            transfer_value = (all_time.get(key) or batch.get(key) or {}).get("value") or Decimal("0")

            # Target qty:
            # - If stock < all-time transferred qty → restore missing transfer credit (SHORT).
            # - If a date-only double-fix over-reduced (stock + batch_qty == previous
            #   and all_qty > batch_qty), restore to stock + batch_qty when that equals
            #   a sensible prior+all_qty single application (stock + batch_qty >= all_qty
            #   and stock + batch_qty - all_qty is small prior).
            target_qty = stock_qty
            reason = None

            if transfer_qty > 0 and stock_qty < transfer_qty:
                # Missing goods from transfer(s).
                target_qty = transfer_qty
                reason = f"SHORT restore {stock_qty} → {target_qty} (transfer_sum={transfer_qty})"
            elif (
                batch_qty > 0
                and all_qty > batch_qty
                and stock_qty + batch_qty >= all_qty
            ):
                # Likely over-reduced by date-scoped --fix-extra-transfer.
                restored = stock_qty + batch_qty
                prior = restored - all_qty
                if prior >= 0:
                    target_qty = restored
                    reason = (
                        f"UNDO over-reduce {stock_qty} → {target_qty} "
                        f"(all_transfer={all_qty}, batch={batch_qty}, prior≈{prior})"
                    )

            primary = sorted(rows, key=lambda s: int(s.quantity or 0), reverse=True)[0]
            old_avg = Decimal(str(primary.average_buy_price or 0))
            old_value = _money(Decimal(stock_qty) * old_avg)

            # Rebuild average from transfer sale prices + residual at old avg.
            new_qty = Decimal(str(target_qty))
            t_qty = Decimal(str(transfer_qty))
            if t_qty > 0 and transfer_value > 0:
                residual_qty = new_qty - t_qty
                if residual_qty <= 0:
                    new_avg = (transfer_value / t_qty).quantize(TWOPLACES, rounding=ROUND_HALF_UP)
                elif old_avg > 0:
                    new_avg = (
                        (residual_qty * old_avg + transfer_value) / new_qty
                    ).quantize(TWOPLACES, rounding=ROUND_HALF_UP)
                else:
                    unit = (transfer_value / t_qty)
                    new_avg = (
                        (residual_qty * unit + transfer_value) / new_qty
                    ).quantize(TWOPLACES, rounding=ROUND_HALF_UP)
            else:
                new_avg = old_avg

            new_value = _money(new_qty * new_avg)
            qty_changed = int(target_qty) != int(stock_qty)
            avg_changed = new_avg != old_avg

            if not qty_changed and not avg_changed:
                continue

            self.stdout.write(
                f"{display} [USD] qty {stock_qty}→{int(target_qty)} "
                f"avg {old_avg}→{new_avg} value {old_value}→{new_value}"
                + (f" | {reason}" if reason else " | avg/value only")
            )

            if dry_run:
                changed += 1
                continue

            with transaction.atomic():
                locked = Stock.objects.select_for_update().get(pk=primary.pk)
                locked.quantity = int(target_qty)
                locked.average_buy_price = new_avg
                locked.save(update_fields=["quantity", "average_buy_price", "updated_at"])
                if locked.purchase_item_id and t_qty > 0 and new_qty <= t_qty:
                    pi = locked.purchase_item
                    pi.buy_price = new_avg
                    pi.save(update_fields=["buy_price", "updated_at"])
                for extra in rows:
                    if extra.pk == primary.pk:
                        continue
                    Stock.objects.filter(pk=extra.pk).update(is_deleted=True, quantity=0)
                changed += 1

        after = self._usd_summary(dest.id) if not dry_run else before
        if dry_run:
            self.stdout.write(self.style.WARNING(f"DRY-RUN planned changes: {changed}"))
        else:
            self.stdout.write(
                self.style.SUCCESS(
                    f"AFTER {dest.name} USD value={after['value']} qty={after['qty']} "
                    f"rows={after['rows']} | updated={changed}"
                )
            )

    def _usd_summary(self, shop_id):
        total_value = Decimal("0")
        total_qty = Decimal("0")
        rows = 0
        for stock in Stock.objects.filter(
            shop_id=shop_id,
            is_deleted=False,
            quantity__gt=0,
            purchase_item__isnull=False,
        ).select_related("purchase_item__currency", "purchase_item__purchase__currency"):
            code = normalize_currency_code(stock.currency.title if stock.currency else "")
            if code != "USD":
                continue
            qty = Decimal(str(stock.quantity or 0))
            total_qty += qty
            total_value += qty * Decimal(str(stock.average_buy_price or 0))
            rows += 1
        return {
            "value": _money(total_value),
            "qty": total_qty,
            "rows": rows,
        }
