import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { salesCreditNoteLifecycle } from "../db/salesCreditNote.lifecycle.generated";
import type { Transaction } from "../generated/kysely-tailordb";
import { encodeJson } from "../lib/domain";
import {
  CreditLimitExceededError,
  InvalidCreditNoteStatusError,
  SalesCreditNoteNotFoundError,
} from "../lib/errors.generated";

export interface IssueSalesCreditNoteInput {
  salesCreditNoteId: string;
}

export async function run(db: Transaction, input: IssueSalesCreditNoteInput, ctx: CommandContext) {
  void ctx;
  const salesCreditNote = await db
    .selectFrom("SalesCreditNote")
    .selectAll()
    .where("id", "=", input.salesCreditNoteId)
    .forUpdate()
    .executeTakeFirst();
  if (!salesCreditNote) {
    return err(new SalesCreditNoteNotFoundError(input.salesCreditNoteId));
  }
  const nextStatus = salesCreditNoteLifecycle.tryTransition(salesCreditNote.status, "issue");
  if (!nextStatus) {
    return err(new InvalidCreditNoteStatusError(input.salesCreditNoteId));
  }
  const lines = await db
    .selectFrom("SalesCreditNoteLine")
    .selectAll()
    .where("salesCreditNoteId", "=", input.salesCreditNoteId)
    .execute();
  for (const line of lines) {
    const invoiceLine = await db
      .selectFrom("SalesInvoiceLine")
      .selectAll()
      .where("id", "=", line.salesInvoiceLineId)
      .executeTakeFirst();
    const issuedCreditNotes = await db
      .selectFrom("SalesCreditNote")
      .selectAll()
      .where("salesInvoiceId", "=", salesCreditNote.salesInvoiceId)
      .execute();
    const issuedIds = new Set(
      issuedCreditNotes.filter((note) => note.status === "ISSUED").map((note) => note.id),
    );
    const priorLines = await db.selectFrom("SalesCreditNoteLine").selectAll().execute();
    const priorQuantity = priorLines
      .filter(
        (item) =>
          issuedIds.has(item.salesCreditNoteId) &&
          item.salesInvoiceLineId === line.salesInvoiceLineId,
      )
      .reduce((sum, item) => sum + (item.quantity ?? 0), 0);
    if ((line.quantity ?? 0) + priorQuantity > (invoiceLine?.quantity ?? 0)) {
      return err(new CreditLimitExceededError(line.salesInvoiceLineId));
    }
  }
  const updated = await db
    .updateTable("SalesCreditNote")
    .set({
      status: nextStatus,
      creditNoteNumber: salesCreditNote.creditNoteNumber ?? `CN-${salesCreditNote.id}`,
      correctionSnapshot: encodeJson({ issuedAt: new Date().toISOString() }),
    })
    .where("id", "=", input.salesCreditNoteId)
    .returningAll()
    .executeTakeFirstOrThrow();
  return ok({ salesCreditNote: updated });
}
