import { Prisma } from "@prisma/client";
import { prisma } from "@/lib/prisma";

type TxClient = {
  purchaseRealizationEntry: typeof prisma.purchaseRealizationEntry;
  purchaseRealizationSummary: typeof prisma.purchaseRealizationSummary;
};

const OPENING_EVENT_TYPES = new Set(["OPENING_COST"]);
const SALE_EVENT_TYPES = new Set(["SALE_REVENUE", "CONSIGNMENT_REVENUE"]);
const RETURN_EVENT_TYPES = new Set(["SALE_RETURN", "CONSIGNMENT_RETURN"]);
const COST_ADDITIONAL_EVENT_TYPES = new Set([
  "WASHING_COST",
  "WASHING_SHRINKAGE",
  "TRANSFORMATION_SHRINKAGE",
  "SALE_SHRINKAGE",
  "CONSIGNMENT_SHRINKAGE",
  "STOCK_ADJUSTMENT_LOSS",
  "MANUAL_ADJUSTMENT"
]);

type RealizationEntry = {
  eventType: string;
  qtyIn: Prisma.Decimal;
  qtyOut: Prisma.Decimal;
  qtyShrinkage: Prisma.Decimal;
  amountCost: Prisma.Decimal;
  amountRevenue: Prisma.Decimal;
  amountExpense: Prisma.Decimal;
  agentAmount: Prisma.Decimal;
};

type AggregateSummary = {
  qtyOpening: number;
  qtyRemaining: number;
  qtySold: number;
  qtyReturned: number;
  qtyShrinkage: number;
  costOpeningTotal: number;
  costAdditionalTotal: number;
  revenueTotal: number;
  profitLossTotal: number;
  agentProfitTotal: number;
};

function toNumber(value: Prisma.Decimal) {
  return value.toNumber();
}

function roundQty(value: number) {
  return Number(value.toFixed(3));
}

function roundAmount(value: number) {
  return Number(value.toFixed(2));
}

export async function recalculatePurchaseRealizationSummary(
  tx: TxClient,
  purchaseId: bigint,
  agentSharePercent: number | null
) {
  const entries = await tx.purchaseRealizationEntry.findMany({
    where: { purchaseId },
    select: {
      eventType: true,
      qtyIn: true,
      qtyOut: true,
      qtyShrinkage: true,
      amountCost: true,
      amountRevenue: true,
      amountExpense: true,
      agentAmount: true
    }
  });

  const aggregate = (entries as RealizationEntry[]).reduce<AggregateSummary>(
    (state, entry) => {
      const qtyIn = toNumber(entry.qtyIn);
      const qtyOut = toNumber(entry.qtyOut);
      const qtyShrinkage = toNumber(entry.qtyShrinkage);
      const amountCost = toNumber(entry.amountCost);
      const amountRevenue = toNumber(entry.amountRevenue);
      const amountExpense = toNumber(entry.amountExpense);
      const agentAmount = toNumber(entry.agentAmount);

      state.qtyRemaining += qtyIn - qtyOut - qtyShrinkage;
      state.profitLossTotal += amountRevenue - amountCost - amountExpense;
      state.agentProfitTotal += agentAmount;

      if (OPENING_EVENT_TYPES.has(entry.eventType)) {
        state.qtyOpening += qtyIn;
        state.costOpeningTotal += amountCost;
      }
      if (SALE_EVENT_TYPES.has(entry.eventType)) {
        state.qtySold += qtyOut;
        state.revenueTotal += amountRevenue;
      }
      if (RETURN_EVENT_TYPES.has(entry.eventType)) {
        state.qtyReturned += qtyIn;
      }
      if (COST_ADDITIONAL_EVENT_TYPES.has(entry.eventType)) {
        state.costAdditionalTotal += amountCost + amountExpense;
      }
      return state;
    },
    {
      qtyOpening: 0,
      qtyRemaining: 0,
      qtySold: 0,
      qtyReturned: 0,
      qtyShrinkage: 0,
      costOpeningTotal: 0,
      costAdditionalTotal: 0,
      revenueTotal: 0,
      profitLossTotal: 0,
      agentProfitTotal: 0
    }
  );

  aggregate.qtyShrinkage = (entries as RealizationEntry[]).reduce(
    (sum, entry) => sum + toNumber(entry.qtyShrinkage),
    0
  );

  const status =
    aggregate.qtyOpening <= 0
      ? "OPEN"
      : aggregate.qtyRemaining <= 0
      ? "READY_TO_CLOSE"
      : aggregate.qtySold > 0 || aggregate.qtyShrinkage > 0
      ? "PARTIAL"
      : "OPEN";

  return tx.purchaseRealizationSummary.upsert({
    where: { purchaseId },
    update: {
      status,
      qtyOpening: new Prisma.Decimal(roundQty(aggregate.qtyOpening)),
      qtyRemaining: new Prisma.Decimal(roundQty(aggregate.qtyRemaining)),
      qtySold: new Prisma.Decimal(roundQty(aggregate.qtySold)),
      qtyReturned: new Prisma.Decimal(roundQty(aggregate.qtyReturned)),
      qtyShrinkage: new Prisma.Decimal(roundQty(aggregate.qtyShrinkage)),
      costOpeningTotal: new Prisma.Decimal(roundAmount(aggregate.costOpeningTotal)),
      costAdditionalTotal: new Prisma.Decimal(roundAmount(aggregate.costAdditionalTotal)),
      revenueTotal: new Prisma.Decimal(roundAmount(aggregate.revenueTotal)),
      profitLossTotal: new Prisma.Decimal(roundAmount(aggregate.profitLossTotal)),
      agentSharePercent:
        agentSharePercent === null ? null : new Prisma.Decimal(roundAmount(agentSharePercent)),
      agentProfitTotal: new Prisma.Decimal(roundAmount(aggregate.agentProfitTotal)),
      closedAt: null
    },
    create: {
      purchaseId,
      status,
      qtyOpening: new Prisma.Decimal(roundQty(aggregate.qtyOpening)),
      qtyRemaining: new Prisma.Decimal(roundQty(aggregate.qtyRemaining)),
      qtySold: new Prisma.Decimal(roundQty(aggregate.qtySold)),
      qtyReturned: new Prisma.Decimal(roundQty(aggregate.qtyReturned)),
      qtyShrinkage: new Prisma.Decimal(roundQty(aggregate.qtyShrinkage)),
      costOpeningTotal: new Prisma.Decimal(roundAmount(aggregate.costOpeningTotal)),
      costAdditionalTotal: new Prisma.Decimal(roundAmount(aggregate.costAdditionalTotal)),
      revenueTotal: new Prisma.Decimal(roundAmount(aggregate.revenueTotal)),
      profitLossTotal: new Prisma.Decimal(roundAmount(aggregate.profitLossTotal)),
      agentSharePercent:
        agentSharePercent === null ? null : new Prisma.Decimal(roundAmount(agentSharePercent)),
      agentProfitTotal: new Prisma.Decimal(roundAmount(aggregate.agentProfitTotal)),
      closedAt: null
    }
  });
}
