import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { purchaseOrderLifecycle } from "../db/purchaseOrder.lifecycle.generated";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  ItemNotActiveError,
  PartnerNotSupplierError,
  PurchaseOrderNotFoundError,
  PurchaseOrderNotSubmittedError,
  SupplierNotActiveError,
} from "../lib/errors.generated";
import type { BusinessPartnerQueries, InventoryCommands, ItemManagementQueries } from "../module";

export interface ApprovePurchaseOrderInput {
  id: string;
}

export async function run(
  db: Transaction,
  input: ApprovePurchaseOrderInput,
  ctx: CommandContext,
  businessPartnerQueries: Pick<BusinessPartnerQueries, "getPartner" | "getPartnerRole">,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
  inventoryCommands: Pick<InventoryCommands, "createInventorySupplyPlan">,
) {
  const purchaseOrder = await db
    .selectFrom("PurchaseOrder")
    .selectAll()
    .where("id", "=", input.id)
    .forUpdate()
    .executeTakeFirst();
  if (!purchaseOrder) {
    return err(new PurchaseOrderNotFoundError(input.id));
  }
  const nextOrderStatus = purchaseOrderLifecycle.tryTransition(
    purchaseOrder.orderStatus,
    "approve",
  );
  if (!nextOrderStatus) {
    return err(new PurchaseOrderNotSubmittedError(input.id));
  }

  const { partner: supplier } = (
    await businessPartnerQueries.getPartner(db, { id: purchaseOrder.supplierId }, ctx)
  ).value;
  if (supplier?.status !== "ACTIVE") {
    return err(new SupplierNotActiveError(purchaseOrder.supplierId));
  }
  const { partnerRole } = (
    await businessPartnerQueries.getPartnerRole(
      db,
      { partnerId: purchaseOrder.supplierId, role: "SUPPLIER" },
      ctx,
    )
  ).value;
  if (!partnerRole) {
    return err(new PartnerNotSupplierError(purchaseOrder.supplierId));
  }

  const lines = await db
    .selectFrom("PurchaseOrderLine")
    .selectAll()
    .where("purchaseOrderId", "=", input.id)
    .execute();

  for (const line of lines) {
    const { item } = (await itemManagementQueries.getItem(db, { id: line.itemId }, ctx)).value;
    if (item?.status !== "ACTIVE") {
      return err(new ItemNotActiveError(line.itemId));
    }
  }

  const approved = await db
    .updateTable("PurchaseOrder")
    .set({
      orderStatus: nextOrderStatus,
      receiptStatus: "NOT_RECEIVED",
      billingStatus: "NOT_BILLED",
    })
    .where("id", "=", input.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  const supplyPlansToCreate: Array<{
    sourceLineId: string;
    itemId: string;
    siteId: string;
    expectedQuantity: string;
    unitId: string;
    expectedDate: Date;
  }> = [];
  for (const line of lines) {
    if (!line.requiresPhysicalReceipt) continue;
    const siteId = line.receivingSiteId ?? approved.receivingSiteId;
    if (!siteId) continue;

    supplyPlansToCreate.push({
      sourceLineId: line.id,
      itemId: line.itemId,
      siteId,
      expectedQuantity: line.quantity,
      unitId: line.unitId,
      expectedDate: approved.orderDate,
    });
  }

  if (supplyPlansToCreate.length > 0) {
    const syncResult = await inventoryCommands.createInventorySupplyPlan(
      db,
      { sourceType: "PURCHASE_ORDER", sourceId: approved.id, supplyPlans: supplyPlansToCreate },
      ctx,
    );
    if (!syncResult.ok) return syncResult;
  }

  return ok({ purchaseOrder: approved });
}
