from decimal import Decimal

from django.db import transaction
from django.db.models import DecimalField, Q, Sum, Value
from django.db.models.functions import Coalesce
from rest_framework.exceptions import ValidationError

from api.models.data.packing import PackingRun
from api.models.data.storage_tank import ProductionStorageTransfer, StorageTank


def _decimal(value):
    return Decimal(str(value or 0))


@transaction.atomic
def replace_production_storage_transfers(production, transfers_data):
    """Replace a production run's tank transfers after enforcing both limits."""
    list(production.storage_transfers.select_for_update().all())
    production.storage_transfers.all().delete()

    transfers_data = transfers_data or []
    normalized = []
    tank_ids = []
    total_liters = Decimal('0')
    for transfer in transfers_data:
        tank_id = transfer.get('storage_tank')
        quantity_liters = _decimal(transfer.get('quantity_liters'))
        if not tank_id:
            raise ValidationError({
                'storage_transfers': 'Select a storage tank for every transfer row.'
            })
        if quantity_liters <= 0:
            raise ValidationError({
                'storage_transfers': 'Transfer quantities must be greater than zero.'
            })
        tank_id = int(tank_id)
        if tank_id in tank_ids:
            raise ValidationError({
                'storage_transfers': 'Use each storage tank only once per production run.'
            })
        tank_ids.append(tank_id)
        normalized.append((tank_id, quantity_liters))
        total_liters += quantity_liters

    if len(normalized) > 1:
        raise ValidationError({
            'storage_transfers': 'A production run can be moved to only one storage tank.'
        })

    if total_liters > production.output_quantity_liters:
        raise ValidationError({
            'storage_transfers': 'Transferred liters cannot exceed produced liters.'
        })

    if production.status == production.STATUS_MOVED_TO_STORAGE and not normalized:
        raise ValidationError({
            'storage_transfers': 'Select a storage tank and transfer a positive quantity.'
        })
    if production.status != production.STATUS_MOVED_TO_STORAGE and normalized:
        raise ValidationError({
            'storage_transfers': 'Transfers are only allowed when status is Moved to Storage.'
        })

    tanks_by_id = {
        tank.id: tank
        for tank in StorageTank.objects.select_for_update().filter(
            pk__in=tank_ids,
        ).order_by('id')
    }
    if len(tanks_by_id) != len(tank_ids):
        raise ValidationError({
            'storage_transfers': 'One or more selected storage tanks no longer exist.'
        })

    stored_totals = dict(
        ProductionStorageTransfer.objects.filter(
            storage_tank_id__in=tank_ids,
            deleted_at__isnull=True,
            production__deleted_at__isnull=True,
        ).values('storage_tank_id').annotate(
            total=Coalesce(
                Sum('quantity_liters'),
                Value(Decimal('0')),
                output_field=DecimalField(max_digits=12, decimal_places=4),
            )
        ).values_list('storage_tank_id', 'total')
    )
    packed_totals = dict(
        PackingRun.objects.filter(
            storage_tank_id__in=tank_ids,
            deleted_at__isnull=True,
        ).values('storage_tank_id').annotate(
            total=Coalesce(
                Sum('quantity_liters'),
                Value(Decimal('0')),
                output_field=DecimalField(max_digits=12, decimal_places=4),
            )
        ).values_list('storage_tank_id', 'total')
    )

    for tank_id, quantity_liters in normalized:
        tank = tanks_by_id[tank_id]
        already_stored = max(
            Decimal('0'),
            stored_totals.get(tank_id, Decimal('0'))
            - packed_totals.get(tank_id, Decimal('0')),
        )
        if already_stored + quantity_liters > tank.capacity_liters:
            available = max(
                Decimal('0'), tank.capacity_liters - already_stored)
            raise ValidationError({
                'storage_transfers': (
                    f'{tank.name} has space for {available} liters, '
                    f'but {quantity_liters} liters were requested.'
                )
            })

    ProductionStorageTransfer.objects.bulk_create([
        ProductionStorageTransfer(
            production=production,
            storage_tank_id=tank_id,
            quantity_liters=quantity_liters,
        )
        for tank_id, quantity_liters in normalized
    ])
