from django.db import transaction
from django.db.models import Sum

from .models import ProductBatch, ProductSerial, ProductStock, StockMovement


def _update_global_stock_from_locations(product):
    total = product.location_stocks.aggregate(total=Sum("quantity"))["total"]
    if total is None:
        return
    product.stock_quantity = total
    product.save(update_fields=["stock_quantity"])


def _update_location_stock(product, location, quantity_delta):
    stock, _ = ProductStock.objects.get_or_create(
        product=product,
        location=location,
        defaults={"quantity": 0},
    )
    new_quantity = stock.quantity + quantity_delta
    if new_quantity < 0:
        raise ValueError(f"Not enough stock for {product.name} at {location.name}")
    stock.quantity = new_quantity
    stock.save(update_fields=["quantity", "updated_at"])


@transaction.atomic
def add_stock(
    product,
    quantity,
    cost_price=None,
    reference="",
    note="",
    user=None,
    location=None,
    batch_number="",
    expiry_date=None,
    serial_numbers=None,
    movement_type=StockMovement.MovementType.STOCK_IN,
):
    """
    Add stock and write movement logs with optional location/batch/serial tracking.
    """

    product.stock_quantity += quantity
    if cost_price is not None:
        product.cost_price = cost_price
    product.save(update_fields=["stock_quantity", "cost_price"])

    if location:
        _update_location_stock(product, location, quantity)
        _update_global_stock_from_locations(product)

    batch = None
    if batch_number:
        batch, _ = ProductBatch.objects.get_or_create(
            product=product,
            location=location,
            batch_number=batch_number,
            defaults={
                "quantity": 0,
                "unit_cost": cost_price or product.cost_price,
                "expiry_date": expiry_date,
            },
        )
        batch.quantity += quantity
        if cost_price is not None:
            batch.unit_cost = cost_price
        if expiry_date:
            batch.expiry_date = expiry_date
        batch.save()

    movement = StockMovement.objects.create(
        product=product,
        location=location,
        batch=batch,
        movement_type=movement_type,
        quantity=quantity,
        cost_price=cost_price if cost_price is not None else product.cost_price,
        reference=reference,
        note=note,
        created_by=user,
    )

    if serial_numbers:
        for serial_number in serial_numbers:
            serial, created = ProductSerial.objects.get_or_create(
                serial_number=serial_number,
                defaults={
                    "product": product,
                    "location": location,
                    "batch": batch,
                    "status": ProductSerial.Status.IN_STOCK,
                },
            )
            if not created:
                serial.product = product
                serial.location = location
                serial.batch = batch
                serial.status = ProductSerial.Status.IN_STOCK
                serial.save()
            StockMovement.objects.create(
                product=product,
                location=location,
                batch=batch,
                serial=serial,
                movement_type=movement_type,
                quantity=1,
                cost_price=cost_price if cost_price is not None else product.cost_price,
                reference=reference,
                note=f"{note} | Serial {serial.serial_number}".strip(" |"),
                created_by=user,
            )

    return movement


@transaction.atomic
def deduct_stock(
    product,
    quantity,
    reference="",
    note="",
    user=None,
    location=None,
    batch=None,
    serial_numbers=None,
    movement_type=StockMovement.MovementType.SALE,
):
    """
    Deduct stock with optional location/batch/serial tracking.
    """

    if location:
        _update_location_stock(product, location, -quantity)
    elif product.stock_quantity < quantity:
        raise ValueError(f"Not enough stock for {product.name}")

    product.stock_quantity -= quantity
    if product.stock_quantity < 0:
        raise ValueError(f"Not enough stock for {product.name}")
    product.save(update_fields=["stock_quantity"])

    if location:
        _update_global_stock_from_locations(product)

    if batch:
        if batch.quantity < quantity:
            raise ValueError(f"Not enough stock in batch {batch.batch_number}")
        batch.quantity -= quantity
        batch.save(update_fields=["quantity"])

    movement = StockMovement.objects.create(
        product=product,
        location=location,
        batch=batch,
        movement_type=movement_type,
        quantity=quantity,
        cost_price=product.cost_price,
        reference=reference,
        note=note,
        created_by=user,
    )

    if serial_numbers:
        serials = ProductSerial.objects.filter(
            serial_number__in=serial_numbers,
            product=product,
        )
        found = serials.count()
        if found != len(serial_numbers):
            raise ValueError("One or more serial numbers were not found for this product.")
        for serial in serials:
            serial.status = ProductSerial.Status.SOLD
            serial.sold_date = movement.date
            serial.save(update_fields=["status", "sold_date"])
            StockMovement.objects.create(
                product=product,
                location=location,
                batch=batch,
                serial=serial,
                movement_type=movement_type,
                quantity=1,
                cost_price=product.cost_price,
                reference=reference,
                note=f"{note} | Serial {serial.serial_number}".strip(" |"),
                created_by=user,
            )

    return movement
