import prisma from "../config/prisma.js";
import { Prisma } from "../generated/prisma/client.js";
import { ArrivalRepository } from "../repositories/ArrivalRepository.js";
import { ArrivalSettlementRepository } from "../repositories/ArrivalSettlementRepository.js";

export class ArrivalSettlementService {
  constructor(
    private readonly arrivalRepository = new ArrivalRepository(),
    private readonly settlementRepository = new ArrivalSettlementRepository()
  ) { }

  private generateSettlementNoCandidate(): string {
    const today = new Date();
    const datePart = today.toISOString().slice(0, 10).replace(/-/g, "");
    const rand = Math.floor(1000 + Math.random() * 9000);
    return `PATTI-${datePart}-${rand}`;
  }

  async calculateSettlementPreview(purchaseId: bigint, customCommissionRate: number = 4.0, allowUnsold: boolean = false) {
    const arrival = await this.arrivalRepository.findById(purchaseId);
    if (!arrival) {
      throw new Error("Arrival lot not found.");
    }

    const salesSummary = await this.arrivalRepository.getArrivalSalesSummary(purchaseId);

    const commissionPercent = new Prisma.Decimal(customCommissionRate);
    const grossSalesTotal = salesSummary.grossSalesTotal;
    const commissionAmount = grossSalesTotal
      .mul(commissionPercent)
      .div(100)
      .toDecimalPlaces(2, Prisma.Decimal.ROUND_HALF_UP);

    const freightDeducted = arrival.freight;
    const extraFreightDeducted = arrival.extraFreight;
    const associationDeducted = arrival.associationFee;
    const unloadingDeducted = arrival.unloadingFee;
    const miscDeducted = arrival.miscellaneousFee;

    const totalDeductions = freightDeducted
      .plus(extraFreightDeducted)
      .plus(associationDeducted)
      .plus(unloadingDeducted)
      .plus(miscDeducted);

    const advanceAdjusted = arrival.advanceAmount;

    const netPayable = grossSalesTotal
      .minus(commissionAmount)
      .minus(totalDeductions)
      .minus(advanceAdjusted)
      .toDecimalPlaces(2, Prisma.Decimal.ROUND_HALF_UP);

    const totalRemainingQty = arrival.remainingQuantity.plus(salesSummary.retailRemainingQty);
    const totalRemainingBags = (arrival.remainingBags ?? new Prisma.Decimal(0)).plus(salesSummary.retailRemainingBags);

    if (!allowUnsold && totalRemainingQty.gt(0) && !arrival.arrivalSettlement) {
      throw new Error("Cannot generate settlement: Sales are not fully completed (items remaining in stock).");
    }

    return {
      arrival,
      salesSummary,
      calculations: {
        totalWeightReceived: arrival.quantity,
        totalWeightSold: salesSummary.totalWeightSold,
        remainingQuantity: totalRemainingQty,
        wholesaleRemainingQuantity: arrival.remainingQuantity,
        retailRemainingQuantity: salesSummary.retailRemainingQty,
        retailAllocatedQuantity: salesSummary.retailAllocatedQty,
        totalBagsReceived: arrival.bags,
        totalBagsSold: salesSummary.totalBagsSold,
        remainingBags: totalRemainingBags,
        grossSalesTotal,
        wholesaleGrossTotal: salesSummary.wholesaleGrossTotal,
        retailGrossTotal: salesSummary.retailGrossTotal,
        wholesaleWeightSold: salesSummary.wholesaleWeightSold,
        retailWeightSold: salesSummary.retailWeightSold,
        commissionPercent,
        commissionAmount,
        freightDeducted,
        extraFreightDeducted,
        associationDeducted,
        unloadingDeducted,
        miscDeducted,
        totalDeductions,
        advanceAdjusted,
        netPayable,
      },
    };
  }

