import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import type { Transaction } from "../generated/kysely-tailordb";
import { type SalesCreditNoteLineInput } from "../lib/domain";
import {
  InvalidCreditNoteLineError,
  InvoiceNotIssuedError,
  SalesInvoiceNotFoundError,
} from "../lib/errors.generated";

export interface SalesCreditNoteHeaderInput {
  salesInvoiceId: string;
  creditDate?: Date;
  reason?: string;
}

export interface CreateSalesCreditNoteInput {
  header: SalesCreditNoteHeaderInput;
  lines: SalesCreditNoteLineInput[];
}

export async function run<CF extends Record<string, unknown>, LCF extends Record<string, unknown>>(
  db: Transaction,
  input: {
    header: SalesCreditNoteHeaderInput & CF;
    lines: (SalesCreditNoteLineInput & LCF)[];
  },
  ctx: CommandContext,
) {
  void ctx;
  const { header, lines } = input;
  const salesInvoice = await db
    .selectFrom("SalesInvoice")
    .selectAll()
    .where("id", "=", header.salesInvoiceId)
    .executeTakeFirst();
  if (!salesInvoice) {
    return err(new SalesInvoiceNotFoundError(header.salesInvoiceId));
  }
  if (salesInvoice.status !== "ISSUED") {
    return err(new InvoiceNotIssuedError(header.salesInvoiceId));
  }
  if (!lines.length) {
    return err(new InvalidCreditNoteLineError(header.salesInvoiceId));
  }
  for (const line of lines) {
    const invoiceLine = await db
      .selectFrom("SalesInvoiceLine")
      .selectAll()
      .where("id", "=", line.salesInvoiceLineId)
      .executeTakeFirst();
    if (invoiceLine?.salesInvoiceId !== header.salesInvoiceId) {
      return err(new InvalidCreditNoteLineError(line.salesInvoiceLineId));
    }
  }
  const salesCreditNote = await db
    .insertInto("SalesCreditNote")
    .values({
      // Header input first (incl. custom fields) so command-set columns always win.
      ...(header as Record<string, unknown>),
      companyId: salesInvoice.companyId,
      customerId: salesInvoice.customerId,
      salesInvoiceId: header.salesInvoiceId,
      status: "DRAFT",
      creditNoteNumber: null,
      creditDate: header.creditDate ?? new Date(),
      reason: header.reason ?? null,
      correctionSnapshot: null,
    })
    .returningAll()
    .executeTakeFirstOrThrow();
  if (lines.length > 0) {
    await db
      .insertInto("SalesCreditNoteLine")
      .values(
        lines.map((line) => {
          const { salesInvoiceLineId, quantity, amount, reason, taxAmount, ...lineCustomFields } =
            line;
          return {
            // Custom fields first so command-set columns always win.
            ...(lineCustomFields as Record<string, unknown>),
            salesCreditNoteId: salesCreditNote.id,
            salesInvoiceLineId,
            quantity: quantity ?? null,
            amount: amount ?? null,
            reason: reason ?? null,
            taxAmount: taxAmount ?? null,
          };
        }),
      )
      .execute();
  }
  return ok({ salesCreditNote });
}
