import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import { billOfMaterialLifecycle } from "../db/billOfMaterial.lifecycle.generated";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  BomNotFoundError,
  BomNotActivatableError,
  ComponentLineRequiredError,
  ComponentItemInactiveError,
  CircularBomReferenceError,
  EffectivityConflictError,
} from "../lib/errors.generated";
import type { ItemManagementQueries } from "../module";

export interface ActivateBillOfMaterialInput {
  id: string;
}

/**
 * Function: activateBillOfMaterial
 *
 * Validates a draft BOM version and makes it selectable for production-order
 * release. Activation enforces circular-reference checks, effectivity
 * conflicts, and component readiness.
 */
export async function run(
  db: Transaction,
  input: ActivateBillOfMaterialInput,
  ctx: CommandContext,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
) {
  const { id } = 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. Validate status is activatable
  const nextStatus = billOfMaterialLifecycle.tryTransition(bom.status, "activate");
  if (!nextStatus) {
    return err(new BomNotActivatableError(id));
  }

  // 3. Validate at least one component line exists
  const lines = await db
    .selectFrom("BillOfMaterialLine")
    .selectAll()
    .where("billOfMaterialId", "=", id)
    .execute();

  if (!lines || lines.length === 0) {
    return err(new ComponentLineRequiredError(id));
  }

  // 4. Validate all component items are active
  for (const line of lines) {
    const { item: componentItem } = (
      await itemManagementQueries.getItem(db, { id: line.itemId }, ctx)
    ).value;

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

  // 5. Check for circular references using DFS traversal
  const circularResult = await detectCircularReference(
    db,
    lines,
    new Set<string>([bom.parentItemId]),
  );
  if (circularResult) {
    return err(new CircularBomReferenceError(id));
  }

  // 6. Check effectivity conflicts — overlapping active effectivity windows
  //    for the same parent item and scope
  const conflictingBom = await db
    .selectFrom("BillOfMaterial")
    .selectAll()
    .where("parentItemId", "=", bom.parentItemId)
    .where("companyId", "=", bom.companyId)
    .where("id", "!=", id)
    .where("status", "=", "ACTIVE")
    .executeTakeFirst();

  if (conflictingBom) {
    // Check date overlap
    const bomStart = bom.effectivityStartDate;
    const bomEnd = bom.effectivityEndDate;
    const conflictStart = conflictingBom.effectivityStartDate;
    const conflictEnd = conflictingBom.effectivityEndDate;

    const hasOverlap =
      (!bomStart || !conflictEnd || bomStart <= conflictEnd) &&
      (!bomEnd || !conflictStart || bomEnd >= conflictStart);

    if (hasOverlap && bom.defaultSelection && conflictingBom.defaultSelection) {
      return err(new EffectivityConflictError(id));
    }
  }

  // 7. Set status to ACTIVE
  const updatedBom = await db
    .updateTable("BillOfMaterial")
    .set({
      status: nextStatus,
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ billOfMaterial: updatedBom });
}

async function detectCircularReference(
  db: Transaction,
  lines: { itemId: string; isSubassembly: boolean | null }[],
  visitedItems: Set<string>,
): Promise<boolean> {
  for (const line of lines) {
    // If this item has already been visited in the path, we have a cycle
    if (visitedItems.has(line.itemId)) {
      return true;
    }

    if (!line.isSubassembly) continue;

    // Find ALL child BOMs for this item (multiple versions may exist)
    const childBoms = await db
      .selectFrom("BillOfMaterial")
      .selectAll()
      .where("parentItemId", "=", line.itemId)
      .where("status", "in", ["DRAFT", "ACTIVE"])
      .execute();

    if (childBoms.length > 0) {
      visitedItems.add(line.itemId);

      for (const childBom of childBoms) {
        const childLines = await db
          .selectFrom("BillOfMaterialLine")
          .selectAll()
          .where("billOfMaterialId", "=", childBom.id)
          .execute();

        const hasCycle = await detectCircularReference(db, childLines, visitedItems);

        if (hasCycle) return true;
      }

      visitedItems.delete(line.itemId);
    }
  }
  return false;
}
