import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import { transferOrderLifecycle } from "../db/transferOrder.lifecycle.generated";
import type { Transaction } from "../generated/kysely-tailordb";
import { getOpenStockReservationQuantity } from "../internal/stockReservation";
import {
  EmptyTransferLinesError,
  InsufficientAvailableStockError,
  InvalidStateTransitionError,
  TransferOrderNotFoundError,
} from "../lib/errors.generated";

export interface OpenTransferOrderInput {
  id: string;
}

export async function run(db: Transaction, input: OpenTransferOrderInput, _ctx: CommandContext) {
  const existing = await db
    .selectFrom("TransferOrder")
    .selectAll()
    .where("id", "=", input.id)
    .forUpdate()
    .executeTakeFirst();
  if (!existing) {
    return err(new TransferOrderNotFoundError(input.id));
  }

  const nextStatus = transferOrderLifecycle.tryTransition(existing.status, "open");
  if (!nextStatus) {
    return err(new InvalidStateTransitionError(input.id));
  }

  const lines = await db
    .selectFrom("TransferOrderLine")
    .selectAll()
    .where("transferOrderId", "=", input.id)
    .orderBy("id", "asc")
    .execute();
  if (lines.length === 0) {
    return err(new EmptyTransferLinesError(input.id));
  }

  const demandByItem = new Map<string, Decimal>();
  for (const line of lines) {
    demandByItem.set(
      line.itemId,
      (demandByItem.get(line.itemId) ?? new Decimal(0)).plus(line.orderedQuantity),
    );
  }

  const itemIds = [...demandByItem.keys()];
  const locationIds = await db
    .selectFrom("StorageLocation")
    .select("id")
    .where("siteId", "=", existing.sourceSiteId)
    .where("status", "=", "ACTIVE")
    .execute();
  if (locationIds.length === 0) {
    return err(new InsufficientAvailableStockError(existing.sourceSiteId));
  }

  const sourceSiteStockLevels = await db
    .selectFrom("StockLevel")
    .selectAll()
    .where("itemId", "in", itemIds)
    .where(
      "storageLocationId",
      "in",
      locationIds.map((location) => location.id),
    )
    .where("stockType", "=", "AVAILABLE")
    .orderBy("storageLocationId", "asc")
    .forUpdate()
    .execute();
  const openReservations = await db
    .selectFrom("StockReservation")
    .selectAll()
    .where("itemId", "in", itemIds)
    .where("siteId", "=", existing.sourceSiteId)
    .where("status", "=", "OPEN")
    .execute();

  const availableByItem = new Map<string, Decimal>();
  for (const stockLevel of sourceSiteStockLevels) {
    availableByItem.set(
      stockLevel.itemId,
      (availableByItem.get(stockLevel.itemId) ?? new Decimal(0)).plus(stockLevel.quantity),
    );
  }
  for (const reservation of openReservations) {
    availableByItem.set(
      reservation.itemId,
      (availableByItem.get(reservation.itemId) ?? new Decimal(0)).minus(
        getOpenStockReservationQuantity(reservation),
      ),
    );
  }

  for (const [itemId, demandQuantity] of demandByItem.entries()) {
    const availableQuantity = availableByItem.get(itemId);
    if (!availableQuantity || availableQuantity.lt(demandQuantity)) {
      return err(new InsufficientAvailableStockError(`${itemId}:${existing.sourceSiteId}`));
    }
  }

  const transferOrder = await db
    .updateTable("TransferOrder")
    .set({ status: nextStatus })
    .where("id", "=", input.id)
    .returningAll()
    .executeTakeFirstOrThrow();

  const stockReservations = await db
    .insertInto("StockReservation")
    .values(
      lines.map((line) => ({
        sourceType: "TRANSFER_ORDER" as const,
        sourceId: existing.id,
        sourceLineId: line.id,
        itemId: line.itemId,
        siteId: existing.sourceSiteId,
        storageLocationId: null,
        reservedQuantity: line.orderedQuantity,
        consumedQuantity: "0",
        status: "OPEN" as const,
        requiredDate: existing.plannedShipmentDate,
      })),
    )
    .returningAll()
    .execute();

  const supplyPlans = await db
    .insertInto("InventorySupplyPlan")
    .values(
      lines.map((line) => ({
        sourceType: "TRANSFER_ORDER" as const,
        sourceId: existing.id,
        sourceLineId: line.id,
        itemId: line.itemId,
        siteId: existing.destinationSiteId,
        expectedQuantity: line.orderedQuantity,
        receivedQuantity: "0",
        status: "OPEN" as const,
        expectedDate: existing.expectedReceiptDate,
        lotId: line.lotId,
        serialNumberId: line.serialNumberId,
      })),
    )
    .returningAll()
    .execute();

  return ok({ transferOrder, stockReservations, supplyPlans });
}
