import type { Transaction } from "../generated/kysely-tailordb";
import {
  WorkOrderNotFoundError,
  WorkOrderNotCompletableError,
  WorkOrderNotStartedError,
  InvalidCompletionQuantityError,
  DuplicateBackflushRiskError,
  ReceiptHandoffRequiredError,
  LotReferenceRequiredError,
  SerialReferenceRequiredError,
} from "../lib/errors.generated";
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";

export interface ReceiptData {
  itemReference?: string | null;
  unitOfMeasure?: string | null;
  siteReference?: string | null;
  postingDate?: Date | null;
  storageLocationReference?: string | null;
  lotTracked: boolean;
  serialTracked: boolean;
  finishedGoodLotReference?: string | null;
  serialReferences?: string[] | null;
}

export interface ZeroQuantityBypassPolicy {
  allowZeroCompletion: boolean;
  reasonCode?: string | null;
}

export interface CompleteWorkOrderInput {
  id: string;
  completedQuantity: number;
  zeroQuantityBypassPolicy?: ZeroQuantityBypassPolicy | null;
  backflushRequired: boolean;
  manuallyIssuedQuantity?: number;
  receiptRequired: boolean;
  receiptData?: ReceiptData | null;
  notes?: string | null;
}

/**
 * Function: completeWorkOrder
 *
 * Finishes execution on an in-progress work order. Records the final completed
 * quantity, validates backflush and receipt-handoff obligations, creates a
 * COMPLETED execution event, and rolls up completion to the parent production
 * order when all sibling work orders are finished.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: CompleteWorkOrderInput & CF,
  _ctx: CommandContext,
) {
  const {
    id,
    completedQuantity,
    zeroQuantityBypassPolicy,
    backflushRequired,
    manuallyIssuedQuantity,
    receiptRequired,
    receiptData,
    notes,
    ...customFields
  } = input;
  void customFields;

  // 1. Fetch work order with lock
  const workOrder = await db
    .selectFrom("WorkOrder")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();

  if (!workOrder) {
    return err(new WorkOrderNotFoundError(id));
  }

  // 2. Validate status is IN_PROGRESS
  if (workOrder.status !== "IN_PROGRESS") {
    return err(new WorkOrderNotCompletableError(id));
  }

  // 3. Validate actual start evidence exists
  if (!workOrder.actualStartDate) {
    return err(new WorkOrderNotStartedError(id));
  }

  // 4. Validate completed quantity, allowing explicit zero-quantity bypass.
  const zeroQuantityBypassAllowed =
    completedQuantity === 0 && zeroQuantityBypassPolicy?.allowZeroCompletion === true;
  if (completedQuantity < 0 || (completedQuantity === 0 && !zeroQuantityBypassAllowed)) {
    return err(new InvalidCompletionQuantityError(id));
  }

  // 5. Validate backflush does not duplicate manual issue
  if (backflushRequired && manuallyIssuedQuantity != null && manuallyIssuedQuantity > 0) {
    return err(new DuplicateBackflushRiskError(id));
  }

  // 6. Validate receipt handoff data when receipt is required
  if (receiptRequired) {
    if (!receiptData) {
      return err(new ReceiptHandoffRequiredError(id));
    }

    if (
      !receiptData.itemReference ||
      !receiptData.unitOfMeasure ||
      !receiptData.siteReference ||
      !receiptData.postingDate
    ) {
      return err(new ReceiptHandoffRequiredError(id));
    }

    if (receiptData.lotTracked && !receiptData.finishedGoodLotReference) {
      return err(new LotReferenceRequiredError(id));
    }

    if (
      receiptData.serialTracked &&
      (!receiptData.serialReferences || receiptData.serialReferences.length === 0)
    ) {
      return err(new SerialReferenceRequiredError(id));
    }
  }

  // 7. Update work order to COMPLETE
  const now = new Date();
  const updatedWorkOrder = await db
    .updateTable("WorkOrder")
    .set({
      status: "COMPLETE",
      completedQuantity: workOrder.completedQuantity + completedQuantity,
      executionNotes: notes ?? workOrder.executionNotes,
      updatedAt: now,
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirstOrThrow();

  // 8. Create COMPLETED execution event
  await db
    .insertInto("WorkOrderExecutionEvent")
    .values({
      workOrderId: id,
      eventType: "COMPLETED",
      timestamp: now,
      quantity: completedQuantity,
      timeValue: null,
      scrapValue: null,
      notes: notes ?? null,
      createdAt: now,
      updatedAt: null,
    })
    .execute();

  const backflushHandoff = backflushRequired
    ? {
        productionOrderReference: workOrder.productionOrderId,
        workOrderReference: id,
        completedQuantity,
        manuallyIssuedQuantity: manuallyIssuedQuantity ?? 0,
        postingDate: receiptData?.postingDate ?? now,
        bypassReason: zeroQuantityBypassAllowed
          ? (zeroQuantityBypassPolicy?.reasonCode ?? null)
          : null,
      }
    : null;

  const receiptHandoff =
    receiptRequired && receiptData
      ? {
          productionOrderReference: workOrder.productionOrderId,
          workOrderReference: id,
          itemReference: receiptData.itemReference,
          quantity: completedQuantity,
          unitOfMeasure: receiptData.unitOfMeasure,
          siteReference: receiptData.siteReference,
          postingDate: receiptData.postingDate,
          storageLocationReference: receiptData.storageLocationReference ?? null,
          finishedGoodLotReference: receiptData.finishedGoodLotReference ?? null,
          serialReferences: receiptData.serialReferences ?? null,
        }
      : null;

  // 9. Roll up to parent production order
  const siblingWorkOrders = await db
    .selectFrom("WorkOrder")
    .selectAll()
    .where("productionOrderId", "=", workOrder.productionOrderId)
    .execute();

  const allComplete = (siblingWorkOrders as { id: string; status: string }[]).every((wo) => {
    if (wo.id === id) return true; // this one was just completed
    return wo.status === "COMPLETE" || wo.status === "CANCELLED";
  });

  if (allComplete) {
    await db
      .updateTable("ProductionOrder")
      .set({
        status: "COMPLETED",
        updatedAt: now,
      })
      .where("id", "=", workOrder.productionOrderId)
      .execute();
  }

  return ok({ workOrder: updatedWorkOrder, backflushHandoff, receiptHandoff });
}
