import type { Transaction } from "../generated/kysely-tailordb";
import {
  InvalidMovementTypeError,
  EmptyMoveLinesError,
  ItemNotFoundError,
  ItemNotActiveError,
  InvalidQuantityError,
  SameSourceDestinationError,
  StorageLocationNotFoundError,
  ReasonCodeRequiredError,
  LotRequiredForTrackedItemError,
} from "../lib/errors.generated";
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";

const VALID_MOVEMENT_TYPES = [
  "GOODS_RECEIPT",
  "GOODS_ISSUE",
  "INTERNAL_TRANSFER",
  "BLOCK",
  "UNBLOCK",
] as const;

type MovementType = (typeof VALID_MOVEMENT_TYPES)[number];

export interface MoveLineInput {
  itemId: string;
  quantity: number;
  sourceLocationId?: string | null;
  destinationLocationId?: string | null;
  lotId?: string | null;
  serialNumberId?: string | null;
}

export interface CreateStockMovementInput {
  movementType: string;
  reasonCode?: string | null;
  lines: MoveLineInput[];
}

export interface ItemManagementQueries {
  getItem: (
    db: unknown,
    input: { id: string },
    ctx: CommandContext,
  ) => Promise<{
    ok: true;
    value: { item: { id: string; status: string; isLotTracked?: boolean } | null };
  }>;
}

/**
 * Function: createStockMovement
 *
 * Creates a new stock movement in DRAFT status with one or more move lines.
 */
export async function run(
  db: Transaction,
  input: CreateStockMovementInput,
  ctx: CommandContext,
  itemManagementQueries: ItemManagementQueries,
) {
  const { movementType, reasonCode, lines } = input;

  // 1. Validate movementType
  if (!VALID_MOVEMENT_TYPES.includes(movementType as MovementType)) {
    return err(new InvalidMovementTypeError(movementType));
  }

  // 2. Validate lines not empty
  if (!lines || lines.length === 0) {
    return err(new EmptyMoveLinesError(movementType));
  }

  // 3. Validate each line
  for (const line of lines) {
    // Check item exists and is ACTIVE
    const itemResult = await itemManagementQueries.getItem(db, { id: line.itemId }, ctx);
    const item = itemResult.value.item;

    if (!item) {
      return err(new ItemNotFoundError(line.itemId));
    }

    if (item.status !== "ACTIVE") {
      return err(new ItemNotActiveError(line.itemId));
    }

    // Quantity > 0
    if (line.quantity <= 0) {
      return err(new InvalidQuantityError(line.itemId));
    }

    // Source != destination
    if (
      line.sourceLocationId &&
      line.destinationLocationId &&
      line.sourceLocationId === line.destinationLocationId
    ) {
      return err(new SameSourceDestinationError(line.sourceLocationId));
    }

    // Validate locations exist
    if (line.sourceLocationId) {
      const sourceLoc = await db
        .selectFrom("StorageLocation")
        .selectAll()
        .where("id", "=", line.sourceLocationId)
        .executeTakeFirst();
      if (!sourceLoc) {
        return err(new StorageLocationNotFoundError(line.sourceLocationId));
      }
    }

    if (line.destinationLocationId) {
      const destLoc = await db
        .selectFrom("StorageLocation")
        .selectAll()
        .where("id", "=", line.destinationLocationId)
        .executeTakeFirst();
      if (!destLoc) {
        return err(new StorageLocationNotFoundError(line.destinationLocationId));
      }
    }

    // Lot-tracked item requires lotId
    if (item.isLotTracked && !line.lotId) {
      return err(new LotRequiredForTrackedItemError(line.itemId));
    }

    // BLOCK/UNBLOCK require reasonCode
    if ((movementType === "BLOCK" || movementType === "UNBLOCK") && !reasonCode) {
      return err(new ReasonCodeRequiredError(movementType));
    }
  }

  // 4. Create the movement
  const movement = await db
    .insertInto("StockMovement")
    .values({
      movementType: movementType as MovementType,
      reasonCode: reasonCode ?? null,
      status: "DRAFT",
      executedAt: null,
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

  // 5. Create move lines
  const moveLines = [];
  for (const line of lines) {
    const moveLine = await db
      .insertInto("StockMoveLine")
      .values({
        stockMovementId: movement!.id,
        itemId: line.itemId,
        quantity: line.quantity,
        sourceLocationId: line.sourceLocationId ?? null,
        destinationLocationId: line.destinationLocationId ?? null,
        lotId: line.lotId ?? null,
        serialNumberId: line.serialNumberId ?? null,
        createdAt: new Date(),
        updatedAt: null,
      })
      .returningAll()
      .executeTakeFirst();
    moveLines.push(moveLine!);
  }

  return ok({ stockMovement: movement!, stockMoveLines: moveLines });
}
