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

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

export type UpdateStockMovementInput = {
  id: string;
  reasonCode?: string | null;
  lines?: UpdateMoveLineInput[];
};

export interface ItemManagementQueries {
  getItem: (
    db: unknown,
    input: { id: string },
    ctx: CommandContext,
  ) => Promise<{
    ok: true;
    value: { item: { id: string; status: string; isLotTracked?: boolean } | null };
  }>;
}
/**
 * Function: updateStockMovement
 *
 * Updates a stock movement that is still in DRAFT status.
 * Fields can be updated and move lines can be replaced.
 * Same validation rules as creation apply to new lines.
 */
export async function run(
  db: Transaction,
  input: UpdateStockMovementInput,
  ctx: CommandContext,
  itemManagementQueries: ItemManagementQueries,
) {
  const { id, reasonCode, lines } = input;

  // 1. Check movement exists
  const movement = await db
    .selectFrom("StockMovement")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();

  if (!movement) {
    return err(new StockMovementNotFoundError(id));
  }

  // 2. Must be DRAFT
  if (movement.status !== "DRAFT") {
    return err(new StockMovementNotDraftError(id));
  }

  // 3. Update movement fields
  const updatedMovement = await db
    .updateTable("StockMovement")
    .set({
      reasonCode: reasonCode !== undefined ? (reasonCode ?? null) : movement.reasonCode,
      updatedAt: new Date(),
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirst();

  // 4. If lines provided, validate then delete old lines and re-create
  if (lines) {
    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));
      }

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

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

      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));
      }
    }

    await db.deleteFrom("StockMoveLine").where("stockMovementId", "=", id).execute();

    const newLines = [];
    for (const line of lines) {
      const moveLine = await db
        .insertInto("StockMoveLine")
        .values({
          stockMovementId: 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();
      newLines.push(moveLine!);
    }

    return ok({ stockMovement: updatedMovement!, stockMoveLines: newLines });
  }

  return ok({ stockMovement: updatedMovement! });
}
