import prisma from "../config/prisma.js";
import { Prisma } from "../generated/prisma/client.js";
import { ARRIVAL_COMMISSION_PERCENT } from "../config/constants.js";

// ─── Serializer helpers ───────────────────────────────────────────────────────

function serializeBigInts(obj: any): any {
  return JSON.parse(
    JSON.stringify(obj, (key, value) =>
      typeof value === 'bigint' ? value.toString() : value
    )
  );
}

// ─── AllSalesService ──────────────────────────────────────────────────────────

export class AllSalesService {
  /**
   * Get all sales: wholesale (Sale) + retail sales that have been moved to wholesale.
   * Both are normalised into a common shape with a `source` field.
   */
  async getAllSales(
    page: number = 1,
    limit: number = 10,
    dateFromStr?: string,
    dateToStr?: string,
    search?: string
  ) {
    // ── Build WHERE for wholesale sales ──────────────────────────────────────
    const wholesaleWhere: Prisma.SaleWhereInput = { deletedAt: null };

    if (search) {
      wholesaleWhere.OR = [
        { invoiceNo: { contains: search } },
        { customer: { name: { contains: search } } },
        { walkInName: { contains: search } },
      ];
    }

    if (dateFromStr || dateToStr) {
      wholesaleWhere.invoiceDate = {};
      if (dateFromStr) (wholesaleWhere.invoiceDate as any).gte = new Date(dateFromStr);
      if (dateToStr) (wholesaleWhere.invoiceDate as any).lte = new Date(dateToStr);
    }

    // ── Build WHERE for moved retail sales ───────────────────────────────────
    const retailWhere: Prisma.RetailSaleWhereInput = {
      deletedAt: null,
      movedToWholesale: true,
    };

    if (search) {
      retailWhere.OR = [
        { invoiceNo: { contains: search } },
        { walkInName: { contains: search } },
        { walkInMobile: { contains: search } },
      ];
    }

    if (dateFromStr || dateToStr) {
      retailWhere.invoiceDate = {};
      if (dateFromStr) (retailWhere.invoiceDate as any).gte = new Date(dateFromStr);
      if (dateToStr) (retailWhere.invoiceDate as any).lte = new Date(dateToStr);
    }

    // ── Fetch both sets in parallel ──────────────────────────────────────────
    const [wholesaleSales, wholesaleCount, retailSales, retailCount] = await Promise.all([
      prisma.sale.findMany({
        where: wholesaleWhere,
        orderBy: { invoiceDate: "desc" },
        include: {
          customer: { select: { name: true } },
          items: {
            include: {
              saleReturns: true,
              purchase: {
                select: {
                  lotNo: true,
                  vegetable: { select: { name: true, isFlexible: true } },
                  purchaseType: true,
                  arrivalSettlement: { select: { status: true } },
                },
              },
            },
          },
        },
      }),
      prisma.sale.count({ where: wholesaleWhere }),
      prisma.retailSale.findMany({
        where: retailWhere,
        orderBy: { invoiceDate: "desc" },
        include: {
          items: {
            include: {
              retailStock: {
                select: {
                  lotNo: true,
                  purchase: {
                    select: {
                      vegetable: { select: { name: true, isFlexible: true } },
                      purchaseType: true,
                      arrivalSettlement: { select: { status: true } },
                    }
                  },
                },
              },
            },
          },
        },
      }),
      prisma.retailSale.count({ where: retailWhere }),
    ]);

    // ── Normalise into a common shape ────────────────────────────────────────
    const normalised: any[] = [];

    for (const sale of wholesaleSales) {
      let netSubTotalBeforeDiscount = 0;
      let actualCostPrice = 0;

      if (sale.items) {
        for (const item of sale.items) {
          const returnedQty = (item.saleReturns || []).reduce(
            (sum: number, ret: any) => sum + Number(ret.quantity),
            0
          );
          const returnedAmount = (item.saleReturns || []).reduce(
            (sum: number, ret: any) => sum + Number(ret.returnAmount),
            0
          );

          const netQty = Math.max(0, Number(item.quantity) - returnedQty);
          const netTotal = Math.max(0, Number(item.total) - returnedAmount);

          netSubTotalBeforeDiscount += netTotal;

          const isBagOnly = item.saleUnit !== 'KG' && Number(item.quantity) <= 0;
          const netBags = isBagOnly ? Math.max(0, Number(item.bags || 0) - returnedQty) : 0;
          const effectiveNetQty = isBagOnly ? netBags : netQty;

          const isArrival = item.purchase?.purchaseType === 'COMMISSION_ARRIVAL';
          const isFlexible = !!item.purchase?.vegetable?.isFlexible || item.saleUnit === 'PIECES';
          if (isArrival) {
            const isSettled = item.purchase?.arrivalSettlement?.status === 'SETTLED';
            const itemProfit = isSettled ? netTotal * (ARRIVAL_COMMISSION_PERCENT / 100) : 0;
            actualCostPrice += (netTotal - itemProfit);
          } else if (isFlexible) {
            // Flexible vegetables (Coconut, Banana, Cabbage) do not calculate profit
            actualCostPrice += netTotal;
          } else {
            const qtyForCost = isBagOnly ? effectiveNetQty : netQty;
            actualCostPrice += qtyForCost * Number(item.costPerUnit);
          }
        }
      }

      const discount = Number(sale.discount ?? 0);
      const freightCharge = Number(sale.freightCharge ?? 0);
      const netSubTotalAfterDiscount = netSubTotalBeforeDiscount - discount;
      const effectiveNetSubTotal = netSubTotalAfterDiscount < 0 ? 0 : netSubTotalAfterDiscount;
      const netGrandTotal = effectiveNetSubTotal + freightCharge;

      const profit = netGrandTotal - actualCostPrice;
      const isProfit = profit >= 0;
      const formattedProfit = `${isProfit ? '+' : ''}\u20B9${profit.toFixed(2)}`;

      normalised.push(serializeBigInts({
        ...sale,
        grandTotal: netGrandTotal,
        source: "wholesale",
        customerName: sale.customer?.name || sale.walkInName || "Walk-in Customer",
        itemCount: sale.items?.length || 0,
        actualCostPrice,
        profit,
        formattedProfit,
        profitColor: isProfit ? "success" : "error",
      }));
    }

    for (const sale of retailSales) {
      const actualCostPrice = sale.items?.reduce(
        (sum, item) => {
          const isArrival = item.retailStock?.purchase?.purchaseType === 'COMMISSION_ARRIVAL';
          const isFlexible = !!item.retailStock?.purchase?.vegetable?.isFlexible || item.saleUnit === 'PIECES';
          if (isArrival) {
            const isSettled = item.retailStock?.purchase?.arrivalSettlement?.status === 'SETTLED';
            const itemTotal = Number(item.total || 0);
            const itemProfit = isSettled ? itemTotal * (ARRIVAL_COMMISSION_PERCENT / 100) : 0;
            return sum + (itemTotal - itemProfit);
          }
          if (isFlexible) {
            return sum + Number(item.total || 0); // Exclude from profit calculation
          }
          // item.quantity is always in KG; costPerUnit is always per-KG.
          // Use quantity(KG) × costPerUnit(per-KG) for cost regardless of sale unit.
          return sum + (Number(item.quantity || 0) * Number(item.costPerUnit));
        },
        0
      ) || 0;
      const profit = Number(sale.grandTotal) - actualCostPrice;

      const isProfit = profit >= 0;
      const formattedProfit = `${isProfit ? '+' : ''}\u20B9${profit.toFixed(2)}`;

      normalised.push(serializeBigInts({
        ...sale,
        source: "retail",
        customerName: sale.walkInName || "Walk-in Customer",
        itemCount: sale.items?.length || 0,
        actualCostPrice,
        profit,
        formattedProfit,
        profitColor: isProfit ? "success" : "error",
      }));
    }

    // ── Sort by invoiceDate descending, then by id descending ────────────────
    normalised.sort((a, b) => {
      const dateA = new Date(a.invoiceDate).getTime();
      const dateB = new Date(b.invoiceDate).getTime();
      if (dateB !== dateA) return dateB - dateA;
      // Secondary sort: larger id first (newest)
      return Number(BigInt(b.id) - BigInt(a.id));
    });

    // ── Paginate the combined results ────────────────────────────────────────
    const total = wholesaleCount + retailCount;
    const totalPages = Math.ceil(total / limit);
    const skip = (page - 1) * limit;
    const paginatedData = normalised.slice(skip, skip + limit);

    // ── Calculate overall totals ─────────────────────────────────────────────
    const overallActualCostPrice = normalised.reduce((sum, item) => sum + (Number(item.actualCostPrice) || 0), 0);
    const overallSellingPrice = normalised.reduce((sum, item) => sum + (Number(item.grandTotal) || 0), 0);
    const overallProfit = normalised.reduce((sum, item) => sum + (Number(item.profit) || 0), 0);

    return {
      data: paginatedData,
      meta: { total, page, limit, totalPages },
      summary: {
        totalPurchase: overallActualCostPrice,
        totalSales: overallSellingPrice,
        totalProfit: overallProfit,
      },
    };
  }
}


