type DecimalLike = { toNumber(): number };

function normalizeUnitCode(unitCode: string | null | undefined) {
  return (unitCode ?? "").trim().toLowerCase();
}

function convertQtyToKg(qty: number, unitCode: string | null | undefined) {
  const normalizedUnit = normalizeUnitCode(unitCode);
  if (normalizedUnit === "g" || normalizedUnit === "gr" || normalizedUnit === "gram" || normalizedUnit === "grams") {
    return qty / 1000;
  }
  return qty;
}

type PurchaseAnalysisSource = {
  id: bigint;
  purchaseNo: string;
  purchaseDate: Date;
  status: string;
  moistureBuyPercent: DecimalLike | null;
  moistureReceivedPercent: DecimalLike | null;
  aboveAverageRatioPercent: DecimalLike | null;
  mkSharePercent: DecimalLike | null;
  nonMkSharePercent: DecimalLike | null;
  profitShareScheme: {
    shareAgent: DecimalLike;
  } | null;
  agent: {
    id: bigint;
    name: string;
  } | null;
  lines: Array<{
    subtotal: DecimalLike;
    qtyOrdered: DecimalLike;
    qtyReceived: DecimalLike;
    qtyAccepted: DecimalLike;
    unitPrice: DecimalLike;
    malUnitPrice: DecimalLike | null;
    moistureReceivedPercent: DecimalLike | null;
    purchaseMoisturePercent: DecimalLike | null;
    marketReferencePrice: DecimalLike | null;
    unit: {
      code: string;
    };
  }>;
  shippingCost: DecimalLike | null;
  incomingOperationalCost: DecimalLike | null;
  afterArrivalOperationalCost: DecimalLike | null;
  lots: Array<{
    originalQty: DecimalLike;
    finalMoisturePercent: DecimalLike | null;
    aboveAverageRatioPercent: DecimalLike | null;
    unit: {
      code: string;
    } | null;
  }>;
  analysis: {
    status: string;
    weightBuy: DecimalLike | null;
    weightReceived: DecimalLike | null;
    weightFinal: DecimalLike | null;
    moistureBuyPercent: DecimalLike | null;
    moistureReceivedPercent: DecimalLike | null;
    moistureFinalPercent: DecimalLike | null;
    aboveAverageRatioPercent: DecimalLike | null;
    averagePrice: DecimalLike | null;
    modalBeli: DecimalLike | null;
    modalMasuk: DecimalLike | null;
    modalJual: DecimalLike | null;
    modalBarang: DecimalLike | null;
    totalModalBeli: DecimalLike | null;
    totalModalMal: DecimalLike | null;
    marketReferencePrice: DecimalLike | null;
    marketValuationTotal: DecimalLike | null;
    agentProfitShareTotal: DecimalLike;
    notes: string | null;
    costEntries: Array<{
      id: bigint;
      costType: string;
      description: string | null;
      amount: DecimalLike;
    }>;
  } | null;
};

const round = (value: number | null, digits = 2) => {
  if (value === null || !Number.isFinite(value)) return null;
  const factor = 10 ** digits;
  return Math.round(value * factor) / factor;
};

function sum(values: number[]) {
  return values.reduce((total, value) => total + value, 0);
}

function average(values: Array<number | null>) {
  const filtered = values.filter((value): value is number => value !== null);
  if (filtered.length === 0) return null;
  return sum(filtered) / filtered.length;
}