  async createSettlement(data: {
    purchaseId: bigint;
    settlementDate?: Date;
    commissionPercent?: number;
    paidAmount?: number;
    paymentMode?: "CASH" | "BANK" | "UPI";
    remarks?: string;
  }, userId?: bigint) {
    const { purchaseId } = data;
    const arrival = await this.arrivalRepository.findById(purchaseId);
    if (!arrival) {
      throw new Error("Arrival lot not found.");
    }

    const preview = await this.calculateSettlementPreview(purchaseId, data.commissionPercent ?? 4.0);
    const { calculations } = preview;

    const existingSettlement = arrival.arrivalSettlement;
    if (existingSettlement) {
      throw new Error(
        `Patti settlement (${existingSettlement.settlementNo}) has already been generated and is locked against updates.`
      );
    }

    const paidAmount = new Prisma.Decimal(Math.max(0, Number(data.paidAmount || 0)));
    const balanceAmount = calculations.netPayable.minus(paidAmount);
    const settlementDate = data.settlementDate || new Date();

    let settlementStatus: "UNSETTLED" | "PARTIALLY_SETTLED" | "SETTLED" = "UNSETTLED";
    if (calculations.netPayable.lte(0)) {
      // Net payable is zero or negative (deductions >= gross sales) — nothing owed to supplier
      settlementStatus = "SETTLED";
    } else if (balanceAmount.lte(0) && paidAmount.gt(0)) {
      settlementStatus = "SETTLED";
    } else if (paidAmount.gt(0)) {
      settlementStatus = "PARTIALLY_SETTLED";
    }

    return await prisma.$transaction(async (tx) => {
      let settlement;
      let settlementNo: string;

      /*
      // OLD CODE (Update existing settlement) - Commented out as Patti is locked once generated:
      if (existingSettlement) {
        settlementNo = existingSettlement.settlementNo;
        settlement = await this.settlementRepository.update(
          existingSettlement.id,
          {
            settlementDate,
            totalWeightSold: calculations.totalWeightSold,
            grossSalesTotal: calculations.grossSalesTotal,
            commissionPercent: calculations.commissionPercent,
            commissionAmount: calculations.commissionAmount,
            freightDeducted: calculations.freightDeducted,
            extraFreightDeducted: calculations.extraFreightDeducted,
            associationDeducted: calculations.associationDeducted,
            unloadingDeducted: calculations.unloadingDeducted,
            miscDeducted: calculations.miscDeducted,
            totalDeductions: calculations.totalDeductions,
            advanceAdjusted: calculations.advanceAdjusted,
            netPayable: calculations.netPayable,
            paidAmount,
            balanceAmount,
            status: settlementStatus,
            paymentMode: data.paymentMode || existingSettlement.paymentMode || null,
            remarks: data.remarks || existingSettlement.remarks || null,
          },
          tx
        );
      } else {
      */

      do {
        settlementNo = this.generateSettlementNoCandidate();
        const existing = await tx.arrivalSettlement.findUnique({ where: { settlementNo } });
        if (!existing) break;
      } while (true);

      settlement = await this.settlementRepository.create(
        {
          purchaseId,
          settlementNo,
            settlementDate,
            totalWeightSold: calculations.totalWeightSold,
            grossSalesTotal: calculations.grossSalesTotal,
            commissionPercent: calculations.commissionPercent,
            commissionAmount: calculations.commissionAmount,
            freightDeducted: calculations.freightDeducted,
            extraFreightDeducted: calculations.extraFreightDeducted,
            associationDeducted: calculations.associationDeducted,
            unloadingDeducted: calculations.unloadingDeducted,
            miscDeducted: calculations.miscDeducted,
            totalDeductions: calculations.totalDeductions,
            advanceAdjusted: calculations.advanceAdjusted,
            netPayable: calculations.netPayable,
            paidAmount,
            balanceAmount,
            status: settlementStatus,
            paymentMode: data.paymentMode || null,
            remarks: data.remarks || null,
            createdBy: userId || null,
          },
          tx
        );
        // } // End of old else branch

      // Update purchase status based on remaining physical stock:
      // If stock remains (> 0), keep ACTIVE so remaining stock can be sold/allocated.
      // If depleted (<= 0), set to DEPLETED.
      const combinedRemaining = arrival.remainingQuantity.plus(preview.salesSummary.retailRemainingQty);
      await tx.purchase.update({
        where: { id: purchaseId },
        data: {
          status: combinedRemaining.lte(0) ? "DEPLETED" : "ACTIVE",
          ratePerUnit: arrival.quantity.gt(0)
            ? calculations.netPayable.div(arrival.quantity).toDecimalPlaces(2, Prisma.Decimal.ROUND_HALF_UP)
            : new Prisma.Decimal(0),
          goodsTotal: calculations.netPayable,
          grandTotal: calculations.netPayable.plus(calculations.totalDeductions),
        },
      });

      // Sync supplier payment record for this settlement (type ARRIVAL_LOT)
      if (arrival.supplierId) {
        const existingPayments = await tx.supplierPayment.findMany({
          where: {
            referenceNo: settlementNo,
            type: "ARRIVAL_LOT",
          },
          orderBy: { id: "asc" },
        });

        if (paidAmount.gt(0)) {
          if (existingPayments.length > 0) {
            const primaryPayment = existingPayments[0]!;
            const extraDuplicates = existingPayments.slice(1);
            await tx.supplierPayment.update({
              where: { id: primaryPayment.id },
              data: {
                supplierId: arrival.supplierId,
                paymentDate: settlementDate,
                amount: paidAmount,
                paymentMode: data.paymentMode || "CASH",
                referenceNo: settlementNo,
                remarks: data.remarks || `Handover for P.Patti Settlement ${settlementNo} (Lot ${arrival.lotNo})`,
                deletedAt: null,
              },
            });

            // Remove any fragmented/duplicate incremental payments from prior updates
            if (extraDuplicates.length > 0) {
              await tx.supplierPayment.deleteMany({
                where: {
                  id: { in: extraDuplicates.map((p) => p.id) },
                },
              });
            }
          } else {
            // Create a single supplier payment record
            await tx.supplierPayment.create({
              data: {
                supplierId: arrival.supplierId,
                paymentDate: settlementDate,
                amount: paidAmount,
                paymentMode: data.paymentMode || "CASH",
                referenceNo: settlementNo,
                remarks: data.remarks || `Handover for P.Patti Settlement ${settlementNo} (Lot ${arrival.lotNo})`,
                type: "ARRIVAL_LOT",
                createdBy: userId || null,
              },
            });
          }
        } else if (existingPayments.length > 0) {
          // If paidAmount is 0, remove any existing payments for this settlement
          await tx.supplierPayment.deleteMany({
            where: {
              referenceNo: settlementNo,
              type: "ARRIVAL_LOT",
            },
          });
        }
      }

      return settlement;
    });
  }

