import type { Transaction } from "../generated/kysely-tailordb";
import {
  BomNotFoundError,
  BomNotMutableError,
  InvalidComponentQuantityError,
  ComponentItemInactiveError,
  AmbiguousEffectivityRuleError,
} from "../lib/errors.generated";
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";

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

export type UpdateBillOfMaterialInput = {
  id: string;
  bomType?: string | null;
  effectivityStartDate?: Date | null;
  effectivityEndDate?: Date | null;
  defaultSelection?: boolean | null;
  revisionNumber?: string | null;
  lines?: UpdateBillOfMaterialLineInput[];
};
/**
 * Function: updateBillOfMaterial
 *
 * Revises mutable draft BOM content before activation. Supports changes to
 * effectivity, default flags, bomType, and component lines while preserving
 * released production-order snapshots.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: UpdateBillOfMaterialInput & Omit<Partial<CF>, "status">,
  _ctx: CommandContext,
) {
  const {
    id,
    bomType,
    effectivityStartDate,
    effectivityEndDate,
    defaultSelection,
    revisionNumber,
    lines,
    ...customFields
  } = input;

  // 1. Fetch BOM with lock
  const bom = await db
    .selectFrom("BillOfMaterial")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();

  if (!bom) {
    return err(new BomNotFoundError(id));
  }

  // 2. Only DRAFT BOMs are mutable
  if (bom.status !== "DRAFT") {
    return err(new BomNotMutableError(id));
  }

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

      // Check component item is not inactive
      const componentItem = await db
        .selectFrom("Item")
        .selectAll()
        .where("id", "=", line.itemId)
        .executeTakeFirst();

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

  // 4. Check effectivity ambiguity — if effectivity or default selection changed,
  //    verify no ambiguous active-selection plan would result once activated
  const effectiveDefaultSelection =
    defaultSelection !== undefined ? defaultSelection : bom.defaultSelection;
  const effectiveStartDate =
    effectivityStartDate !== undefined ? effectivityStartDate : bom.effectivityStartDate;
  const effectiveEndDate =
    effectivityEndDate !== undefined ? effectivityEndDate : bom.effectivityEndDate;

  if (
    defaultSelection !== undefined ||
    effectivityStartDate !== undefined ||
    effectivityEndDate !== undefined
  ) {
    // Find other active BOMs for same parent item and scope that could conflict
    const conflictingBom = await db
      .selectFrom("BillOfMaterial")
      .selectAll()
      .where("parentItemId", "=", bom.parentItemId)
      .where("companyId", "=", bom.companyId)
      .where("id", "!=", id)
      .where("status", "=", "ACTIVE")
      .where("defaultSelection", "=", effectiveDefaultSelection ?? false)
      .executeTakeFirst();

    if (conflictingBom && effectiveDefaultSelection) {
      // Check date overlap
      const hasOverlap =
        !effectiveStartDate ||
        !conflictingBom.effectivityEndDate ||
        effectiveStartDate <= conflictingBom.effectivityEndDate;
      const hasOverlap2 =
        !effectiveEndDate ||
        !conflictingBom.effectivityStartDate ||
        effectiveEndDate >= conflictingBom.effectivityStartDate;

      if (hasOverlap && hasOverlap2) {
        return err(new AmbiguousEffectivityRuleError(id));
      }
    }
  }

  // 5. Build update set for BOM header
  const updateSet: Record<string, unknown> = {
    ...customFields,
    updatedAt: new Date(),
  };

  if (bomType !== undefined) updateSet.bomType = bomType;
  if (effectivityStartDate !== undefined) updateSet.effectivityStartDate = effectivityStartDate;
  if (effectivityEndDate !== undefined) updateSet.effectivityEndDate = effectivityEndDate;
  if (defaultSelection !== undefined) updateSet.defaultSelection = defaultSelection;
  if (revisionNumber !== undefined) updateSet.revisionNumber = revisionNumber;

  // 6. Persist BOM header changes
  const updatedBom = await db
    .updateTable("BillOfMaterial")
    .set(updateSet)
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirstOrThrow();

  // 7. Replace lines if provided (delete old, insert new)
  if (lines) {
    await db.deleteFrom("BillOfMaterialLine").where("billOfMaterialId", "=", id).execute();

    for (const line of lines) {
      await db
        .insertInto("BillOfMaterialLine")
        .values({
          billOfMaterialId: 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: updatedBom });
}