export function calculatePurchaseAnalysis(source: PurchaseAnalysisSource) {
  const baseUnitCode = source.lines[0]?.unit.code ?? source.lots[0]?.unit?.code ?? null;
  const actualModalBarang = sum(
    source.lines.map((line) => {
      const qtyOrdered = convertQtyToKg(line.qtyOrdered.toNumber(), line.unit.code);
      const malUnitPrice = line.malUnitPrice?.toNumber() ?? line.unitPrice.toNumber();
      return qtyOrdered * malUnitPrice;
    })
  );
  const totalModalMalFromPurchase = sum(
    source.lines.map((line) => {
      const qtyOrdered = convertQtyToKg(line.qtyOrdered.toNumber(), line.unit.code);
      const malUnitPrice = line.malUnitPrice?.toNumber() ?? line.unitPrice.toNumber();
      return qtyOrdered * malUnitPrice;
    })
  );
  const derivedWeightBuy = sum(
    source.lines.map((line) => convertQtyToKg(line.qtyOrdered.toNumber(), line.unit.code))
  );
  const totalQtyAcceptedFromPurchaseLines = sum(
    source.lines.map((line) => convertQtyToKg(line.qtyAccepted.toNumber(), line.unit.code))
  );
  const totalQtyReceivedFromPurchaseLines = sum(
    source.lines.map((line) => convertQtyToKg(line.qtyReceived.toNumber(), line.unit.code))
  );
  const hasPurchaseReceiving = totalQtyAcceptedFromPurchaseLines > 0 || totalQtyReceivedFromPurchaseLines > 0;
  const derivedWeightReceived = hasPurchaseReceiving
    ? totalQtyAcceptedFromPurchaseLines
    : derivedWeightBuy;
  const finalWeightFromLots = sum(
    source.lots.map((lot) => convertQtyToKg(lot.originalQty.toNumber(), lot.unit?.code))
  );
  const derivedWeightFinal =
    source.lots.length > 0 ? finalWeightFromLots : derivedWeightReceived;
  const fallbackMoistureFinal =
    derivedWeightFinal > 0
      ? ((derivedWeightFinal - derivedWeightReceived) / derivedWeightFinal) * 100
      : null;

  const actualMoistureBuy =
    (source.moistureBuyPercent ? source.moistureBuyPercent.toNumber() : null) ??
    average(source.lines.map((line) => (line.purchaseMoisturePercent ? line.purchaseMoisturePercent.toNumber() : null)));
  const actualMoistureReceived = average(
    source.moistureReceivedPercent !== null
      ? [source.moistureReceivedPercent.toNumber()]
      : hasPurchaseReceiving
      ? source.lines.map((line) => (line.moistureReceivedPercent ? line.moistureReceivedPercent.toNumber() : null))
      : source.lines.map((line) => (line.purchaseMoisturePercent ? line.purchaseMoisturePercent.toNumber() : null))
  );
  const actualMoistureFinal = average(
    source.lots.map((lot) => (lot.finalMoisturePercent ? lot.finalMoisturePercent.toNumber() : null))
  ) ?? fallbackMoistureFinal;
  const actualAboveAverageRatio =
    (source.aboveAverageRatioPercent ? source.aboveAverageRatioPercent.toNumber() : null) ??
    average(source.lots.map((lot) => (lot.aboveAverageRatioPercent ? lot.aboveAverageRatioPercent.toNumber() : null)));
  const actualMarketReferencePrice = average(
    source.lines.map((line) => (line.marketReferencePrice ? line.marketReferencePrice.toNumber() : null))
  );

  const inputs = source.analysis;
  const effectiveWeightBuy = inputs?.weightBuy?.toNumber() ?? derivedWeightBuy;
  const effectiveWeightReceived = inputs?.weightReceived?.toNumber() ?? derivedWeightReceived;
  const effectiveWeightFinal = inputs?.weightFinal?.toNumber() ?? derivedWeightFinal;
  const effectiveWeightBuyKg = convertQtyToKg(effectiveWeightBuy, baseUnitCode);
  const effectiveWeightReceivedKg = convertQtyToKg(effectiveWeightReceived, baseUnitCode);
  const effectiveWeightFinalKg = convertQtyToKg(effectiveWeightFinal, baseUnitCode);
  const effectiveMoistureBuy =
    inputs?.moistureBuyPercent !== null && inputs?.moistureBuyPercent !== undefined
      ? inputs.moistureBuyPercent.toNumber()
      : actualMoistureBuy;
  const effectiveMoistureReceived =
    inputs?.moistureReceivedPercent !== null && inputs?.moistureReceivedPercent !== undefined
      ? inputs.moistureReceivedPercent.toNumber()
      : actualMoistureReceived;
  const effectiveMoistureFinal =
    inputs?.moistureFinalPercent !== null && inputs?.moistureFinalPercent !== undefined
      ? inputs.moistureFinalPercent.toNumber()
      : actualMoistureFinal;
  const effectiveAboveAverageRatio =
    inputs?.aboveAverageRatioPercent !== null && inputs?.aboveAverageRatioPercent !== undefined
      ? inputs.aboveAverageRatioPercent.toNumber()
      : actualAboveAverageRatio;
  const effectiveMarketReferencePrice =
    inputs?.marketReferencePrice !== null && inputs?.marketReferencePrice !== undefined
      ? inputs.marketReferencePrice.toNumber()
      : actualMarketReferencePrice;

  const headerOperational = [
    source.shippingCost,
    source.incomingOperationalCost,
    source.afterArrivalOperationalCost
  ]
    .map((value) => value?.toNumber() ?? 0)
    .filter((value) => Number.isFinite(value));

  const operationalTotal = sum([
    ...headerOperational,
    ...(inputs?.costEntries ?? []).map((entry) => entry.amount.toNumber())
  ]);
  const modalBarang = inputs?.modalBarang?.toNumber() ?? actualModalBarang;
  const totalModalBeli = inputs?.totalModalBeli?.toNumber() ?? (modalBarang + operationalTotal);
  const marketValuationTotal = inputs?.totalModalMal?.toNumber() ?? totalModalMalFromPurchase;
  const totalLabaRugi =
    marketValuationTotal !== null ? marketValuationTotal - totalModalBeli : null;
  const agentSharePercent = source.profitShareScheme?.shareAgent.toNumber() ?? 0;
  const agentProfitShareTotal =
    totalLabaRugi !== null ? (totalLabaRugi * agentSharePercent) / 100 : 0;

  const beratNaikPercent =
    effectiveWeightBuy > 0
      ? ((effectiveWeightReceived - effectiveWeightBuy) / effectiveWeightBuy) * 100
      : null;
  const susutTambah =
    effectiveWeightReceived > 0 || effectiveWeightFinal > 0
      ? effectiveWeightFinal - effectiveWeightReceived
      : null;
  const modalBeliPerKg =
    inputs?.modalBeli?.toNumber() ?? (effectiveWeightBuyKg > 0 ? totalModalBeli / effectiveWeightBuyKg : null);
  const modalMasukPerKg =
    inputs?.modalMasuk?.toNumber() ?? (effectiveWeightReceivedKg > 0 ? totalModalBeli / effectiveWeightReceivedKg : null);
  const modalJualPerKg =
    inputs?.modalJual?.toNumber() ?? (effectiveWeightFinalKg > 0 ? marketValuationTotal / effectiveWeightFinalKg : null);
  const averagePrice =
    inputs?.averagePrice?.toNumber() ?? (effectiveWeightFinalKg > 0 ? totalModalBeli / effectiveWeightFinalKg : null);
  const labaTotalPerKg =
    totalLabaRugi !== null && effectiveWeightFinalKg > 0
      ? totalLabaRugi / effectiveWeightFinalKg
      : null;
  const labaAgenPerKg =
    effectiveWeightFinalKg > 0 ? agentProfitShareTotal / effectiveWeightFinalKg : null;

  return {
    purchase: {
      id: source.id.toString(),
      purchase_no: source.purchaseNo,
      purchase_date: source.purchaseDate.toISOString().slice(0, 10),
      supplier_name: source.agent?.name ?? "-",
      status: source.status,
      grand_total: modalBarang,
      line_count: source.lines.length
    },
    actuals: {
      weight_buy: round(effectiveWeightBuy, 3) ?? 0,
      weight_received: round(effectiveWeightReceived, 3) ?? 0,
      weight_final: round(effectiveWeightFinal, 3) ?? 0,
      moisture_buy_percent: round(actualMoistureBuy),
      moisture_received_percent: round(actualMoistureReceived),
      moisture_final_percent: round(actualMoistureFinal),
      above_average_ratio_percent: round(actualAboveAverageRatio),
      market_reference_price: round(actualMarketReferencePrice)
    },
    inputs: {
      status: inputs?.status ?? "DRAFT",
      weight_buy: inputs?.weightBuy !== null && inputs?.weightBuy !== undefined ? inputs.weightBuy.toNumber() : null,
      weight_received: inputs?.weightReceived !== null && inputs?.weightReceived !== undefined ? inputs.weightReceived.toNumber() : null,
      weight_final: inputs?.weightFinal !== null && inputs?.weightFinal !== undefined ? inputs.weightFinal.toNumber() : null,
      moisture_buy_percent: inputs?.moistureBuyPercent !== null && inputs?.moistureBuyPercent !== undefined ? inputs.moistureBuyPercent.toNumber() : null,
      moisture_received_percent: inputs?.moistureReceivedPercent !== null && inputs?.moistureReceivedPercent !== undefined ? inputs.moistureReceivedPercent.toNumber() : null,
      moisture_final_percent: inputs?.moistureFinalPercent !== null && inputs?.moistureFinalPercent !== undefined ? inputs.moistureFinalPercent.toNumber() : null,
      above_average_ratio_percent: inputs?.aboveAverageRatioPercent !== null && inputs?.aboveAverageRatioPercent !== undefined ? inputs.aboveAverageRatioPercent.toNumber() : null,
      average_price: inputs?.averagePrice !== null && inputs?.averagePrice !== undefined ? inputs.averagePrice.toNumber() : null,
      modal_beli: inputs?.modalBeli !== null && inputs?.modalBeli !== undefined ? inputs.modalBeli.toNumber() : null,
      modal_masuk: inputs?.modalMasuk !== null && inputs?.modalMasuk !== undefined ? inputs.modalMasuk.toNumber() : null,
      modal_jual: inputs?.modalJual !== null && inputs?.modalJual !== undefined ? inputs.modalJual.toNumber() : null,
      modal_barang: inputs?.modalBarang !== null && inputs?.modalBarang !== undefined ? inputs.modalBarang.toNumber() : null,
      total_modal_beli: inputs?.totalModalBeli !== null && inputs?.totalModalBeli !== undefined ? inputs.totalModalBeli.toNumber() : null,
      total_modal_mal: inputs?.totalModalMal !== null && inputs?.totalModalMal !== undefined ? inputs.totalModalMal.toNumber() : null,
      market_reference_price: inputs?.marketReferencePrice !== null && inputs?.marketReferencePrice !== undefined ? inputs.marketReferencePrice.toNumber() : null,
      market_valuation_total: round(totalModalMalFromPurchase),
      agent_profit_share_total: agentProfitShareTotal,
      notes: inputs?.notes ?? null,
      cost_entries: (inputs?.costEntries ?? []).map((entry) => ({
        id: entry.id.toString(),
        cost_type: entry.costType,
        description: entry.description,
        amount: entry.amount.toNumber()
      }))
    },
    summary: {
      berat_beli: round(effectiveWeightBuy, 3),
      berat_masuk: round(effectiveWeightReceived, 3),
      berat_akhir: round(effectiveWeightFinal, 3),
      berat_naik_percent: round(beratNaikPercent),
      susut_tambah: round(susutTambah, 3),
      kadar_beli_percent: round(effectiveMoistureBuy),
      kadar_masuk_percent: round(effectiveMoistureReceived),
      kadar_akhir_percent: round(effectiveMoistureFinal),
      barang_atas_rata2_percent: round(effectiveAboveAverageRatio),
      mk_percent: round(source.mkSharePercent?.toNumber() ?? null),
      non_mk_percent: round(source.nonMkSharePercent?.toNumber() ?? null),
      harga_mk_ar: round(effectiveMarketReferencePrice),
      harga_rata2: round(averagePrice),
      modal_beli: round(modalBeliPerKg),
      modal_masuk: round(modalMasukPerKg),
      modal_jual: round(modalJualPerKg),
      modal_barang: round(modalBarang) ?? 0,
      operasional: round(operationalTotal) ?? 0,
      total_modal_beli: round(totalModalBeli) ?? 0,
      total_modal_mal: round(marketValuationTotal),
      total_laba_rugi: round(totalLabaRugi),
      laba_rugi_agen: round(agentProfitShareTotal) ?? 0,
      laba_total_per_kg: round(labaTotalPerKg),
      laba_agen_per_kg: round(labaAgenPerKg)
    }
  };
}
