"""
Centralised on-hand stock mutation + audit logging.

Every change to an `Inventory.quantity` should flow through `apply_inventory_delta`
(relative change) or `set_inventory_quantity` (absolute set). Both update the
balance AND write an immutable `StockMovement` row, so the stock ledger is a
complete record of how each product's quantity reached its current value.

Logging is best-effort: a ledger failure must never break the stock operation.
"""
from __future__ import annotations

import logging

logger = logging.getLogger(__name__)


def _to_float(value, default=0.0):
    try:
        return float(value)
    except (TypeError, ValueError):
        return default


def record_stock_movement(
    inventory,
    *,
    movement_type,
    quantity_before,
    quantity_after,
    staff=None,
    reference="",
    stock_transaction=None,
    notes="",
):
    """Write one immutable stock-ledger row. Never raises."""
    try:
        from .models import StockMovement

        before = _to_float(quantity_before)
        after = _to_float(quantity_after)
        StockMovement.objects.create(
            inventory=inventory,
            product=getattr(inventory, "product", None),
            warehouse=getattr(inventory, "warehouse", None),
            movement_type=movement_type,
            quantity_before=before,
            quantity_change=after - before,
            quantity_after=after,
            reference=reference or "",
            stock_transaction=stock_transaction,
            notes=(notes or "")[:500],
            created_by=staff,
        )
    except Exception as exc:  # noqa: BLE001 — logging must not break operations
        logger.warning("stock movement log failed: %s", exc)


def apply_inventory_delta(
    inventory,
    delta,
    *,
    movement_type,
    staff=None,
    reference="",
    stock_transaction=None,
    notes="",
    clamp_zero=False,
):
    """Apply a relative change to inventory on-hand, persist it, and log it.

    Returns the new on-hand quantity (float).
    """
    before = _to_float(inventory.quantity)
    after = before + _to_float(delta)
    if clamp_zero and after < 0:
        after = 0.0
    inventory.quantity = f"{after}"
    if staff is not None:
        inventory.last_updated_by = staff
    inventory.save()
    record_stock_movement(
        inventory,
        movement_type=movement_type,
        quantity_before=before,
        quantity_after=after,
        staff=staff,
        reference=reference,
        stock_transaction=stock_transaction,
        notes=notes,
    )
    return after


def log_inventory_set(
    inventory,
    quantity_before,
    *,
    movement_type="adjustment",
    staff=None,
    reference="",
    notes="",
):
    """Log a movement when an inventory quantity was set to an absolute value
    (e.g. a manual correction done through a serializer). Pass the value the row
    held BEFORE the save; the current `inventory.quantity` is the new value.
    No-op when the value did not actually change."""
    before = _to_float(quantity_before)
    after = _to_float(inventory.quantity)
    if before == after:
        return
    record_stock_movement(
        inventory,
        movement_type=movement_type,
        quantity_before=before,
        quantity_after=after,
        staff=staff,
        reference=reference,
        notes=notes,
    )
