from decimal import Decimal

from api.models.data.raw_materials import RawMaterialStock

FOURPLACES = Decimal('0.0001')
SIXPLACES = Decimal('0.000001')


def _q2(value):
    return Decimal(str(value or 0)).quantize(FOURPLACES)


def _q6(value):
    return Decimal(str(value or 0)).quantize(SIXPLACES)


def reconcile_raw_material_stock(purchase, *, previous_status, previous_items, save_stock):
    """Apply the difference between a purchase's previous and current stock effect."""
    previous_quantities = {}
    previous_piece_counts = {}
    if previous_status == 'received':
        for raw_material_id, quantity, piece_count in previous_items:
            previous_quantities[raw_material_id] = (
                previous_quantities.get(raw_material_id, Decimal('0'))
                + Decimal(str(quantity or 0))
            )
            if Decimal(str(quantity or 0)) > 0:
                previous_piece_counts[raw_material_id] = (
                    previous_piece_counts.get(raw_material_id, 0)
                    + int(piece_count or 0)
                )

    current_quantities = {}
    current_piece_counts = {}
    if purchase.status == 'received':
        for item in purchase.items.all():
            current_quantities[item.raw_material_id] = (
                current_quantities.get(item.raw_material_id, Decimal('0'))
                + Decimal(str(item.quantity or 0))
            )
            if Decimal(str(item.quantity or 0)) > 0:
                current_piece_counts[item.raw_material_id] = (
                    current_piece_counts.get(item.raw_material_id, 0)
                    + int(item.piece_count or 0)
                )

    for raw_material_id in previous_quantities.keys() | current_quantities.keys():
        stock, _ = RawMaterialStock.objects.get_or_create(
            raw_material_id=raw_material_id,
            defaults={'quantity': Decimal(
                '0'), 'last_purchase_price': Decimal('0')},
        )
        stock.quantity = _q2(
            stock.quantity
            + current_quantities.get(raw_material_id, Decimal('0'))
            - previous_quantities.get(raw_material_id, Decimal('0'))
        )
        stock.piece_count = max(
            0,
            stock.piece_count
            + current_piece_counts.get(raw_material_id, 0)
            - previous_piece_counts.get(raw_material_id, 0),
        )

        latest_item = purchase.items.model.objects.filter(
            raw_material_id=raw_material_id,
            purchase__status='received',
            purchase__deleted_at__isnull=True,
            quantity__gt=0,
        ).select_related('purchase').order_by(
            '-purchase__purchase_date', '-id'
        ).first()
        if latest_item:
            stock.last_purchase_price = _q6(latest_item.price_per_unit_base)
            stock.last_purchase_date = latest_item.purchase.purchase_date.date()
        else:
            stock.last_purchase_price = Decimal('0')
            stock.last_purchase_date = None

        if save_stock:
            stock.save()


def update_raw_material_stock_on_purchase(item, purchase, *, save_stock):
    """Update raw material stock when a purchase is received."""
    if purchase.status != 'received':
        return

    stock, created = RawMaterialStock.objects.get_or_create(
        raw_material=item.raw_material,
        defaults={'quantity': Decimal(
            '0'), 'last_purchase_price': Decimal('0')}
    )

    # Add quantity to stock
    stock.quantity = _q2(
        stock.quantity + Decimal(str(item.quantity or 0)))
    stock.piece_count += int(item.piece_count or 0)
    stock.last_purchase_price = _q6(item.price_per_unit_base)
    stock.last_purchase_date = purchase.purchase_date.date()

    if save_stock:
        stock.save()


def revert_raw_material_stock_from_purchase(item, purchase, *, save_stock):
    """Revert raw material stock when a purchase is cancelled."""
    stock, created = RawMaterialStock.objects.get_or_create(
        raw_material=item.raw_material,
        defaults={'quantity': Decimal(
            '0'), 'last_purchase_price': Decimal('0')}
    )

    # Subtract quantity from stock
    stock.quantity = _q2(
        stock.quantity - Decimal(str(item.quantity or 0)))
    stock.piece_count = max(0, stock.piece_count - int(item.piece_count or 0))
    if stock.quantity < 0:
        stock.quantity = Decimal('0')

    if save_stock:
        stock.save()


def update_raw_material_stock_on_item_change(
    item, purchase, *, old_quantity, old_piece_count=0, save_stock
):
    """Update raw material stock when a purchase item quantity changes."""
    if purchase.status != 'received':
        return

    stock, created = RawMaterialStock.objects.get_or_create(
        raw_material=item.raw_material,
        defaults={'quantity': Decimal(
            '0'), 'last_purchase_price': Decimal('0')}
    )

    # Calculate difference and update
    old_qty = Decimal(str(old_quantity or 0))
    new_qty = Decimal(str(item.quantity or 0))
    difference = new_qty - old_qty

    stock.quantity = _q2(stock.quantity + difference)
    stock.piece_count = max(
        0,
        stock.piece_count + int(item.piece_count or 0) -
        int(old_piece_count or 0),
    )
    stock.last_purchase_price = _q6(item.price_per_unit_base)
    stock.last_purchase_date = purchase.purchase_date.date()

    if save_stock:
        stock.save()
