import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import type {
  StockAdjustmentLineAdjustmentDirection,
  StockAdjustmentLineFromStockCategory,
} from "../generated/enums";
import type { Transaction, Updateable } from "../generated/kysely-tailordb";
import {
  StockAdjustmentNotFoundError,
  InvalidStatusError,
  EmptyAdjustmentLinesError,
  InvalidQuantityError,
  ItemNotFoundError,
  ItemNotActiveError,
  StorageLocationNotFoundError,
  StorageLocationNotActiveError,
  InvalidFromStockCategoryError,
  LotNotFoundError,
  SerialNotFoundError,
} from "../lib/errors.generated";
import type { ItemManagementQueries } from "../module";

export interface UpdateStockAdjustmentLineInput {
  itemId: string;
  storageLocationId: string;
  quantity: string;
  adjustmentDirection?: StockAdjustmentLineAdjustmentDirection | null;
  fromStockCategory?: StockAdjustmentLineFromStockCategory | null;
  lotId?: string | null;
  serialNumberId?: string | null;
  unitCost?: string | null;
}

export interface UpdateStockAdjustmentInput {
  id: string;
  reasonCode?: string;
  adjustmentDate?: Date;
  lines?: UpdateStockAdjustmentLineInput[];
}

/**
 * Function: updateStockAdjustment
 *
 * Modifies a stock adjustment in DRAFT or REJECTED status.
 * When updating a REJECTED adjustment, its status transitions back to DRAFT.
 */
export async function run(
  db: Transaction,
  input: UpdateStockAdjustmentInput,
  ctx: CommandContext,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
) {
  const { id, reasonCode, adjustmentDate, lines } = input;

  // 1. Fetch adjustment
  const adjustment = await db
    .selectFrom("StockAdjustment")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();

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

  // 2. Validate status is DRAFT or REJECTED
  if (adjustment.status !== "DRAFT" && adjustment.status !== "REJECTED") {
    return err(new InvalidStatusError(id));
  }

  // 3. If lines provided, validate them
  if (lines !== undefined) {
    if (lines.length === 0) {
      return err(new EmptyAdjustmentLinesError(id));
    }

    for (const line of lines) {
      if (new Decimal(line.quantity).lte(0)) {
        return err(new InvalidQuantityError(line.quantity));
      }

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

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

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

      if (location.status !== "ACTIVE") {
        return err(new StorageLocationNotActiveError(line.storageLocationId));
      }

      if (line.fromStockCategory && adjustment.adjustmentType !== "SCRAP") {
        return err(new InvalidFromStockCategoryError(line.itemId));
      }

      // lotId and serialNumberId are optional; when provided, validate existence.
      if (line.lotId) {
        const lot = await db
          .selectFrom("Lot")
          .selectAll()
          .where("id", "=", line.lotId)
          .executeTakeFirst();
        if (!lot) {
          return err(new LotNotFoundError(line.lotId));
        }
      }

      if (line.serialNumberId) {
        const serial = await db
          .selectFrom("SerialNumber")
          .selectAll()
          .where("id", "=", line.serialNumberId)
          .executeTakeFirst();
        if (!serial) {
          return err(new SerialNotFoundError(line.serialNumberId));
        }
      }
    }
  }

  // 4. Update the adjustment
  const updateData: Updateable<"StockAdjustment"> = {};

  // If REJECTED, transition back to DRAFT and clear rejectionReason
  if (adjustment.status === "REJECTED") {
    updateData.status = "DRAFT";
    updateData.rejectionReason = null;
  }

  if (reasonCode !== undefined) {
    updateData.reasonCode = reasonCode;
  }

  if (adjustmentDate !== undefined) {
    updateData.adjustmentDate = adjustmentDate;
  }

  const stockAdjustment =
    Object.keys(updateData).length === 0
      ? adjustment
      : await db
          .updateTable("StockAdjustment")
          .set(updateData)
          .where("id", "=", id)
          .returningAll()
          .executeTakeFirstOrThrow();

  // 5. Replace lines if provided
  let stockAdjustmentLines: unknown[] = [];

  if (lines !== undefined) {
    // Delete existing lines
    await db.deleteFrom("StockAdjustmentLine").where("stockAdjustmentId", "=", id).execute();

    // Insert new lines (validated non-empty above)
    stockAdjustmentLines = await db
      .insertInto("StockAdjustmentLine")
      .values(
        lines.map((line) => ({
          stockAdjustmentId: id,
          itemId: line.itemId,
          storageLocationId: line.storageLocationId,
          quantity: line.quantity,
          adjustmentDirection: line.adjustmentDirection ?? null,
          fromStockCategory: line.fromStockCategory ?? null,
          unitCost: line.unitCost ?? null,
          lotId: line.lotId ?? null,
          serialNumberId: line.serialNumberId ?? null,
        })),
      )
      .returningAll()
      .execute();
  } else {
    // Fetch existing lines if not replacing
    stockAdjustmentLines = await db
      .selectFrom("StockAdjustmentLine")
      .selectAll()
      .where("stockAdjustmentId", "=", id)
      .execute();
  }

  return ok({ stockAdjustment, stockAdjustmentLines });
}
