import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import { stockAdjustmentLifecycle } from "../db/stockAdjustment.lifecycle.generated";
import type { Selectable, Transaction } from "../generated/kysely-tailordb";
import {
  StockAdjustmentNotFoundError,
  InvalidStatusError,
  ItemNotFoundError,
} from "../lib/errors.generated";
import type { ItemManagementQueries } from "../module";

export interface SubmitStockAdjustmentInput {
  id: string;
}

type StockAdjustment = Selectable<"StockAdjustment">;
type StockAdjustmentLine = Selectable<"StockAdjustmentLine">;
type ExecutionEffect = {
  direction: "IN" | "OUT";
  stockType: "AVAILABLE" | "BLOCKED" | "IN_TRANSIT";
};

function getExecutionEffects(
  adjustment: StockAdjustment,
  line: StockAdjustmentLine,
): ExecutionEffect[] {
  if (adjustment.adjustmentType === "CORRECTION") {
    return [
      {
        direction: line.adjustmentDirection === "INCREASE" ? "IN" : "OUT",
        stockType: "AVAILABLE",
      },
    ];
  }

  if (adjustment.adjustmentType === "SCRAP") {
    return [
      {
        direction: "OUT",
        stockType: line.fromStockCategory === "BLOCKED" ? "BLOCKED" : "AVAILABLE",
      },
    ];
  }

  if (adjustment.adjustmentType === "BLOCK") {
    return [
      { direction: "OUT", stockType: "AVAILABLE" },
      { direction: "IN", stockType: "BLOCKED" },
    ];
  }

  return [
    { direction: "IN", stockType: "AVAILABLE" },
    { direction: "OUT", stockType: "BLOCKED" },
  ];
}

/**
 * Function: submitStockAdjustment
 *
 * Transitions a stock adjustment from DRAFT to SUBMITTED status.
 * Creates a DRAFT InventoryExecution snapshot; no inventory changes occur at this stage.
 */
export async function run(
  db: Transaction,
  input: SubmitStockAdjustmentInput,
  ctx: CommandContext,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
) {
  const adjustment = await db
    .selectFrom("StockAdjustment")
    .selectAll()
    .where("id", "=", input.id)
    .forUpdate()
    .executeTakeFirst();

  if (!adjustment) {
    return err(new StockAdjustmentNotFoundError(input.id));
  }

  const nextStatus = stockAdjustmentLifecycle.tryTransition(adjustment.status, "submit");
  if (!nextStatus) {
    return err(new InvalidStatusError(input.id));
  }

  const lines = await db
    .selectFrom("StockAdjustmentLine")
    .selectAll()
    .where("stockAdjustmentId", "=", input.id)
    .execute();

  const executionLineInputs = [];
  for (const line of lines) {
    const { item } = (await itemManagementQueries.getItem(db, { id: line.itemId }, ctx)).value;
    if (!item) {
      return err(new ItemNotFoundError(line.itemId));
    }

    for (const effect of getExecutionEffects(adjustment, line)) {
      executionLineInputs.push({
        direction: effect.direction,
        itemId: line.itemId,
        quantity: line.quantity,
        unitId: item.unitId,
        primaryQuantity: line.quantity,
        primaryUnitId: item.unitId,
        unitConversionRate: "1",
        storageLocationId: line.storageLocationId,
        stockType: effect.stockType,
        sourceLineId: line.id,
        lotId: line.lotId ?? null,
        serialNumberId: line.serialNumberId ?? null,
      });
    }
  }

  const inventoryExecution = await db
    .insertInto("InventoryExecution")
    .values({
      executionType: "STOCK_ADJUSTMENT",
      status: "DRAFT",
      effectiveDate: adjustment.adjustmentDate,
      postedAt: null,
      sourceDocumentType: "STOCK_ADJUSTMENT",
      sourceDocumentId: adjustment.id,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  const inventoryExecutionLines =
    executionLineInputs.length > 0
      ? await db
          .insertInto("InventoryExecutionLine")
          .values(
            executionLineInputs.map((executionLine) => ({
              ...executionLine,
              inventoryExecutionId: inventoryExecution.id,
            })),
          )
          .returningAll()
          .execute()
      : [];

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

  return ok({ stockAdjustment, inventoryExecution, inventoryExecutionLines });
}
