import type { Transaction } from "../generated/kysely-tailordb";
import {
  CrossCompanyReferenceError,
  InvalidInvoiceLineError,
  SalesOrderNotFoundError,
} from "../lib/errors.generated";
import { encodeJson, type SalesInvoiceLineInput } from "../lib/domain";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";

export interface CreateSalesInvoiceInput {
  companyId: string;
  salesOrderId: string;
  shipmentId?: string | null;
  invoiceDate?: Date;
  lines: SalesInvoiceLineInput[];
  notes?: string;
}

export async function run(db: Transaction, input: CreateSalesInvoiceInput, ctx: CommandContext) {
  void ctx;
  const salesOrder = await db
    .selectFrom("SalesOrder")
    .selectAll()
    .where("id", "=", input.salesOrderId)
    .executeTakeFirst();
  if (!salesOrder) {
    return err(new SalesOrderNotFoundError(input.salesOrderId));
  }
  if (salesOrder.companyId !== input.companyId) {
    return err(new CrossCompanyReferenceError(input.salesOrderId));
  }
  if (!input.lines.length) {
    return err(new InvalidInvoiceLineError(input.salesOrderId));
  }
  for (const line of input.lines) {
    const orderLine = await db
      .selectFrom("SalesOrderLine")
      .selectAll()
      .where("id", "=", line.salesOrderLineId)
      .executeTakeFirst();
    if (orderLine?.salesOrderId !== input.salesOrderId || line.quantity <= 0) {
      return err(new InvalidInvoiceLineError(line.salesOrderLineId));
    }
  }
  const salesInvoice = await db
    .insertInto("SalesInvoice")
    .values({
      companyId: input.companyId,
      customerId: salesOrder.customerId,
      salesOrderId: input.salesOrderId,
      paymentTermId: salesOrder.paymentTermId,
      shipmentId: input.shipmentId ?? null,
      status: "DRAFT",
      invoiceNumber: null,
      invoiceDate: input.invoiceDate ?? new Date(),
      billingPolicy: null,
      paymentTermSnapshot: salesOrder.paymentTermSnapshot,
      dueSchedule: null,
      taxIdentitySnapshot: null,
      deliveryMetadata: null,
      arHandoffAccepted: false,
      notes: input.notes ?? null,
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();
  for (const line of input.lines) {
    const orderLine = await db
      .selectFrom("SalesOrderLine")
      .selectAll()
      .where("id", "=", line.salesOrderLineId)
      .executeTakeFirst();
    await db
      .insertInto("SalesInvoiceLine")
      .values({
        salesInvoiceId: salesInvoice!.id,
        salesOrderLineId: line.salesOrderLineId,
        shipmentLineId: line.shipmentLineId ?? null,
        quantity: line.quantity,
        unitPrice: line.unitPrice ?? orderLine!.unitPrice,
        taxCode: line.taxCode ?? null,
        taxableAmount: line.taxableAmount ?? null,
        taxAmount: line.taxAmount ?? null,
        billingPolicy:
          line.billingPolicy ?? (line.shipmentLineId ? "SHIPMENT" : orderLine!.billingPolicy),
        orderPriceSnapshot: encodeJson({ unitPrice: orderLine!.unitPrice }),
        createdAt: new Date(),
        updatedAt: null,
      })
      .executeTakeFirst();
  }
  return ok({ salesInvoice: salesInvoice! });
}
