from collections import defaultdict

from django.core.management.base import BaseCommand
from django.db import transaction
from django.db.models import Q

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


class Command(BaseCommand):
    help = (
        "Audit accepted Kabul → Takhar transfers vs Takhar stock. "
        "Use --fix-extra-transfer when transfer qty was applied twice "
        "(old_qty + transfer + transfer)."
    )

    def add_arguments(self, parser):
        parser.add_argument(
            "--from-branch",
            default="Kabul Main Branch",
            help="Source branch name (default: Kabul Main Branch).",
        )
        parser.add_argument(
            "--to-branch",
            default="Takhar Branch",
            help="Destination branch name (default: Takhar Branch).",
        )
        parser.add_argument(
            "--product-name",
            type=str,
            help="Exact product name filter (optional).",
        )
        parser.add_argument(
            "--transfer-date",
            type=str,
            help=(
                "Only include accepted transfers on this date (YYYY-MM-DD). "
                "Example: 2026-08-05 for your two recent Kabul→Takhar batches."
            ),
        )
        parser.add_argument(
            "--fix",
            action="store_true",
            help="Fix stock == 2× transferred qty (no prior stock case).",
        )
        parser.add_argument(
            "--fix-extra-transfer",
            action="store_true",
            help=(
                "Fix double-applied transfer: stock looks like "
                "old + transfer + transfer. Sets qty to stock - transfer "
                "(e.g. 116 → 66 for DH AIR FILTER)."
            ),
        )
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="With --fix / --fix-extra-transfer, only print planned changes.",
        )
        parser.add_argument(
            "--show-ok",
            action="store_true",
            help="Also print OK / stock-only rows.",
        )

    def handle(self, *args, **options):
        from_name = (options.get("from_branch") or "").strip()
        to_name = (options.get("to_branch") or "").strip()
        product_name = (options.get("product_name") or "").strip()
        transfer_date = (options.get("transfer_date") or "").strip()
        do_fix = options.get("fix")
        do_fix_extra = options.get("fix_extra_transfer")
        dry_run = options.get("dry_run")
        show_ok = options.get("show_ok")

        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(
                    f"Branch not found. from={from_name!r} ({source}) to={to_name!r} ({dest})"
                )
            )
            return

        transfers = (
            StockTransfer.objects.filter(
                status=StockTransfer.Status.ACCEPTED,
                supplier_branch=source,
                customer_branch=dest,
            )
            .select_related(
                "product",
                "currency",
                "target_stock",
                "target_stock__purchase_item",
                "target_stock__purchase_item__product",
                "target_stock__purchase_item__currency",
                "target_stock__purchase_item__purchase__currency",
            )
            .order_by("id")
        )
        if transfer_date:
            transfers = transfers.filter(transfer_date=transfer_date)
        if product_name:
            transfers = transfers.filter(
                Q(product__name__iexact=product_name)
                | Q(target_stock__purchase_item__product__name__iexact=product_name)
            )

        grouped = defaultdict(lambda: {"transfer_qty": 0, "transfer_ids": [], "products": set()})
        for tr in transfers:
            dest_product = None
            if tr.target_stock and tr.target_stock.purchase_item:
                dest_product = tr.target_stock.purchase_item.product
            if not dest_product:
                dest_product = tr.product
            if not dest_product:
                continue
            name = str(dest_product.name or "").strip()
            if not name:
                continue
            currency = tr.currency or (tr.target_stock.currency if tr.target_stock else None)
            code = normalize_currency_code(currency.title) if currency else "UNKNOWN"
            key = (name.lower(), code)
            grouped[key]["transfer_qty"] += int(tr.transfer_quantity or 0)
            grouped[key]["transfer_ids"].append(tr.transfer_id or str(tr.id))
            grouped[key]["name"] = name
            grouped[key]["currency"] = code
            grouped[key]["products"].add(dest_product.id)

        stocks = (
            Stock.objects.filter(shop=dest, is_deleted=False, quantity__gt=0)
            .select_related(
                "purchase_item__product",
                "purchase_item__currency",
                "purchase_item__purchase__currency",
            )
            .order_by("id")
        )
        stock_groups = defaultdict(list)
        scoped_names = {k[0] for k in grouped.keys()} if (transfer_date or product_name) else None
        for stock in stocks:
            product = getattr(getattr(stock, "purchase_item", None), "product", None)
            if not product:
                continue
            name = str(product.name or "").strip()
            if not name:
                continue
            if product_name and name.lower() != product_name.lower():
                continue
            if scoped_names is not None and name.lower() not in scoped_names:
                continue
            currency = stock.currency
            code = normalize_currency_code(currency.title) if currency else "UNKNOWN"
            stock_groups[(name.lower(), code)].append(stock)

        counts = defaultdict(int)
        fixed = 0
        # When scoped to a transfer date, only audit products from those transfers.
        if transfer_date or product_name:
            all_keys = sorted(grouped.keys(), key=lambda k: (k[1], k[0]))
        else:
            all_keys = sorted(set(grouped.keys()) | set(stock_groups.keys()), key=lambda k: (k[1], k[0]))

        scope_note = f" | date={transfer_date}" if transfer_date else ""
        self.stdout.write(
            self.style.NOTICE(
                f"Audit {source.name} → {dest.name}{scope_note} | "
                f"transfer lines={transfers.count()} | groups={len(all_keys)}"
            )
        )

        for key in all_keys:
            info = grouped.get(key)
            transfer_qty = int((info or {}).get("transfer_qty") or 0)
            display_name = (info or {}).get("name") or key[0]
            code = key[1]
            rows = stock_groups.get(key) or []
            stock_qty = sum(int(s.quantity or 0) for s in rows)

            if transfer_qty <= 0 and stock_qty <= 0:
                continue
            if transfer_qty <= 0:
                status = "STOCK_ONLY"
            elif stock_qty == transfer_qty:
                status = "OK_MATCH"
            elif stock_qty == 2 * transfer_qty:
                status = "SUSPECT_DOUBLE"
            elif stock_qty > transfer_qty:
                status = "EXTRA_TRANSFER_ADD"
            elif stock_qty < transfer_qty:
                status = "SHORT"
            else:
                status = "CHECK"

            counts[status] += 1
            if status == "SUSPECT_DOUBLE":
                correct_qty = transfer_qty
            elif status == "EXTRA_TRANSFER_ADD":
                correct_qty = max(stock_qty - transfer_qty, 0)
            else:
                correct_qty = stock_qty

            should_print = (
                show_ok
                or status in ("SHORT", "SUSPECT_DOUBLE", "EXTRA_TRANSFER_ADD")
                or (do_fix_extra and status == "EXTRA_TRANSFER_ADD")
                or (do_fix and status == "SUSPECT_DOUBLE")
            )
            if not should_print:
                continue

            transfer_ids = ",".join((info or {}).get("transfer_ids") or [])[:80]
            extra = ""
            if status == "EXTRA_TRANSFER_ADD":
                old_before = correct_qty - transfer_qty
                extra = (
                    f" | was≈{old_before}+{transfer_qty}+{transfer_qty} "
                    f"→ should be {correct_qty} ({old_before}+{transfer_qty})"
                )
            elif status == "SUSPECT_DOUBLE":
                extra = f" | was≈0+{transfer_qty}+{transfer_qty} → should be {correct_qty}"

            self.stdout.write(
                f"{status:24} | {display_name} [{code}] "
                f"| stock={stock_qty} transfer_sum={transfer_qty} "
                f"| rows={len(rows)} | {transfer_ids}{extra}"
            )

            apply_fix = (
                rows
                and transfer_qty > 0
                and (
                    (do_fix and status == "SUSPECT_DOUBLE")
                    or (do_fix_extra and status == "EXTRA_TRANSFER_ADD")
                )
                and correct_qty != stock_qty
            )
            if not apply_fix:
                continue

            if dry_run:
                self.stdout.write(
                    self.style.WARNING(
                        f"  DRY-RUN would set qty {stock_qty} → {correct_qty} for {display_name}"
                    )
                )
                fixed += 1
                continue

            with transaction.atomic():
                primary = sorted(rows, key=lambda s: int(s.quantity or 0), reverse=True)[0]
                primary = Stock.objects.select_for_update().get(pk=primary.pk)
                primary.quantity = int(correct_qty)
                primary.save(update_fields=["quantity", "updated_at"])
                for extra_row in rows:
                    if extra_row.pk == primary.pk:
                        continue
                    Stock.objects.filter(pk=extra_row.pk).update(is_deleted=True, quantity=0)
                StockTransfer.objects.filter(
                    status=StockTransfer.Status.ACCEPTED,
                    supplier_branch=source,
                    customer_branch=dest,
                    target_stock_id__in=[s.pk for s in rows],
                ).update(target_stock=primary)
                fixed += 1
                self.stdout.write(
                    self.style.WARNING(
                        f"  FIXED qty {stock_qty} → {correct_qty} for {display_name}"
                    )
                )

        self.stdout.write("")
        self.stdout.write(self.style.SUCCESS("Summary:"))
        for status, n in sorted(counts.items()):
            self.stdout.write(f"  {status}: {n}")
        if do_fix or do_fix_extra:
            mode = "DRY-RUN planned" if dry_run else "Fixed"
            self.stdout.write(self.style.SUCCESS(f"{mode} groups: {fixed}"))
        else:
            self.stdout.write(
                "Notes:\n"
                "  OK_MATCH = stock equals transferred qty (good)\n"
                "  EXTRA_TRANSFER_ADD = old + transfer applied TWICE "
                "(e.g. 16+50+50=116 → should be 66)\n"
                "  SUSPECT_DOUBLE = no prior stock, transfer applied twice\n"
                "  SHORT = stock less than transferred (sold/missing)\n"
                "\n"
                "For your two recent Kabul→Takhar batches on 2026-08-05:\n"
                "  python manage.py audit_kabul_takhar_transfers "
                "--transfer-date 2026-08-05 --fix-extra-transfer --dry-run\n"
                "  python manage.py audit_kabul_takhar_transfers "
                "--transfer-date 2026-08-05 --fix-extra-transfer\n"
            )
