import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import type { StockReservationSourceType } from "../generated/enums";
import type { Transaction } from "../generated/kysely-tailordb";
import { calculateSiteAvailableExcludingReservation } from "../internal/stockReservation";
import {
  InsufficientAvailableStockError,
  InvalidReservationQuantitiesError,
  ItemNotFoundError,
  SiteNotFoundError,
  StorageLocationNotFoundError,
} from "../lib/errors.generated";
import type { ItemManagementQueries, OrganizationQueries } from "../module";

export interface CreateStockReservationInput {
  sourceType: StockReservationSourceType;
  sourceId: string;
  sourceLineId: string;
  itemId: string;
  siteId: string;
  storageLocationId?: string | null;
  reservedQuantity: string;
  requiredDate: Date;
}

export async function run(
  db: Transaction,
  input: CreateStockReservationInput,
  ctx: CommandContext,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
  organizationQueries: Pick<OrganizationQueries, "getSite">,
) {
  const {
    sourceType,
    sourceId,
    sourceLineId,
    itemId,
    siteId,
    storageLocationId,
    reservedQuantity: inputReservedQuantity,
    requiredDate,
  } = input;

  const reservedQuantity = new Decimal(inputReservedQuantity);
  if (reservedQuantity.lte(0)) {
    return err(new InvalidReservationQuantitiesError(inputReservedQuantity));
  }

  const { item } = (await itemManagementQueries.getItem(db, { id: itemId }, ctx)).value;
  if (!item) {
    return err(new ItemNotFoundError(itemId));
  }

  const { site } = (await organizationQueries.getSite(db, { id: siteId }, ctx)).value;
  if (!site) {
    return err(new SiteNotFoundError(siteId));
  }

  if (storageLocationId) {
    const location = await db
      .selectFrom("StorageLocation")
      .selectAll()
      .where("id", "=", storageLocationId)
      .where("siteId", "=", siteId)
      .executeTakeFirst();

    if (!location) {
      return err(new StorageLocationNotFoundError(storageLocationId));
    }
  }

  const available = await calculateSiteAvailableExcludingReservation(db, itemId, siteId);
  if (!available || available.lt(reservedQuantity)) {
    return err(new InsufficientAvailableStockError(`${itemId}:${siteId}`));
  }

  const stockReservation = await db
    .insertInto("StockReservation")
    .values({
      sourceType,
      sourceId,
      sourceLineId,
      itemId,
      siteId,
      storageLocationId: storageLocationId ?? null,
      reservedQuantity: inputReservedQuantity,
      consumedQuantity: "0",
      status: "OPEN",
      requiredDate,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ stockReservation });
}
