import type { Transaction } from "../generated/kysely-tailordb";
import {
  RoutingNotFoundError,
  RoutingNotMutableError,
  DuplicateOperationSequenceError,
  InvalidStandardTimeError,
  WorkCenterNotFoundError,
  CrossCompanyWorkCenterError,
} from "../lib/errors.generated";
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";

export interface UpdateRoutingOperationInput {
  sequenceNumber: number;
  operationDescription?: string | null;
  workCenterId: string;
  standardSetupTime: number;
  standardRunTime: number;
  operatorInstructions?: string | null;
}

export type UpdateRoutingInput = {
  id: string;
  revisionNumber?: string | null;
  operations: UpdateRoutingOperationInput[];
};
/**
 * Function: updateRouting
 *
 * Updates a draft routing's operations. Only DRAFT routings are mutable.
 * Replaces all existing operations with the revised set.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: UpdateRoutingInput & Omit<Partial<CF>, "status">,
  _ctx: CommandContext,
) {
  const { id, revisionNumber, operations, ...customFields } = input;

  // 1. Fetch routing
  const routing = await db
    .selectFrom("Routing")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();

  if (!routing) {
    return err(new RoutingNotFoundError(id));
  }

  // 2. Verify DRAFT status
  if (routing.status !== "DRAFT") {
    return err(new RoutingNotMutableError(id));
  }

  // 3. Validate unique sequence numbers
  const seqNumbers = operations.map((op) => op.sequenceNumber);
  const uniqueSeqs = new Set(seqNumbers);
  if (uniqueSeqs.size !== seqNumbers.length) {
    return err(new DuplicateOperationSequenceError(id));
  }

  // 4. Validate standard times >= 0
  for (const op of operations) {
    if (op.standardSetupTime < 0 || op.standardRunTime < 0) {
      return err(new InvalidStandardTimeError(id));
    }
  }

  // 5. Validate work centers exist and belong to same company
  for (const op of operations) {
    const workCenter = await db
      .selectFrom("WorkCenter")
      .selectAll()
      .where("id", "=", op.workCenterId)
      .executeTakeFirst();

    if (!workCenter) {
      return err(new WorkCenterNotFoundError(op.workCenterId));
    }

    if (workCenter.companyId !== routing.companyId) {
      return err(new CrossCompanyWorkCenterError(op.workCenterId));
    }
  }

  // 6. Update routing header
  const updatedRouting = await db
    .updateTable("Routing")
    .set({
      ...(customFields as Record<string, unknown>),
      ...(revisionNumber !== undefined ? { revisionNumber } : {}),
      updatedAt: new Date(),
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirst();

  // 7. Delete existing operations and replace
  await db.deleteFrom("RoutingOperation").where("routingId", "=", id).execute();

  const createdOperations = [];
  for (const op of operations) {
    const operation = await db
      .insertInto("RoutingOperation")
      .values({
        routingId: id,
        sequenceNumber: op.sequenceNumber,
        operationDescription: op.operationDescription ?? null,
        workCenterId: op.workCenterId,
        standardSetupTime: op.standardSetupTime,
        standardRunTime: op.standardRunTime,
        operatorInstructions: op.operatorInstructions ?? null,
        createdAt: new Date(),
        updatedAt: null,
      })
      .returningAll()
      .executeTakeFirst();
    createdOperations.push(operation!);
  }

  return ok({ routing: updatedRouting!, routingOperations: createdOperations });
}
