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 Prefetch

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)


def _shop_stock_value_summary(shop_id):
    totals = {}
    qty_total = Decimal("0")
    for stock in Stock.objects.filter(
        shop_id=shop_id,
        is_deleted=False,
        quantity__gt=0,
        purchase_item__isnull=False,
        purchase_item__is_deleted=False,
        purchase_item__purchase__is_deleted=False,
    ).select_related(
        "purchase_item",
        "purchase_item__currency",
        "purchase_item__purchase__currency",
    ):
        qty = Decimal(str(stock.quantity or 0))
        qty_total += qty
        code = normalize_currency_code(stock.currency.title if stock.currency else "")
        if not code:
            continue
        totals[code] = totals.get(code, Decimal("0")) + qty * Decimal(
            str(stock.average_buy_price or 0)
        )
    return qty_total, {
        code: amount.quantize(TWOPLACES) for code, amount in sorted(totals.items())
    }


class Command(BaseCommand):
    help = (
        "Recalculate destination stock average_buy_price from accepted stock "
        "transfers so Total Stock Value matches transfer Total Amount."
    )

    def add_arguments(self, parser):
        parser.add_argument(
            "--shop",
            type=int,
            help="Only repair destination stocks for this shop id (e.g. Takhar).",
        )
        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")
        dry_run = options.get("dry_run")

        transfers = (
            StockTransfer.objects.filter(
                status=StockTransfer.Status.ACCEPTED,
                target_stock__isnull=False,
                target_stock__is_deleted=False,
            )
            .select_related(
                "target_stock",
                "target_stock__shop",
                "target_stock__purchase_item",
                "currency",
                "customer_branch",
                "supplier_branch",
                "product",
            )
            .order_by("target_stock_id", "id")
        )
        if shop_id:
            transfers = transfers.filter(customer_branch_id=shop_id)

        by_target = defaultdict(list)
        for transfer in transfers:
            by_target[transfer.target_stock_id].append(transfer)

        repaired = 0
        skipped = 0
        touched_shops = set()

        for target_stock_id, rows in by_target.items():
            stock = rows[0].target_stock
            if not stock or int(stock.quantity or 0) <= 0:
                skipped += 1
                continue

            transfer_qty = Decimal("0")
            transfer_value = Decimal("0")
            for transfer in rows:
                qty = Decimal(str(transfer.transfer_quantity or 0))
                price = Decimal(str(transfer.sale_price or 0))
                if qty <= 0:
                    continue
                transfer_qty += qty
                transfer_value += qty * price

            if transfer_qty <= 0:
                skipped += 1
                continue

            stock_qty = Decimal(str(stock.quantity or 0))
            old_avg = Decimal(str(stock.average_buy_price or 0))
            old_value = (stock_qty * old_avg).quantize(TWOPLACES)

            # Rebuild average so transferred qty is valued at transfer Total Amount
            # (qty × sale_price), not the old/wrong average.
            residual_qty = stock_qty - transfer_qty
            if residual_qty <= 0:
                new_avg = (transfer_value / transfer_qty).quantize(
                    TWOPLACES, rounding=ROUND_HALF_UP
                )
            elif old_avg == 0:
                # Residual has no recorded cost; put full transfer amount onto the row.
                new_avg = (transfer_value / stock_qty).quantize(
                    TWOPLACES, rounding=ROUND_HALF_UP
                )
            else:
                residual_value = residual_qty * old_avg
                new_avg = ((residual_value + transfer_value) / stock_qty).quantize(
                    TWOPLACES, rounding=ROUND_HALF_UP
                )

            new_value = (stock_qty * new_avg).quantize(TWOPLACES)
            if new_avg == old_avg:
                skipped += 1
                continue

            currency = normalize_currency_code(
                (stock.currency.title if stock.currency else "")
                or (rows[0].currency.title if rows[0].currency else "")
            )
            product_name = (
                stock.purchase_item.product.name
                if stock.purchase_item and stock.purchase_item.product
                else (rows[0].product.name if rows[0].product else "?")
            )
            self.stdout.write(
                f"Stock #{stock.id} ({stock.shop.name}) {product_name} "
                f"qty={stock_qty} {currency or '?'}: "
                f"avg {old_avg} -> {new_avg} | "
                f"value {old_value} -> {new_value} "
                f"(from {len(rows)} transfer line(s), transfer_qty={transfer_qty}, "
                f"transfer_amount={_money(transfer_value)})"
            )

            touched_shops.add(stock.shop_id)
            if dry_run:
                repaired += 1
                continue

            with transaction.atomic():
                locked = Stock.objects.select_for_update().filter(pk=stock.id).first()
                if not locked:
                    skipped += 1
                    continue
                locked.average_buy_price = new_avg
                locked.save(update_fields=["average_buy_price", "updated_at"])
                purchase_item = locked.purchase_item
                if purchase_item:
                    # Keep buy_price aligned for UI / reports when this row is
                    # transfer-driven (qty fully covered by transfers).
                    if residual_qty <= 0:
                        purchase_item.buy_price = new_avg
                        purchase_item.save(update_fields=["buy_price", "updated_at"])
            repaired += 1

        shops = sorted(touched_shops) if touched_shops else (
            [shop_id] if shop_id else []
        )
        for sid in shops:
            qty_total, values = _shop_stock_value_summary(sid)
            value_text = ", ".join(f"{amount} {code}" for code, amount in values.items()) or "0"
            self.stdout.write(
                self.style.NOTICE(
                    f"Shop {sid} valuation after repair: qty={qty_total} value=[{value_text}]"
                )
            )

        # Also print expected incoming value from accepted transfers for this shop.
        if shop_id:
            expected = defaultdict(lambda: Decimal("0"))
            expected_qty = Decimal("0")
            qs = StockTransfer.objects.filter(
                status=StockTransfer.Status.ACCEPTED,
                customer_branch_id=shop_id,
            ).select_related("currency")
            for transfer in qs:
                qty = Decimal(str(transfer.transfer_quantity or 0))
                price = Decimal(str(transfer.sale_price or 0))
                code = normalize_currency_code(
                    transfer.currency.title if transfer.currency else ""
                ) or "?"
                expected[code] += qty * price
                expected_qty += qty
            expected_text = ", ".join(
                f"{_money(amount)} {code}" for code, amount in sorted(expected.items())
            ) or "0"
            self.stdout.write(
                self.style.NOTICE(
                    f"Shop {shop_id} accepted-transfer totals: "
                    f"qty={expected_qty} amount=[{expected_text}]"
                )
            )

        action = "Would repair" if dry_run else "Repaired"
        self.stdout.write(
            self.style.SUCCESS(
                f"{action} {repaired} destination stock row(s); skipped {skipped}."
            )
        )