  async getAllSettlements(query: {
    page?: number;
    limit?: number;
    search?: string;
    dateFrom?: string;
    dateTo?: string;
    status?: "UNSETTLED" | "PARTIALLY_SETTLED" | "SETTLED";
  }) {
    const page = Number(query.page) || 1;
    const limit = Number(query.limit) || 20;
    const skip = (page - 1) * limit;

    const dateFrom = query.dateFrom ? new Date(query.dateFrom) : undefined;
    const dateTo = query.dateTo ? new Date(query.dateTo) : undefined;

    const { items, total } = await this.settlementRepository.findAll(
      skip,
      limit,
      query.search,
      dateFrom,
      dateTo,
      query.status
    );

    return {
      data: items,
      meta: {
        page,
        limit,
        total,
        totalPages: Math.ceil(total / limit),
      },
    };
  }

  async getSettlementById(id: bigint) {
    const settlement = await this.settlementRepository.findById(id);
    if (!settlement) {
      throw new Error("Settlement record not found.");
    }
    return settlement;
  }

  // ─── Custom / Modified Patti Methods (For sharing without altering original records) ───

  async getCustomPatti(purchaseId: bigint) {
    const arrival = await this.arrivalRepository.findById(purchaseId);
    if (!arrival) {
      throw new Error("Arrival lot not found.");
    }

    const commRate =
      arrival.arrivalSettlement?.commissionPercent != null
        ? Number(arrival.arrivalSettlement.commissionPercent)
        : 4.0;

    const preview = await this.calculateSettlementPreview(purchaseId, commRate, true);

    const existing = await prisma.arrivalCustomPatti.findUnique({
      where: { purchaseId },
      include: {
        purchase: {
          include: {
            supplier: true,
            vegetable: true,
          },
        },
      },
    });

    const formattedSaleItems = (preview.salesSummary?.saleItems || []).map((item: any, idx: number) => {
      const invDate = item.invoiceDate || item.sale?.invoiceDate;
      return {
        id: item.id || `item-${idx + 1}`,
        date: invDate
          ? new Date(invDate).toISOString().slice(0, 10)
          : new Date().toISOString().slice(0, 10),
        invoiceNo: item.invoiceNo || item.sale?.invoiceNo || "-",
        channel: item.channel === "RETAIL" ? "Retail" : "Wholesale",
        buyer:
          item.buyerName ||
          item.sale?.customer?.name ||
          item.sale?.walkInName ||
          (item.channel === "RETAIL" ? "Retail Walk-in" : "Buyer"),
        quantity: Number(item.netQuantity ?? item.quantity ?? 0),
        rate: Number(item.sellingRate ?? 0),
        total: Number(item.netTotal ?? item.total ?? 0),
      };
    });

    const calc = preview.calculations;

    const originalData = {
      purchaseId: purchaseId.toString(),
      pattiNo: arrival.arrivalSettlement?.settlementNo || `PATTI-${arrival.lotNo}`,
      settlementDate: arrival.arrivalSettlement?.settlementDate || arrival.arrivalDate || new Date(),
      items: formattedSaleItems,
      totalWeightSold: Number(calc?.totalWeightSold || 0),
      grossSalesTotal: Number(calc?.grossSalesTotal || 0),
      commissionPercent: Number(calc?.commissionPercent || commRate),
      commissionAmount: Number(calc?.commissionAmount || 0),
      freightDeducted: Number(calc?.freightDeducted || 0),
      extraFreightDeducted: Number(calc?.extraFreightDeducted || 0),
      associationDeducted: Number(calc?.associationDeducted || 0),
      unloadingDeducted: Number(calc?.unloadingDeducted || 0),
      miscDeducted: Number(calc?.miscDeducted || 0),
      totalDeductions: Number(calc?.totalDeductions || 0),
      advanceAdjusted: Number(calc?.advanceAdjusted || 0),
      netPayable: Number(calc?.netPayable || 0),
      remarks: arrival.arrivalSettlement?.remarks || "",
      arrival: preview.arrival,
      purchase: preview.arrival,
    };

    return {
      isCustomSaved: Boolean(existing),
      originalData,
      savedCustomData: existing || null,
      data: originalData,
      preview,
    };
  }

