from collections import defaultdict
from decimal import Decimal

from django.core.management.base import BaseCommand
from django.db import transaction
from django.db.models import Q
from django.utils import timezone

from shop_management.models.data.purchase import Purchase, PurchaseItem
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 StockTransferViewSet


class Command(BaseCommand):
    help = (
        "Deep-repair destination stock: merge exact same-name duplicates per branch/"
        "currency, heal missing units, and apply Payment rules "
        "(Payment OFF → no Transfer-from supplier; Payment ON → keep it)."
    )

    def add_arguments(self, parser):
        parser.add_argument(
            "--shop",
            type=int,
            help="Only repair this destination shop id.",
        )
        parser.add_argument(
            "--shop-name",
            type=str,
            help="Only repair this destination shop by name (e.g. 'Takhar Branch').",
        )
        parser.add_argument(
            "--product-name",
            type=str,
            help=(
                "Exact product name match (case-insensitive). "
                "Example: 'DH AIR FILTER' will NOT touch 'DH AIR FILTER WASPIPE'."
            ),
        )
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Show what would change without writing.",
        )

    def handle(self, *args, **options):
        shop_id = options.get("shop")
        shop_name = (options.get("shop_name") or "").strip()
        product_name = (options.get("product_name") or "").strip()
        dry_run = options.get("dry_run")
        view = StockTransferViewSet()

        shops = Shop.objects.all().order_by("id")
        if shop_id:
            shops = shops.filter(pk=shop_id)
        if shop_name:
            shops = shops.filter(name__iexact=shop_name)
            if not shops.exists():
                self.stderr.write(self.style.ERROR(f"No shop found with name: {shop_name}"))
                return

        fixed_groups = 0
        skipped_groups = 0

        for shop in shops:
            stocks = (
                Stock.objects.filter(shop=shop, is_deleted=False, quantity__gt=0)
                .select_related(
                    "purchase_item",
                    "purchase_item__product",
                    "purchase_item__unit",
                    "purchase_item__currency",
                    "purchase_item__purchase",
                    "purchase_item__purchase__supplier",
                    "purchase_item__purchase__currency",
                )
                .order_by("id")
            )
            if product_name:
                stocks = stocks.filter(purchase_item__product__name__iexact=product_name)

            # Group by exact product name + currency family.
            groups = defaultdict(list)
            for stock in stocks:
                product = getattr(getattr(stock, "purchase_item", None), "product", None)
                if not product or not str(product.name or "").strip():
                    continue
                name_key = str(product.name).strip().lower()
                currency = stock.currency
                currency_code = view._normalize_currency_code(currency.title) if currency else "unknown"
                groups[(name_key, currency_code)].append(stock)

            for (name_key, currency_code), rows in groups.items():
                display_name = rows[0].purchase_item.product.name
                needs_merge = len(rows) > 1
                payment_on = self._group_has_payment_on_transfer(rows)
                has_bad_transfer_supplier = any(
                    view._is_transfer_supplier(getattr(getattr(r, "purchase_item", None), "purchase", None))
                    for r in rows
                ) and not payment_on
                missing_unit = any(
                    not (r.purchase_item and r.purchase_item.unit_id) for r in rows
                )

                if not (needs_merge or has_bad_transfer_supplier or missing_unit):
                    skipped_groups += 1
                    continue

                total_qty = sum(int(r.quantity or 0) for r in rows)
                supplier_labels = []
                for r in rows:
                    purchase = getattr(getattr(r, "purchase_item", None), "purchase", None)
                    supplier = getattr(purchase, "supplier", None) if purchase else None
                    supplier_labels.append(supplier.name if supplier else "--")

                self.stdout.write(
                    f"{shop.name} | {display_name} [{currency_code}] "
                    f"| rows={len(rows)} qty={total_qty} "
                    f"| payment_on={payment_on} "
                    f"| suppliers={supplier_labels} "
                    f"| missing_unit={missing_unit}"
                )

                if dry_run:
                    fixed_groups += 1
                    continue

                with transaction.atomic():
                    self._repair_group(
                        view=view,
                        shop=shop,
                        rows=rows,
                        payment_on=payment_on,
                        display_name=display_name,
                    )
                    fixed_groups += 1

        # Also clear Payment-OFF transfer target stocks that still show Transfer supplier
        # even when they are already a single row.
        resolved_shop_id = shop_id
        if not resolved_shop_id and shop_name:
            resolved_shop_id = shops.values_list("id", flat=True).first()
        transfer_fixed = self._repair_payment_off_transfers(
            view=view,
            shop_id=resolved_shop_id,
            product_name=product_name,
            dry_run=dry_run,
        )

        mode = "Would fix" if dry_run else "Fixed"
        self.stdout.write(
            self.style.SUCCESS(
                f"{mode} {fixed_groups} stock group(s); "
                f"skipped {skipped_groups}; "
                f"payment-off transfer cleanups={transfer_fixed}."
            )
        )

    def _group_has_payment_on_transfer(self, rows):
        stock_ids = [r.id for r in rows]
        return StockTransfer.objects.filter(
            status=StockTransfer.Status.ACCEPTED,
            show_payment_button=True,
            target_stock_id__in=stock_ids,
        ).exists()

    def _pick_primary(self, view, rows, payment_on):
        """Prefer listable + highest qty; if Payment ON prefer Transfer-supplier row."""
        def score(stock):
            listable = 1 if view._purchase_item_is_listable(stock.purchase_item) else 0
            has_unit = 1 if stock.purchase_item and stock.purchase_item.unit_id else 0
            transfer_sup = 1 if view._is_transfer_supplier(
                getattr(getattr(stock, "purchase_item", None), "purchase", None)
            ) else 0
            # Payment ON → keep Transfer supplier identity on survivor.
            transfer_pref = transfer_sup if payment_on else (0 if transfer_sup else 1)
            return (listable, has_unit, transfer_pref, int(stock.quantity or 0), stock.id)

        return sorted(rows, key=score, reverse=True)[0]

    def _repair_group(self, view, shop, rows, payment_on, display_name):
        primary = self._pick_primary(view, rows, payment_on)
        primary = Stock.objects.select_for_update().select_related(
            "purchase_item",
            "purchase_item__product",
            "purchase_item__unit",
            "purchase_item__currency",
            "purchase_item__purchase",
            "purchase_item__purchase__supplier",
        ).get(pk=primary.pk)

        # Fake transfer-like context for helpers.
        class _Ctx:
            pass

        ctx = _Ctx()
        ctx.product = primary.purchase_item.product if primary.purchase_item else None
        ctx.currency = primary.currency
        ctx.customer_branch_id = shop.id
        ctx.supplier_branch = None
        ctx.transfer_date = timezone.localdate()
        ctx.transfer_quantity = int(primary.quantity or 0)
        ctx.sale_price = primary.average_buy_price or 0

        destination_product = ctx.product
        transfer_currency = view._resolve_shop_currency(
            shop.id,
            primary.currency,
            create=True,
        )
        donor_unit = None
        for row in rows:
            if row.purchase_item and row.purchase_item.unit_id:
                donor_unit = row.purchase_item.unit
                break
        if not donor_unit:
            # Fall back to source-branch unit from any linked accepted transfer.
            linked = (
                StockTransfer.objects.filter(
                    status=StockTransfer.Status.ACCEPTED,
                    target_stock_id__in=[r.id for r in rows],
                )
                .select_related("source_stock__purchase_item__unit")
                .order_by("-id")
            )
            for transfer in linked:
                source_unit = transfer.source_stock.unit if transfer.source_stock else None
                if source_unit:
                    donor_unit = view._resolve_destination_unit(
                        shop.id,
                        source_unit,
                        user=transfer.accepted_by or transfer.created_by,
                    )
                    if donor_unit:
                        break

        if not donor_unit:
            # Any other stock at this shop that already has a unit (prefer PCS).
            from shop_management.models.data.utils import Unit

            donor_unit = (
                Unit.objects.filter(shop=shop, title__iexact="PCS").order_by("id").first()
                or Unit.objects.filter(shop=shop).order_by("id").first()
            )
            if not donor_unit:
                donor_unit = Unit.objects.create(shop=shop, title="PCS")

        # Merge siblings into primary.
        siblings = (
            Stock.objects.select_for_update()
            .filter(shop=shop, is_deleted=False, pk__in=[r.pk for r in rows])
            .exclude(pk=primary.pk)
            .select_related("purchase_item", "purchase_item__unit", "purchase_item__product")
        )
        for sibling in siblings:
            if not donor_unit and sibling.purchase_item and sibling.purchase_item.unit_id:
                donor_unit = sibling.purchase_item.unit

            old_qty = Decimal(str(primary.quantity or 0))
            add_qty = Decimal(str(sibling.quantity or 0))
            if add_qty > 0:
                total_qty = old_qty + add_qty
                primary.average_buy_price = (
                    (old_qty * Decimal(str(primary.average_buy_price or 0)))
                    + (add_qty * Decimal(str(sibling.average_buy_price or 0)))
                ) / total_qty
                if sibling.average_wholesale_price is not None or primary.average_wholesale_price is not None:
                    primary.average_wholesale_price = (
                        (old_qty * Decimal(str(primary.average_wholesale_price or 0)))
                        + (add_qty * Decimal(str(sibling.average_wholesale_price or 0)))
                    ) / total_qty
                if sibling.average_retail_price is not None or primary.average_retail_price is not None:
                    primary.average_retail_price = (
                        (old_qty * Decimal(str(primary.average_retail_price or 0)))
                        + (add_qty * Decimal(str(sibling.average_retail_price or 0)))
                    ) / total_qty
                primary.quantity = int(total_qty)
                if sibling.wholesale_price is not None and primary.wholesale_price is None:
                    primary.wholesale_price = sibling.wholesale_price
                if sibling.retail_price is not None and primary.retail_price is None:
                    primary.retail_price = sibling.retail_price
                if sibling.sale_price is not None and (primary.sale_price is None or primary.sale_price == 0):
                    primary.sale_price = sibling.sale_price

            StockTransfer.objects.filter(target_stock=sibling).update(target_stock=primary)
            StockTransfer.objects.filter(source_stock=sibling).update(source_stock=primary)
            sibling.is_deleted = True
            sibling.quantity = 0
            sibling.save(update_fields=["is_deleted", "quantity", "updated_at"])

        primary.save(
            update_fields=[
                "quantity",
                "average_buy_price",
                "average_wholesale_price",
                "average_retail_price",
                "wholesale_price",
                "retail_price",
                "sale_price",
                "updated_at",
            ]
        )

        # Ensure listable purchase_item + unit.
        if not view._purchase_item_is_listable(primary.purchase_item):
            inventory_purchase = Purchase.objects.create(
                shop=shop,
                supplier=None,
                currency=transfer_currency,
                amount=0,
                received_amount=0,
                purchase_date=timezone.localdate(),
            )
            pi = PurchaseItem.objects.create(
                purchase=inventory_purchase,
                currency=transfer_currency,
                product=destination_product,
                unit=donor_unit,
                quantity=int(primary.quantity or 0),
                buy_price=primary.average_buy_price or 0,
                wholesale_price=primary.wholesale_price,
                retail_price=primary.retail_price,
                sale_price=primary.sale_price,
            )
            primary.purchase_item = pi
            primary.save(update_fields=["purchase_item"])
        else:
            pi = primary.purchase_item
            update_fields = []
            if destination_product and pi.product_id != destination_product.id:
                # Keep exact displayed name product; prefer current if name matches.
                if str(pi.product.name or "").strip().lower() != display_name.strip().lower():
                    pi.product = destination_product
                    update_fields.append("product")
            if transfer_currency and pi.currency_id != transfer_currency.id:
                pi.currency = transfer_currency
                update_fields.append("currency")
            if donor_unit and pi.unit_id != donor_unit.id:
                pi.unit = donor_unit
                update_fields.append("unit")
            if update_fields:
                update_fields.append("updated_at")
                pi.save(update_fields=update_fields)

        # Payment rules on the surviving row.
        if payment_on:
            # Keep Transfer supplier if already present; nothing else required.
            return

        # Payment OFF / no payment transfer: never show Transfer-from supplier.
        if view._is_transfer_supplier(getattr(primary.purchase_item, "purchase", None)):
            inventory_purchase = Purchase.objects.create(
                shop=shop,
                supplier=None,
                currency=transfer_currency or primary.currency,
                amount=0,
                received_amount=0,
                purchase_date=timezone.localdate(),
            )
            new_pi = PurchaseItem.objects.create(
                purchase=inventory_purchase,
                currency=transfer_currency or primary.currency,
                product=primary.purchase_item.product,
                unit=primary.purchase_item.unit or donor_unit,
                quantity=int(primary.quantity or 0),
                buy_price=primary.purchase_item.buy_price or primary.average_buy_price or 0,
                wholesale_price=primary.purchase_item.wholesale_price or primary.wholesale_price,
                retail_price=primary.purchase_item.retail_price or primary.retail_price,
                sale_price=primary.purchase_item.sale_price or primary.sale_price,
            )
            primary.purchase_item = new_pi
            primary.save(update_fields=["purchase_item"])

    def _repair_payment_off_transfers(self, view, shop_id, product_name, dry_run):
        qs = (
            StockTransfer.objects.filter(
                status=StockTransfer.Status.ACCEPTED,
                show_payment_button=False,
                target_stock__isnull=False,
                target_stock__is_deleted=False,
            )
            .select_related(
                "product",
                "currency",
                "customer_branch",
                "source_stock",
                "target_stock",
                "target_stock__purchase_item",
                "target_stock__purchase_item__purchase",
                "target_stock__purchase_item__purchase__supplier",
                "accepted_by",
                "created_by",
            )
            .order_by("id")
        )
        if shop_id:
            qs = qs.filter(customer_branch_id=shop_id)
        if product_name:
            qs = qs.filter(
                Q(product__name__iexact=product_name)
                | Q(target_stock__purchase_item__product__name__iexact=product_name)
            )

        fixed = 0
        for transfer in qs:
            target = transfer.target_stock
            if not target or not view._is_transfer_supplier(
                getattr(getattr(target, "purchase_item", None), "purchase", None)
            ):
                continue

            self.stdout.write(
                f"Payment-OFF cleanup TRF {transfer.transfer_id or transfer.id}: "
                f"{transfer.product.name if transfer.product else '?'} @ "
                f"{transfer.customer_branch.name if transfer.customer_branch else '-'}"
            )
            if dry_run:
                fixed += 1
                continue

            with transaction.atomic():
                transfer = StockTransfer.objects.select_for_update().get(pk=transfer.pk)
                target = Stock.objects.select_for_update().filter(pk=transfer.target_stock_id).first()
                if not target:
                    continue
                source_stock = transfer.source_stock
                destination_product = view._resolve_destination_product(
                    transfer,
                    user=transfer.accepted_by or transfer.created_by,
                )
                transfer_currency = view._resolve_shop_currency(
                    transfer.customer_branch_id,
                    transfer.currency or (source_stock.currency if source_stock else None),
                    user=transfer.accepted_by or transfer.created_by,
                    create=True,
                )
                transfer_unit = view._resolve_destination_unit(
                    transfer.customer_branch_id,
                    source_stock.unit if source_stock else None,
                    user=transfer.accepted_by or transfer.created_by,
                )
                transfer_buy_price = (
                    transfer.sale_price
                    if transfer.sale_price is not None
                    else (
                        (source_stock.sale_price or source_stock.retail_price or 0)
                        if source_stock
                        else 0
                    )
                )
                user = transfer.accepted_by or transfer.created_by
                target = view._ensure_stock_only_no_transfer_supplier(
                    target,
                    transfer,
                    destination_product,
                    transfer_currency,
                    transfer_unit,
                    transfer_buy_price,
                    source_stock,
                    user,
                )
                target = view._consolidate_duplicate_destination_stocks(
                    target,
                    transfer,
                    destination_product=destination_product,
                    destination_currency=transfer_currency,
                    destination_unit=transfer_unit,
                )
                target = view._ensure_stock_only_no_transfer_supplier(
                    target,
                    transfer,
                    destination_product,
                    transfer_currency,
                    transfer_unit,
                    transfer_buy_price,
                    source_stock,
                    user,
                )
                if transfer.target_stock_id != target.id:
                    transfer.target_stock = target
                    transfer.save(update_fields=["target_stock", "updated_at"])
                fixed += 1
        return fixed
