import type { Transaction } from "../generated/kysely-tailordb";
import {
  CrossCompanyReferenceError,
  CustomerNotActiveError,
  CustomerNotFoundError,
  CustomerRoleRequiredError,
  InvalidOrderLineError,
  ItemNotActiveError,
  ItemNotFoundError,
} from "../lib/errors.generated";
import { encodeJson, type SalesOrderLineInput } from "../lib/domain";
import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";

export interface CreateSalesOrderInput {
  companyId: string;
  customerId: string;
  channelId?: string | null;
  paymentTermId?: string | null;
  orderDate?: Date;
  requestedShipDate?: Date | null;
  shippingAddressSnapshot?: Record<string, unknown> | null;
  billingAddressSnapshot?: Record<string, unknown> | null;
  lines: SalesOrderLineInput[];
}

export async function run(db: Transaction, input: CreateSalesOrderInput, ctx: CommandContext) {
  void ctx;
  const customer = await db
    .selectFrom("BusinessPartner")
    .selectAll()
    .where("id", "=", input.customerId)
    .executeTakeFirst();
  if (!customer) {
    return err(new CustomerNotFoundError(input.customerId));
  }
  if (customer.status !== "ACTIVE") {
    return err(new CustomerNotActiveError(input.customerId));
  }
  if (customer.companyId !== input.companyId) {
    return err(new CrossCompanyReferenceError(input.customerId));
  }

  const customerRoles = await db
    .selectFrom("PartnerRole")
    .selectAll()
    .where("partnerId", "=", input.customerId)
    .execute();
  if (!customerRoles.some((role) => role.role === "CUSTOMER")) {
    return err(new CustomerRoleRequiredError(input.customerId));
  }
  if (!input.lines.length) {
    return err(new InvalidOrderLineError(input.customerId));
  }

  for (const line of input.lines) {
    if (!line.itemId || line.quantity <= 0 || line.unitPrice < 0 || !line.currencyId) {
      return err(new InvalidOrderLineError(line.itemId || input.customerId));
    }
    const item = await db
      .selectFrom("Item")
      .selectAll()
      .where("id", "=", line.itemId)
      .executeTakeFirst();
    if (!item) {
      return err(new ItemNotFoundError(line.itemId));
    }
    if (item.status !== "ACTIVE") {
      return err(new ItemNotActiveError(line.itemId));
    }
    if (item.companyId && item.companyId !== input.companyId) {
      return err(new CrossCompanyReferenceError(line.itemId));
    }
  }

  if (input.channelId) {
    const channel = await db
      .selectFrom("Channel")
      .selectAll()
      .where("id", "=", input.channelId)
      .executeTakeFirst();
    if (channel && channel.companyId !== input.companyId) {
      return err(new CrossCompanyReferenceError(input.channelId));
    }
  }

  const salesOrder = await db
    .insertInto("SalesOrder")
    .values({
      companyId: input.companyId,
      customerId: input.customerId,
      channelId: input.channelId ?? null,
      paymentTermId: input.paymentTermId ?? null,
      status: "DRAFT",
      orderDate: input.orderDate ?? new Date(),
      requestedShipDate: input.requestedShipDate ?? null,
      shippingAddressSnapshot: input.shippingAddressSnapshot
        ? encodeJson(input.shippingAddressSnapshot)
        : null,
      billingAddressSnapshot: input.billingAddressSnapshot
        ? encodeJson(input.billingAddressSnapshot)
        : null,
      paymentTermSnapshot: null,
      rejectionReason: null,
      holdReason: null,
      closeReason: null,
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

  const insertedLines = [];
  for (const line of input.lines) {
    const inserted = await db
      .insertInto("SalesOrderLine")
      .values({
        salesOrderId: salesOrder!.id,
        itemId: line.itemId,
        description: line.description ?? null,
        quantity: line.quantity,
        unitPrice: line.unitPrice,
        currencyId: line.currencyId,
        unitId: line.unitId ?? null,
        matchedPriceRuleId: line.matchedPriceRuleId ?? null,
        billingPolicy: line.billingPolicy ?? "ORDER",
        requestedShipDate: line.requestedShipDate ?? null,
        requiresPhysicalFulfillment: line.requiresPhysicalFulfillment ?? true,
        priceSnapshot: line.priceSnapshot ? encodeJson(line.priceSnapshot) : null,
        shippedQuantity: line.shippedQuantity ?? 0,
        invoicedQuantity: line.invoicedQuantity ?? 0,
        createdAt: new Date(),
        updatedAt: null,
      })
      .returningAll()
      .executeTakeFirst();
    insertedLines.push(inserted);
  }

  return ok({ salesOrder: salesOrder!, salesOrderLines: insertedLines });
}
