import { Prisma } from '../generated/prisma/client.js';
import prisma from '../config/prisma.js';

interface CreateSpoilageInput {
  purchaseId: bigint;
  quantity: Prisma.Decimal;
  spoilageDate: Date;
  reason?: string;
  remarks?: string;
  createdBy?: bigint;
}

interface ListSpoilagesFilters {
  purchaseId?: bigint;
  vegetableId?: bigint;
  from?: Date;
  to?: Date;
}

export class StockSpoilageService {
  static async createSpoilage(input: CreateSpoilageInput) {
    return prisma.$transaction(async (tx) => {
      // Must use tx to avoid race conditions
      const purchase = await tx.purchase.findFirst({
        where: { id: input.purchaseId, deletedAt: null },
      });
      if (!purchase) {
        throw new Error("Purchase lot not found");
      }

      const isBagOnly = purchase.unit === 'BAG' || purchase.unit === 'BOX';

      if (input.quantity.lte(0)) {
        throw new Error('Quantity must be greater than zero.');
      }

      if (isBagOnly && !input.quantity.isInteger()) {
        throw new Error(
          `Spoilage quantity for ${purchase.unit.toLowerCase()} unit must be a whole number (got ${input.quantity}).`
        );
      }
      
      if (isBagOnly) {
        if (input.quantity.gt(purchase.remainingBags)) {
          throw new Error(
            `Cannot spoil more than remaining stock (${purchase.remainingBags} bags) in this lot.`
          );
        }
      } else {
        if (input.quantity.gt(purchase.remainingQuantity)) {
          throw new Error(
            `Cannot spoil more than remaining stock (${purchase.remainingQuantity} kg) in this lot.`
          );
        }
      }

      let bagsToDeduct = null;
      if (isBagOnly) {
        bagsToDeduct = input.quantity;
      } else {
        if (input.quantity.gte(purchase.remainingQuantity)) {
          bagsToDeduct = purchase.remainingBags;
        } else {
          bagsToDeduct = purchase.weightPerBag
            ? input.quantity.div(purchase.weightPerBag).toDecimalPlaces(3, Prisma.Decimal.ROUND_HALF_UP)
            : null;
        }
      }
            
      const qtyToDeduct = isBagOnly ? new Prisma.Decimal(0) : input.quantity;

      const result = await tx.purchase.updateMany({
        where: { 
          id: input.purchaseId,
          remainingQuantity: { gte: qtyToDeduct },
          ...(bagsToDeduct !== null ? { remainingBags: { gte: bagsToDeduct } } : {})
        },
        data: {
          remainingQuantity: { decrement: qtyToDeduct },
          ...(bagsToDeduct !== null ? { remainingBags: { decrement: bagsToDeduct } } : {}),
        },
      });

      if (result.count === 0) {
        throw new Error('Stock changed concurrently or insufficient stock for spoilage. Please retry.');
      }

      const updatedPurchase = await tx.purchase.findUnique({
        where: { id: input.purchaseId },
        select: { remainingQuantity: true, remainingBags: true, status: true },
      });
      if (updatedPurchase && updatedPurchase.status !== "CLOSED") {
        const hasStock = isBagOnly
          ? updatedPurchase.remainingBags.gt(0)
          : updatedPurchase.remainingQuantity.gt(0);
        const newStatus = hasStock ? "ACTIVE" : "DEPLETED";
        if (updatedPurchase.status !== newStatus) {
          await tx.purchase.update({
            where: { id: input.purchaseId },
            data: { status: newStatus },
          });
        }
      }

      return tx.stockSpoilage.create({
        data: {
          purchaseId: input.purchaseId,
          quantity: input.quantity,
          spoilageDate: input.spoilageDate,
          reason: input.reason ?? null,
          remarks: input.remarks ?? null,
          createdBy: input.createdBy ?? null,
        },
      });
    });
  }

  static async listSpoilages(filters: ListSpoilagesFilters) {
    const whereClause: any = {};

    if (filters.purchaseId) {
      whereClause.purchaseId = filters.purchaseId;
    }

    if (filters.vegetableId) {
      whereClause.purchase = {
        vegetableId: filters.vegetableId,
      };
    }

    if (filters.from || filters.to) {
      whereClause.spoilageDate = {};
      if (filters.from) whereClause.spoilageDate.gte = filters.from;
      if (filters.to) whereClause.spoilageDate.lte = filters.to;
    }

    return prisma.stockSpoilage.findMany({
      where: whereClause,
      include: {
        purchase: {
          include: {
            vegetable: true,
            supplier: true,
          },
        },
      },
      orderBy: {
        spoilageDate: 'desc',
      },
    });
  }
}
