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

const VALID_BOM_TYPES = ["MANUFACTURE", "PHANTOM", "KIT"] as const;

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: string;
  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,
) {
  const {
    parentItemId,
    companyId,
    siteId,
    bomType,
    effectivityStartDate,
    effectivityEndDate,
    defaultSelection,
    revisionNumber,
    lines,
    ...customFields
  } = input;

  // 1. Validate parent item exists
  const parentItem = await db
    .selectFrom("Item")
    .selectAll()
    .where("id", "=", parentItemId)
    .executeTakeFirst();

  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") ||
    ("isActive" in parentItemRecord && parentItemRecord.isActive === false);
  const isNotManufacturable =
    "isManufacturable" in parentItemRecord && parentItemRecord.isManufacturable === false;

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

  // 3. Validate bomType
  if (!VALID_BOM_TYPES.includes(bomType as (typeof VALID_BOM_TYPES)[number])) {
    return err(new InvalidBomTypeError(parentItemId));
  }

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

  // 5. 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 componentItem = await db
      .selectFrom("Item")
      .selectAll()
      .where("id", "=", line.itemId)
      .executeTakeFirst();

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

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

  // 6. 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));
  }

  // 7. Create draft BOM
  const billOfMaterial = await db
    .insertInto("BillOfMaterial")
    .values({
      ...(customFields as Record<string, unknown>),
      parentItemId,
      companyId,
      siteId: siteId ?? null,
      bomType: bomType as "MANUFACTURE" | "PHANTOM" | "KIT",
      effectivityStartDate: effectivityStartDate ?? null,
      effectivityEndDate: effectivityEndDate ?? null,
      defaultSelection: defaultSelection ?? null,
      revisionNumber: revisionNumber ?? null,
      status: "DRAFT",
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

  // 8. Create component lines
  for (const line of lines) {
    await db
      .insertInto("BillOfMaterialLine")
      .values({
        billOfMaterialId: billOfMaterial!.id,
        itemId: line.itemId,
        requiredQuantity: line.requiredQuantity,
        unitOfMeasure: line.unitOfMeasure ?? null,
        scrapAssumption: line.scrapAssumption ?? null,
        isSubassembly: line.isSubassembly ?? null,
        createdAt: new Date(),
        updatedAt: null,
      })
      .returningAll()
      .executeTakeFirst();
  }

  return ok({ billOfMaterial: billOfMaterial! });
}
