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.utils.dateparse import parse_date

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 = (
        "Fix source-branch (e.g. Kabul) Total Stock Value after accepted transfers "
        "so the value drop matches transfer Total Amount (qty × sale_price). "
        "Use --date to limit to one day. Use --undo to reverse a previous repair."
    )

    def add_arguments(self, parser):
        parser.add_argument(
            "--shop",
            type=int,
            required=True,
            help="Source branch shop id to repair (Kabul Main Branch).",
        )
        parser.add_argument(
            "--date",
            type=str,
            help="Only include accepted transfers on this date (YYYY-MM-DD).",
        )
        parser.add_argument(
            "--undo",
            action="store_true",
            help="Restore source averages as if transfer amounts were never applied.",
        )
        parser.add_argument(
            "--dry-run",
            action="store_true",
            help="Show what would change without writing.",
        )

    def handle(self, *args, **options):
        shop_id = options["shop"]
        dry_run = options.get("dry_run")
        undo = options.get("undo")
        date_str = options.get("date")
        transfer_date = parse_date(date_str) if date_str else None
        if date_str and not transfer_date:
            self.stderr.write(self.style.ERROR(f"Invalid --date value: {date_str}"))
            return

        before_qty, before_values = _shop_stock_value_summary(shop_id)
        before_text = ", ".join(
            f"{amount} {code}" for code, amount in before_values.items()
        ) or "0"
        self.stdout.write(
            self.style.NOTICE(
                f"Shop {shop_id} BEFORE: qty={before_qty} value=[{before_text}]"
            )
        )

        transfers = StockTransfer.objects.filter(
            status=StockTransfer.Status.ACCEPTED,
            supplier_branch_id=shop_id,
            source_stock__isnull=False,
            source_stock__is_deleted=False,
        ).select_related(
            "source_stock",
            "source_stock__shop",
            "source_stock__purchase_item",
            "source_stock__purchase_item__product",
            "currency",
            "product",
        )
        if transfer_date:
            transfers = transfers.filter(transfer_date=transfer_date)
            self.stdout.write(self.style.NOTICE(f"Filtering transfers on {transfer_date}"))
        if undo:
            self.stdout.write(self.style.WARNING("UNDO mode: restoring pre-repair averages"))

        transfers = transfers.order_by("source_stock_id", "id")

        by_source = defaultdict(list)
        for transfer in transfers:
            by_source[transfer.source_stock_id].append(transfer)

        repaired = 0
        skipped = 0
        total_amount_by_currency = defaultdict(lambda: Decimal("0"))

        for source_stock_id, rows in by_source.items():
            stock = rows[0].source_stock
            if not stock:
                skipped += 1
                continue

            transferred_qty = Decimal("0")
            transfer_amount = 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
                transferred_qty += qty
                transfer_amount += qty * price
                code = normalize_currency_code(
                    transfer.currency.title if transfer.currency else (
                        stock.currency.title if stock.currency else ""
                    )
                ) or "?"
                total_amount_by_currency[code] += qty * price

            if transferred_qty <= 0:
                skipped += 1
                continue

            remaining_qty = Decimal(str(stock.quantity or 0))
            old_avg = Decimal(str(stock.average_buy_price or 0))
            pre_qty = remaining_qty + transferred_qty

            if undo:
                # Reverse: remaining_value + transfer_amount = pre_qty * original_avg
                restored_value = remaining_qty * old_avg + transfer_amount
                if pre_qty > 0:
                    new_avg = (restored_value / pre_qty).quantize(
                        TWOPLACES, rounding=ROUND_HALF_UP
                    )
                else:
                    new_avg = Decimal("0.00")
            else:
                pre_value = pre_qty * old_avg
                remaining_value = pre_value - transfer_amount
                if remaining_value < 0:
                    remaining_value = Decimal("0")
                if remaining_qty > 0:
                    new_avg = (remaining_value / remaining_qty).quantize(
                        TWOPLACES, rounding=ROUND_HALF_UP
                    )
                else:
                    new_avg = Decimal("0.00")

            old_value = (remaining_qty * old_avg).quantize(TWOPLACES)
            new_value = (remaining_qty * new_avg).quantize(TWOPLACES)
            if new_avg == old_avg:
                skipped += 1
                continue

            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 "?")
            )
            currency = normalize_currency_code(
                stock.currency.title if stock.currency else ""
            ) or "?"
            self.stdout.write(
                f"Stock #{stock.id} {product_name} {currency}: "
                f"qty={remaining_qty} avg {old_avg} -> {new_avg} | "
                f"value {old_value} -> {new_value} "
                f"(transfers qty={transferred_qty} amount={_money(transfer_amount)})"
            )

            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"])
            repaired += 1

        if not dry_run:
            after_qty, after_values = _shop_stock_value_summary(shop_id)
            after_text = ", ".join(
                f"{amount} {code}" for code, amount in after_values.items()
            ) or "0"
            self.stdout.write(
                self.style.NOTICE(
                    f"Shop {shop_id} AFTER: qty={after_qty} value=[{after_text}]"
                )
            )

            deltas = []
            codes = sorted(set(before_values) | set(after_values))
            for code in codes:
                delta = _money(after_values.get(code, 0) - before_values.get(code, 0))
                if delta:
                    sign = "+" if delta > 0 else ""
                    deltas.append(f"{sign}{delta} {code}")
            if deltas:
                self.stdout.write(
                    self.style.NOTICE("Value change: " + ", ".join(deltas))
                )

        expected_text = ", ".join(
            f"{_money(amount)} {code}"
            for code, amount in sorted(total_amount_by_currency.items())
        ) or "0"
        self.stdout.write(
            self.style.NOTICE(
                f"Shop {shop_id} selected outgoing transfer totals: [{expected_text}]"
            )
        )

        action = "Would repair" if dry_run else ("Would undo" if undo and dry_run else ("Undid" if undo else "Repaired"))
        if dry_run and undo:
            action = "Would undo"
        elif dry_run:
            action = "Would repair"
        elif undo:
            action = "Undid"
        else:
            action = "Repaired"
        self.stdout.write(
            self.style.SUCCESS(
                f"{action} {repaired} source stock row(s); skipped {skipped}."
            )
        )
