from collections import defaultdict
from decimal import Decimal, ROUND_HALF_UP

from django.core.management.base import BaseCommand
from django.db import transaction
from django.db.models import Sum

from shop_management.models.data.product import Product
from shop_management.models.data.purchase import PurchaseItem
from shop_management.models.data.sale import Sale, SaleItem
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, normalize_currency_code


TWOPLACES = Decimal("0.01")


def _norm_text(value):
    return " ".join(str(value or "").strip().upper().split())


def _weighted_avg(rows, qty_attr, price_attr):
    total_qty = Decimal("0")
    total_value = Decimal("0")
    for row in rows:
        qty = Decimal(str(getattr(row, qty_attr) or 0))
        price = getattr(row, price_attr, None)
        if price is None:
            continue
        total_qty += qty
        total_value += qty * Decimal(str(price))
    if total_qty <= 0:
        return None
    return (total_value / total_qty).quantize(TWOPLACES, rounding=ROUND_HALF_UP)


class Command(BaseCommand):
    help = (
        "Complete branch inventory fix for one or all shops: "
        "1) merge duplicate products (same name), "
        "2) merge duplicate stock rows (same product + currency) with correct qty/value, "
        "3) dedupe + create missing payment-transfer sales for dashboard."
    )

    def add_arguments(self, parser):
        parser.add_argument(
            "--shop",
            type=int,
            help="Single branch shop id (optional if --all)",
        )
        parser.add_argument(
            "--all",
            action="store_true",
            help="Run for every branch (Kabul, Takhar, Badakhshan, ...)",
        )
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Show what would change without writing",
        )
        parser.add_argument(
            "--skip-sales",
            action="store_true",
            help="Only merge products/stock; skip transfer-sale repair",
        )

    def handle(self, *args, **options):
        dry_run = options["dry_run"]
        skip_sales = options["skip_sales"]
        shop_id = options.get("shop")
        run_all = options["all"]

        if not shop_id and not run_all:
            self.stderr.write(
                self.style.ERROR("Use --shop=<id> or --all")
            )
            return

        if run_all:
            shops = list(Shop.objects.all().order_by("id"))
        else:
            shops = list(Shop.objects.filter(pk=shop_id))
            if not shops:
                self.stderr.write(self.style.ERROR(f"Shop {shop_id} not found."))
                return

        self.stdout.write(
            self.style.NOTICE(
                f"{'DRY-RUN ' if dry_run else ''}Fixing {len(shops)} branch(es)..."
            )
        )

        for shop in shops:
            self.stdout.write("")
            self.stdout.write(self.style.MIGRATE_HEADING(f"=== {shop.name} (id={shop.id}) ==="))
            self._fix_shop(shop.id, dry_run=dry_run)

        if not skip_sales:
            self.stdout.write("")
            self.stdout.write(self.style.MIGRATE_HEADING("=== Transfer sales (dashboard) ==="))
            self._fix_transfer_sales(shop_id=None if run_all else shop_id, dry_run=dry_run)

        self.stdout.write("")
        self.stdout.write(
            self.style.SUCCESS(
                "Done. Refresh Stock List + Dashboard for each branch."
            )
        )

    def _fix_shop(self, shop_id, dry_run=False):
        before = self._stock_value_summary(shop_id)
        self.stdout.write("Stock value BEFORE:")
        self._print_summary(before)

        products = list(Product.objects.filter(shop_id=shop_id).order_by("id"))
        product_groups = self._build_product_groups(products)
        merged_products = 0
        deleted_products = 0

        for _key, group in product_groups.items():
            if len(group) < 2:
                continue
            survivor, duplicates = self._pick_survivor(group)
            self.stdout.write(
                f"  Product merge: keep #{survivor.id} '{survivor.name}' "
                f"(code={survivor.code or '-'}), remove {[p.id for p in duplicates]}"
            )
            merged_products += 1
            deleted_products += len(duplicates)
            if dry_run:
                continue
            with transaction.atomic():
                dup_ids = [p.id for p in duplicates]
                PurchaseItem.objects.filter(product_id__in=dup_ids).update(
                    product_id=survivor.id
                )
                SaleItem.objects.filter(product_id__in=dup_ids).update(
                    product_id=survivor.id
                )
                StockTransfer.objects.filter(product_id__in=dup_ids).update(
                    product_id=survivor.id
                )
                update_fields = []
                if not (survivor.code or "").strip():
                    for dup in duplicates:
                        if (dup.code or "").strip():
                            survivor.code = dup.code
                            update_fields.append("code")
                            break
                if survivor.category_id is None:
                    for dup in duplicates:
                        if dup.category_id:
                            survivor.category_id = dup.category_id
                            update_fields.append("category")
                            break
                if update_fields:
                    survivor.save(update_fields=update_fields + ["updated_at"])
                Product.objects.filter(id__in=dup_ids).delete()

        stock_merged, stock_removed = self._consolidate_stocks(shop_id, dry_run=dry_run)

        after = before if dry_run else self._stock_value_summary(shop_id)
        self.stdout.write("Stock value AFTER:" if not dry_run else "Stock value AFTER (preview=before):")
        self._print_summary(after)
        action = "Would merge" if dry_run else "Merged"
        self.stdout.write(
            f"  {action} products={merged_products} groups "
            f"({deleted_products} deleted); "
            f"stocks={stock_merged} groups ({stock_removed} rows removed)."
        )

    def _fix_transfer_sales(self, shop_id=None, dry_run=False):
        """Create missing transfer sales and remove duplicate sale rows."""
        view = StockTransferViewSet()
        qs = (
            StockTransfer.objects.filter(
                status=StockTransfer.Status.ACCEPTED,
                show_payment_button=True,
            )
            .select_related(
                "product",
                "supplier_branch",
                "customer_branch",
                "currency",
                "source_stock",
                "source_stock__purchase_item",
                "created_by",
                "accepted_by",
            )
            .order_by("id")
        )
        if shop_id:
            qs = qs.filter(supplier_branch_id=shop_id)

        created = 0
        skipped = 0
        deduped = 0

        for transfer in qs:
            notes = view._transfer_sale_notes(transfer)
            # Match exact notes or any notes containing this transfer id.
            sales = list(
                Sale.objects.filter(shop_id=transfer.supplier_branch_id)
                .filter(notes=notes)
                .order_by("id")
            )
            if not sales:
                sales = list(
                    Sale.objects.filter(
                        shop_id=transfer.supplier_branch_id,
                        notes__icontains=transfer.transfer_id,
                    ).order_by("id")
                )

            if len(sales) > 1:
                keep = sales[0]
                extras = sales[1:]
                self.stdout.write(
                    f"  Dedupe sales for {transfer.transfer_id}: "
                    f"keep #{keep.id}, remove {[s.id for s in extras]}"
                )
                deduped += len(extras)
                if not dry_run:
                    with transaction.atomic():
                        for sale in extras:
                            sale.sale_items.all().delete()
                            sale.delete()
                sales = [keep]

            if sales:
                skipped += 1
                continue

            amount = Decimal(str(transfer.transfer_quantity or 0)) * Decimal(
                str(transfer.sale_price or 0)
            )
            self.stdout.write(
                f"  Create sale {transfer.transfer_id}: "
                f"{transfer.supplier_branch.name} -> {transfer.customer_branch.name} "
                f"qty={transfer.transfer_quantity} amount={amount}"
            )
            created += 1
            if dry_run:
                continue
            with transaction.atomic():
                view._ensure_transfer_sale(
                    transfer,
                    source_stock=transfer.source_stock,
                    user=transfer.accepted_by or transfer.created_by,
                )

        action = "Would create" if dry_run else "Created"
        self.stdout.write(
            self.style.SUCCESS(
                f"{action} {created} sale(s); skipped {skipped}; "
                f"removed {deduped} duplicate sale(s)."
            )
        )

    def _build_product_groups(self, products):
        by_name = defaultdict(list)
        for product in products:
            name = _norm_text(product.name)
            if not name:
                continue
            by_name[name].append(product)
        return by_name

    def _pick_survivor(self, group):
        scored = []
        for product in group:
            stock_qty = (
                Stock.objects.filter(
                    shop_id=product.shop_id,
                    is_deleted=False,
                    purchase_item__product_id=product.id,
                ).aggregate(total=Sum("quantity"))["total"]
                or 0
            )
            ref_count = (
                PurchaseItem.objects.filter(product_id=product.id).count()
                + SaleItem.objects.filter(product_id=product.id).count()
                + StockTransfer.objects.filter(product_id=product.id).count()
            )
            has_code = 1 if (product.code or "").strip() else 0
            scored.append((int(stock_qty), ref_count, has_code, -product.id, product))
        scored.sort(reverse=True)
        return scored[0][4], [item[4] for item in scored[1:]]

    def _consolidate_stocks(self, shop_id, dry_run=False):
        stocks = (
            Stock.objects.filter(shop_id=shop_id, is_deleted=False)
            .select_related(
                "purchase_item",
                "purchase_item__product",
                "purchase_item__currency",
                "purchase_item__purchase__currency",
            )
            .order_by("id")
        )
        stock_list = list(stocks)
        if not stock_list:
            return 0, 0

        parent = {s.id: s.id for s in stock_list}

        def find(x):
            while parent[x] != x:
                parent[x] = parent[parent[x]]
                x = parent[x]
            return x

        def union(a, b):
            ra, rb = find(a), find(b)
            if ra != rb:
                parent[rb] = ra

        by_product = defaultdict(list)
        by_name = defaultdict(list)
        for stock in stock_list:
            product = stock.purchase_item.product if stock.purchase_item else None
            currency = normalize_currency_code(
                stock.currency.title if stock.currency else ""
            )
            if not currency or not product:
                continue
            by_product[(currency, product.id)].append(stock.id)
            name = _norm_text(product.name)
            if name:
                by_name[(currency, name)].append(stock.id)

        for ids in list(by_product.values()) + list(by_name.values()):
            first = ids[0]
            for other in ids[1:]:
                union(first, other)

        groups = defaultdict(list)
        for stock in stock_list:
            groups[find(stock.id)].append(stock)

        merged_groups = 0
        removed_rows = 0

        for rows in groups.values():
            if len(rows) < 2:
                continue
            rows_sorted = sorted(
                rows,
                key=lambda s: (0 if (s.quantity or 0) > 0 else 1, s.id),
            )
            primary = rows_sorted[0]
            siblings = rows_sorted[1:]
            product = primary.purchase_item.product if primary.purchase_item else None
            product_name = product.name if product else "?"
            currency = normalize_currency_code(
                primary.currency.title if primary.currency else ""
            )
            total_qty = sum(Decimal(str(r.quantity or 0)) for r in rows)
            new_avg = _weighted_avg(rows, "quantity", "average_buy_price")
            new_wholesale = _weighted_avg(rows, "quantity", "average_wholesale_price")
            new_retail = _weighted_avg(rows, "quantity", "average_retail_price")
            if new_avg is None:
                new_avg = Decimal(str(primary.average_buy_price or 0))

            self.stdout.write(
                f"  Stock merge: {product_name} {currency}: "
                f"{len(rows)} rows -> qty={total_qty} avg={new_avg} "
                f"(keep #{primary.id}, remove {[s.id for s in siblings]})"
            )
            merged_groups += 1
            removed_rows += len(siblings)
            if dry_run:
                continue

            with transaction.atomic():
                locked_primary = Stock.objects.select_for_update().get(pk=primary.id)
                for sibling in siblings:
                    locked_sib = Stock.objects.select_for_update().get(pk=sibling.id)
                    StockTransfer.objects.filter(target_stock=locked_sib).update(
                        target_stock=locked_primary
                    )
                    StockTransfer.objects.filter(source_stock=locked_sib).update(
                        source_stock=locked_primary
                    )
                    locked_sib.quantity = 0
                    locked_sib.is_deleted = True
                    locked_sib.save(
                        update_fields=["quantity", "is_deleted", "updated_at"]
                    )

                locked_primary.quantity = int(total_qty)
                locked_primary.average_buy_price = new_avg
                update_fields = ["quantity", "average_buy_price", "updated_at"]
                if new_wholesale is not None:
                    locked_primary.average_wholesale_price = new_wholesale
                    update_fields.append("average_wholesale_price")
                if new_retail is not None:
                    locked_primary.average_retail_price = new_retail
                    update_fields.append("average_retail_price")
                for sibling in [primary] + siblings:
                    if locked_primary.sale_price in (None, Decimal("0"), 0) and sibling.sale_price:
                        locked_primary.sale_price = sibling.sale_price
                        update_fields.append("sale_price")
                    if (
                        locked_primary.wholesale_price is None
                        and sibling.wholesale_price is not None
                    ):
                        locked_primary.wholesale_price = sibling.wholesale_price
                        update_fields.append("wholesale_price")
                    if (
                        locked_primary.retail_price is None
                        and sibling.retail_price is not None
                    ):
                        locked_primary.retail_price = sibling.retail_price
                        update_fields.append("retail_price")
                locked_primary.save(update_fields=list(dict.fromkeys(update_fields)))

        return merged_groups, removed_rows

    def _stock_value_summary(self, shop_id):
        stocks = (
            Stock.objects.filter(shop_id=shop_id, is_deleted=False, quantity__gt=0)
            .select_related(
                "purchase_item",
                "purchase_item__product",
                "purchase_item__currency",
                "purchase_item__purchase__currency",
            )
        )
        product_ids = set()
        total_qty = Decimal("0")
        totals = defaultdict(lambda: Decimal("0"))
        row_count = 0
        for stock in stocks:
            row_count += 1
            qty = Decimal(str(stock.quantity or 0))
            total_qty += qty
            product = stock.purchase_item.product if stock.purchase_item else None
            if product:
                product_ids.add(product.id)
            currency = normalize_currency_code(
                stock.currency.title if stock.currency else ""
            )
            if not currency:
                continue
            avg = Decimal(str(stock.average_buy_price or 0))
            totals[currency] += qty * avg
        return {
            "item_count": len(product_ids),
            "available_qty": total_qty,
            "stock_value": {
                k: v.quantize(TWOPLACES, rounding=ROUND_HALF_UP)
                for k, v in sorted(totals.items())
            },
            "row_count": row_count,
        }

    def _print_summary(self, summary):
        self.stdout.write(
            f"  rows={summary['row_count']} products={summary['item_count']} "
            f"qty={summary['available_qty']}"
        )
        if not summary["stock_value"]:
            self.stdout.write("  value=(none)")
            return
        for currency, amount in summary["stock_value"].items():
            self.stdout.write(f"  {amount} {currency}")
