import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import type { BillOfMaterialBomType } from "../generated/enums";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  ParentItemNotFoundError,
  ParentItemNotManufacturedError,
  ComponentLineRequiredError,
  InvalidComponentQuantityError,
  ComponentItemInactiveError,
  BomVersionConflictError,
} from "../lib/errors.generated";
import type { ItemManagementQueries } from "../module";

export interface CreateBillOfMaterialLineInput {
  itemId: string;
  requiredQuantity: number;
  unitOfMeasure?: string | null;
  scrapAssumption?: number | null;
  isSubassembly?: boolean | null;
}

export interface CreateBillOfMaterialInput {
  parentItemId: string;
  companyId: string;
  siteId?: string | null;
  bomType: BillOfMaterialBomType;
  effectivityStartDate?: Date | null;
  effectivityEndDate?: Date | null;
  defaultSelection?: boolean | null;
  revisionNumber?: string | null;
  lines: CreateBillOfMaterialLineInput[];
}

/**
 * Function: createBillOfMaterial
 *
 * Creates a draft BOM version for one manufactured parent item at company or
 * site scope. Establishes the initial bomType, effectivity window,
 * default-selection intent, and component-line payload.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: CreateBillOfMaterialInput & CF,
  ctx: CommandContext,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
) {
  const {
    parentItemId,
    companyId,
    siteId,
    bomType,
    effectivityStartDate,
    effectivityEndDate,
    defaultSelection,
    revisionNumber,
    lines,
    ...customFields
  } = input;

  // 1. Validate parent item exists
  const { item: parentItem } = (await itemManagementQueries.getItem(db, { id: parentItemId }, ctx))
    .value;

  if (!parentItem) {
    return err(new ParentItemNotFoundError(parentItemId));
  }

  // 2. Validate parent item is active and manufacturable.
  const parentItemRecord = parentItem as Record<string, unknown>;
  const isInactive = "status" in parentItemRecord && parentItemRecord.status !== "ACTIVE";
  const isNotManufacturable =
    "isManufacturable" in parentItemRecord && parentItemRecord.isManufacturable === false;

  if (isInactive || isNotManufacturable) {
    return err(new ParentItemNotManufacturedError(parentItemId));
  }

  // 3. Validate at least one component line
  if (!lines || lines.length === 0) {
    return err(new ComponentLineRequiredError(parentItemId));
  }

  // 4. Validate each component line
  for (const line of lines) {
    if (line.requiredQuantity <= 0) {
      return err(new InvalidComponentQuantityError(line.itemId));
    }

    // Check component item is not inactive (exists in Item table)
    const { item: componentItem } = (
      await itemManagementQueries.getItem(db, { id: line.itemId }, ctx)
    ).value;

    if (!componentItem) {
      return err(new ComponentItemInactiveError(line.itemId));
    }

    // Check component item is active
    if ("status" in componentItem && componentItem.status !== "ACTIVE") {
      return err(new ComponentItemInactiveError(line.itemId));
    }
  }

  // 5. Check version identity uniqueness within same parent item and scope
  const existingBomQuery = db
    .selectFrom("BillOfMaterial")
    .selectAll()
    .where("parentItemId", "=", parentItemId)
    .where("companyId", "=", companyId);

  const existingBom = await (
    siteId ? existingBomQuery.where("siteId", "=", siteId) : existingBomQuery
  )
    .where("revisionNumber", "=", revisionNumber ?? null)
    .forUpdate()
    .executeTakeFirst();

  if (existingBom) {
    return err(new BomVersionConflictError(parentItemId));
  }

  // 6. Create draft BOM
  const billOfMaterial = await db
    .insertInto("BillOfMaterial")
    .values({
      ...(customFields as Record<string, unknown>),
      parentItemId,
      companyId,
      siteId: siteId ?? null,
      bomType,
      effectivityStartDate: effectivityStartDate ?? null,
      effectivityEndDate: effectivityEndDate ?? null,
      defaultSelection: defaultSelection ?? null,
      revisionNumber: revisionNumber ?? null,
      status: "DRAFT",
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  // 7. Create component lines (bulk insert)
  if (lines.length > 0) {
    await db
      .insertInto("BillOfMaterialLine")
      .values(
        lines.map((line) => ({
          billOfMaterialId: billOfMaterial.id,
          itemId: line.itemId,
          requiredQuantity: line.requiredQuantity,
          unitOfMeasure: line.unitOfMeasure ?? null,
          scrapAssumption: line.scrapAssumption ?? null,
          isSubassembly: line.isSubassembly ?? null,
        })),
      )
      .execute();
  }

  return ok({ billOfMaterial });
}
