import { Decimal } from "decimal.js";
import type { PurchaseOrderReceiptStatus, PurchaseOrderBillingStatus } from "../generated/enums";
import type { Selectable } from "../generated/kysely-tailordb";

type SnapshotLine = NonNullable<Selectable<"PurchaseOrder">["paymentTermSnapshotLines"]>[number];

export interface DueScheduleLine {
  dueDate: Date;
  amount: string;
}

export interface LineProgress {
  quantity: string;
  receivedQuantity?: string | null;
  billedQuantity?: string | null;
}

function summarizeOrderProgress(lines: LineProgress[]) {
  let totalOrdered = new Decimal(0);
  let totalReceived = new Decimal(0);
  let totalBilled = new Decimal(0);

  for (const line of lines) {
    totalOrdered = totalOrdered.plus(line.quantity);
    totalReceived = totalReceived.plus(line.receivedQuantity ?? "0");
    totalBilled = totalBilled.plus(line.billedQuantity ?? "0");
  }

  return { totalOrdered, totalReceived, totalBilled };
}

export function deriveReceiptStatus(lines: LineProgress[]): PurchaseOrderReceiptStatus {
  const { totalOrdered, totalReceived } = summarizeOrderProgress(lines);
  if (totalOrdered.isZero()) return "NOT_RECEIVED";
  if (totalReceived.gte(totalOrdered)) return "RECEIVED";
  if (totalReceived.gt(0)) return "PARTIALLY_RECEIVED";
  return "NOT_RECEIVED";
}

export function deriveBillingStatus(lines: LineProgress[]): PurchaseOrderBillingStatus {
  const { totalOrdered, totalBilled } = summarizeOrderProgress(lines);
  if (totalOrdered.isZero()) return "NOT_BILLED";
  if (totalBilled.gte(totalOrdered)) return "BILLED";
  if (totalBilled.gt(0)) return "PARTIALLY_BILLED";
  return "NOT_BILLED";
}

export function validateScheduleLines(lines: SnapshotLine[]) {
  if (lines.length === 0) return "EMPTY_SCHEDULE";

  let percentageTotal = new Decimal(0);
  let maxDueDays = 0;

  for (const line of lines) {
    if (new Decimal(line.percentage ?? 0).lt(0) || new Decimal(line.fixedAmount ?? 0).lt(0)) {
      return "INVALID_SCHEDULE_LINE";
    }
    if (line.percentage != null) {
      percentageTotal = percentageTotal.plus(line.percentage);
    }
    maxDueDays = Math.max(maxDueDays, line.days ?? 0);
  }

  const hasInstallments = lines.some((line) => line.percentage != null);
  if (hasInstallments && percentageTotal.minus(100).abs().gt(0.0001)) {
    return "INVALID_INSTALLMENT_TOTAL";
  }

  return null;
}

export function calculateDueSchedule(
  scheduleLines: SnapshotLine[],
  billDate: Date,
  billAmount: string,
) {
  if (new Decimal(billAmount).lt(0)) {
    return { error: "INVALID_BILL_AMOUNT" as const };
  }

  const scheduleError = validateScheduleLines(scheduleLines);
  if (scheduleError) {
    return { error: "INVALID_PAYMENT_TERM_CONFIGURATION" as const };
  }

  const total = new Decimal(billAmount);

  const normalized = scheduleLines.map((line) => ({
    ...line,
    amount:
      line.fixedAmount != null
        ? new Decimal(line.fixedAmount)
        : line.percentage != null
          ? total.mul(line.percentage).div(100).toDecimalPlaces(2)
          : new Decimal(0),
  }));

  const assigned = normalized.reduce((sum, line) => sum.plus(line.amount), new Decimal(0));
  const residual = total.minus(assigned);
  if (normalized.length > 0) {
    normalized[normalized.length - 1].amount =
      normalized[normalized.length - 1].amount.plus(residual);
  }

  return {
    schedule: normalized.map((line) => {
      const dueDate = new Date(billDate);
      if (line.kind === "END_OF_MONTH") {
        dueDate.setMonth(dueDate.getMonth() + 1, 0);
      } else if (line.kind === "FIXED_DAY" && line.fixedDayOfMonth) {
        dueDate.setDate(line.fixedDayOfMonth);
      } else {
        dueDate.setDate(dueDate.getDate() + (line.days ?? 0));
      }

      return {
        dueDate,
        amount: line.amount.toDecimalPlaces(2).toString(),
      } satisfies DueScheduleLine;
    }),
  };
}
