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

interface CreateSaleReturnInput {
  quantity: Prisma.Decimal;
  returnDate: Date;
  reason?: string;
  remarks?: string;
  createdBy?: bigint;
}

interface ListSaleReturnsFilters {
  purchaseId?: bigint;
  customerId?: bigint;
  vegetableId?: bigint;
  from?: Date;
  to?: Date;
  isWalkIn?: boolean;
}

export class SaleReturnService {
  static async createSaleReturn(saleItemId: bigint, input: CreateSaleReturnInput) {
    return prisma.$transaction(async (tx) => {
      const saleItem = await tx.saleItem.findUnique({
        where: { id: saleItemId },
        include: {
          sale: true,
          purchase: { include: { arrivalSettlement: true } },
          saleReturns: true,
        },
      });
      if (!saleItem) {
        throw new Error("Sale item not found");
      }

      // Block returns for items sold from Arrival Lot History when the settlement is finalized
      if (
        saleItem.purchase.purchaseType === "COMMISSION_ARRIVAL" &&
        saleItem.purchase.arrivalSettlement?.status === "SETTLED"
      ) {
        throw new Error(
          "Cannot return this item — the Arrival Lot settlement is already settled."
        );
      }

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

      const alreadyReturned = saleItem.saleReturns.reduce(
        (acc, ret) => acc.add(ret.quantity),
        new Prisma.Decimal(0)
      );
      const isBagOnly = saleItem.saleUnit !== 'KG' && saleItem.quantity.lte(0);

      if (isBagOnly && !input.quantity.isInteger()) {
        throw new Error('Return quantity for bags/boxes must be a whole number.');
      }

      const maxReturnable = isBagOnly ? saleItem.bags!.sub(alreadyReturned) : saleItem.quantity.sub(alreadyReturned);

      if (input.quantity.gt(maxReturnable)) {
        throw new Error(
          `Cannot return more than ${maxReturnable} ${isBagOnly ? 'bags' : 'kg'} for this sale item.`
        );
      }

      const originalPerUnitRate = new Prisma.Decimal(saleItem.total)
        .div(isBagOnly ? saleItem.bags! : saleItem.quantity)
        .toDecimalPlaces(6, Prisma.Decimal.ROUND_HALF_UP);
      const returnAmount = input.quantity
        .mul(originalPerUnitRate)
        .toDecimalPlaces(2, Prisma.Decimal.ROUND_HALF_UP);

      const weightPerBag = saleItem.purchase.weightPerBag && saleItem.purchase.weightPerBag.gt(0)
        ? saleItem.purchase.weightPerBag
        : null;

      let kgToRestore: Prisma.Decimal = new Prisma.Decimal(0);
      let bagsToRestore: Prisma.Decimal | null = null;

      if (saleItem.saleUnit === "THAAR" || saleItem.purchase?.unit === "THAAR") {
        bagsToRestore = input.quantity;
        kgToRestore = new Prisma.Decimal(0);
      } else if (isBagOnly) {
        bagsToRestore = input.quantity;
        if (weightPerBag) {
          kgToRestore = input.quantity.mul(weightPerBag).toDecimalPlaces(3, Prisma.Decimal.ROUND_HALF_UP);
        }
      } else {
        kgToRestore = input.quantity;
        if (weightPerBag) {
          bagsToRestore = input.quantity.div(weightPerBag).toDecimalPlaces(3, Prisma.Decimal.ROUND_HALF_UP);
        } else if (saleItem.bags && saleItem.quantity.gt(0)) {
          bagsToRestore = input.quantity.mul(saleItem.bags).div(saleItem.quantity).toDecimalPlaces(3, Prisma.Decimal.ROUND_HALF_UP);
        }
      }

      await tx.purchase.update({
        where: { id: saleItem.purchaseId },
        data: {
          remainingQuantity: { increment: kgToRestore },
          ...(bagsToRestore !== null ? { remainingBags: { increment: bagsToRestore } } : {}),
        },
      });

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

      // Do NOT touch grandTotal in the DB — enrichSaleWithReturns in SaleService
      // recomputes the net grand total from the original stored value minus all
      // return amounts, so writing grandTotal here would cause double-deduction.
      const newBalance = saleItem.sale.balanceAmount
        .sub(returnAmount)
        .toDecimalPlaces(2, Prisma.Decimal.ROUND_HALF_UP);
      const clampedBalance = newBalance.lt(0) ? new Prisma.Decimal(0) : newBalance;

      const newStatus = clampedBalance.lte(0)
        ? 'PAID'
        : saleItem.sale.receivedAmount.gt(0)
        ? 'PARTIALLY_PAID'
        : 'UNPAID';

      await tx.sale.update({
        where: { id: saleItem.saleId },
        data: {
          balanceAmount: clampedBalance,
          paymentStatus: newStatus,
        },
      });

      if (saleItem.sale.customerId) {
        await tx.customer.update({
          where: { id: saleItem.sale.customerId },
          data: { outstandingBalance: { decrement: returnAmount } },
        });
      }

      return tx.saleReturn.create({
        data: {
          saleItemId,
          purchaseId: saleItem.purchaseId,
          quantity: input.quantity,
          returnAmount,
          returnDate: input.returnDate,
          reason: input.reason ?? null,
          remarks: input.remarks ?? null,
          createdBy: input.createdBy ?? null,
        },
      });
    });
  }

  static async listSaleReturns(filters: ListSaleReturnsFilters) {
    const whereClause: any = {};

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

    if (filters.isWalkIn) {
      whereClause.saleItem = {
        sale: {
          customerId: null,
        },
      };
    } else if (filters.customerId) {
      whereClause.saleItem = {
        sale: {
          customerId: filters.customerId,
        },
      };
    }

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

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

    return prisma.saleReturn.findMany({
      where: whereClause,
      include: {
        saleItem: {
          include: {
            sale: {
              include: {
                customer: true,
              },
            },
          },
        },
        purchase: {
          include: {
            vegetable: true,
          },
        },
      },
      orderBy: {
        returnDate: 'desc',
      },
    });
  }
}
