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

export interface SalesOrderHeaderInput {
  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;
}

export async function run<CF extends Record<string, unknown>, LCF extends Record<string, unknown>>(
  db: Transaction,
  input: {
    header: SalesOrderHeaderInput & CF;
    lines: (SalesOrderLineInput & LCF)[];
  },
  ctx: CommandContext,
  businessPartnerQueries: Pick<BusinessPartnerQueries, "getPartner" | "getPartnerRole">,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
) {
  const { header, lines } = input;

  const { partner: customer } = (
    await businessPartnerQueries.getPartner(db, { id: header.customerId }, ctx)
  ).value;
  if (!customer) {
    return err(new CustomerNotFoundError(header.customerId));
  }
  if (customer.status !== "ACTIVE") {
    return err(new CustomerNotActiveError(header.customerId));
  }
  if (customer.companyId !== header.companyId) {
    return err(new CrossCompanyReferenceError(header.customerId));
  }

  const { partnerRole } = (
    await businessPartnerQueries.getPartnerRole(
      db,
      { partnerId: header.customerId, role: "CUSTOMER" },
      ctx,
    )
  ).value;
  if (!partnerRole) {
    return err(new CustomerRoleRequiredError(header.customerId));
  }
  if (!lines.length) {
    return err(new InvalidOrderLineError(header.customerId));
  }

  for (const line of lines) {
    if (!line.itemId || line.quantity <= 0 || line.unitPrice < 0 || !line.currencyId) {
      return err(new InvalidOrderLineError(line.itemId || header.customerId));
    }
    const { item } = (await itemManagementQueries.getItem(db, { id: line.itemId }, ctx)).value;
    if (!item) {
      return err(new ItemNotFoundError(line.itemId));
    }
    if (item.status !== "ACTIVE") {
      return err(new ItemNotActiveError(line.itemId));
    }
    const itemCompanyId = (item as { companyId?: string }).companyId;
    if (itemCompanyId && itemCompanyId !== header.companyId) {
      return err(new CrossCompanyReferenceError(line.itemId));
    }
  }

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

  const salesOrder = await db
    .insertInto("SalesOrder")
    .values({
      // Header input first (incl. custom fields) so command-set columns always win.
      ...(header as Record<string, unknown>),
      companyId: header.companyId,
      customerId: header.customerId,
      channelId: header.channelId ?? null,
      paymentTermId: header.paymentTermId ?? null,
      status: "DRAFT",
      orderDate: header.orderDate ?? new Date(),
      requestedShipDate: header.requestedShipDate ?? null,
      shippingAddressSnapshot: header.shippingAddressSnapshot
        ? encodeJson(header.shippingAddressSnapshot)
        : null,
      billingAddressSnapshot: header.billingAddressSnapshot
        ? encodeJson(header.billingAddressSnapshot)
        : null,
      paymentTermSnapshot: null,
      rejectionReason: null,
      holdReason: null,
      closeReason: null,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  await db
    .insertInto("SalesOrderLine")
    .values(
      lines.map((line) => {
        const {
          itemId,
          description,
          quantity,
          unitPrice,
          currencyId,
          unitId,
          matchedPriceRuleId,
          billingPolicy,
          requestedShipDate,
          requiresPhysicalFulfillment,
          priceSnapshot,
          shippedQuantity,
          invoicedQuantity,
          ...lineCustomFields
        } = line;
        return {
          // Custom fields first so command-set columns always win.
          ...(lineCustomFields as Record<string, unknown>),
          salesOrderId: salesOrder.id,
          itemId,
          description: description ?? null,
          quantity,
          unitPrice,
          currencyId,
          unitId: unitId ?? null,
          matchedPriceRuleId: matchedPriceRuleId ?? null,
          billingPolicy: billingPolicy ?? "ORDER",
          requestedShipDate: requestedShipDate ?? null,
          requiresPhysicalFulfillment: requiresPhysicalFulfillment ?? true,
          priceSnapshot: priceSnapshot ? encodeJson(priceSnapshot) : null,
          shippedQuantity: shippedQuantity ?? 0,
          invoicedQuantity: invoicedQuantity ?? 0,
        };
      }),
    )
    .execute();

  return ok({ salesOrder });
}