  async saveCustomPatti(
    data: {
      purchaseId: bigint;
      pattiNo?: string | null | undefined;
      settlementDate?: Date | null | undefined;
      items: any[];
      commissionPercent?: number | undefined;
      freightDeducted?: number | undefined;
      extraFreightDeducted?: number | undefined;
      associationDeducted?: number | undefined;
      unloadingDeducted?: number | undefined;
      miscDeducted?: number | undefined;
      advanceAdjusted?: number | undefined;
      remarks?: string | null | undefined;
    },
    userId?: bigint
  ) {
    const purchase = await this.arrivalRepository.findById(data.purchaseId);
    if (!purchase) {
      throw new Error("Arrival lot not found.");
    }

    let grossSalesTotal = 0;
    let totalWeightSold = 0;
    const sanitizedItems = (data.items || []).map((item: any, idx: number) => {
      const qty = Number(item.quantity || 0);
      const rate = Number(item.rate ?? item.sellingRate ?? 0);
      const total = Number(item.total !== undefined ? item.total : (qty * rate));
      grossSalesTotal += total;
      totalWeightSold += qty;
      return {
        id: item.id || `custom-${idx + 1}`,
        date: item.date || new Date().toISOString(),
        invoiceNo: item.invoiceNo || "-",
        channel: item.channel || "Wholesale",
        buyer: item.buyer || "Buyer",
        quantity: qty,
        rate,
        total,
      };
    });

    const commissionPercent = Number(data.commissionPercent ?? 4.0);
    const commissionAmount = Number(((grossSalesTotal * commissionPercent) / 100).toFixed(2));
    const freightDeducted = Number(data.freightDeducted || 0);
    const extraFreightDeducted = Number(data.extraFreightDeducted || 0);
    const associationDeducted = Number(data.associationDeducted || 0);
    const unloadingDeducted = Number(data.unloadingDeducted || 0);
    const miscDeducted = Number(data.miscDeducted || 0);
    const advanceAdjusted = Number(data.advanceAdjusted || 0);

    const totalDeductions = Number(
      (commissionAmount + freightDeducted + extraFreightDeducted + associationDeducted + unloadingDeducted + miscDeducted).toFixed(2)
    );
    const netPayable = Number((grossSalesTotal - totalDeductions - advanceAdjusted).toFixed(2));

    const pattiNo = data.pattiNo || purchase.arrivalSettlement?.settlementNo || `CUSTOM-PATTI-${purchase.lotNo}`;
    const settlementDate = data.settlementDate || new Date();

    const saved = await prisma.arrivalCustomPatti.upsert({
      where: { purchaseId: data.purchaseId },
      create: {
        purchaseId: data.purchaseId,
        pattiNo,
        settlementDate,
        items: sanitizedItems,
        totalWeightSold: new Prisma.Decimal(totalWeightSold),
        grossSalesTotal: new Prisma.Decimal(grossSalesTotal),
        commissionPercent: new Prisma.Decimal(commissionPercent),
        commissionAmount: new Prisma.Decimal(commissionAmount),
        freightDeducted: new Prisma.Decimal(freightDeducted),
        extraFreightDeducted: new Prisma.Decimal(extraFreightDeducted),
        associationDeducted: new Prisma.Decimal(associationDeducted),
        unloadingDeducted: new Prisma.Decimal(unloadingDeducted),
        miscDeducted: new Prisma.Decimal(miscDeducted),
        totalDeductions: new Prisma.Decimal(totalDeductions),
        advanceAdjusted: new Prisma.Decimal(advanceAdjusted),
        netPayable: new Prisma.Decimal(netPayable),
        remarks: data.remarks || null,
        createdBy: userId || null,
      },
      update: {
        pattiNo,
        settlementDate,
        items: sanitizedItems,
        totalWeightSold: new Prisma.Decimal(totalWeightSold),
        grossSalesTotal: new Prisma.Decimal(grossSalesTotal),
        commissionPercent: new Prisma.Decimal(commissionPercent),
        commissionAmount: new Prisma.Decimal(commissionAmount),
        freightDeducted: new Prisma.Decimal(freightDeducted),
        extraFreightDeducted: new Prisma.Decimal(extraFreightDeducted),
        associationDeducted: new Prisma.Decimal(associationDeducted),
        unloadingDeducted: new Prisma.Decimal(unloadingDeducted),
        miscDeducted: new Prisma.Decimal(miscDeducted),
        totalDeductions: new Prisma.Decimal(totalDeductions),
        advanceAdjusted: new Prisma.Decimal(advanceAdjusted),
        netPayable: new Prisma.Decimal(netPayable),
        remarks: data.remarks || null,
      },
      include: {
        purchase: {
          include: {
            supplier: true,
            vegetable: true,
          },
        },
      },
    });

    return saved;
  }

  async deleteCustomPatti(purchaseId: bigint) {
    return prisma.arrivalCustomPatti.deleteMany({
      where: { purchaseId },
    });
  }
}

