import type { Transaction } from "../generated/kysely-tailordb";
import {
  AdjustmentNotFoundError,
  AdjustmentInvalidStatusError,
  InsufficientOnHandError,
} from "../lib/errors.generated";
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";

export interface ConfirmInventoryAdjustmentInput {
  id: string;
}

/**
 * Function: confirmInventoryAdjustment
 *
 * Confirms an inventory adjustment, generating a stock movement and updating on-hand quantities
 * and valuation. Can be called from DRAFT (bypassing approval) or APPROVED status.
 *
 * CORRECTION: generates stock movement to/from virtual adjustment location.
 * SCRAP: generates stock movement to virtual scrap location, updates lot stock and serial state.
 */
export async function run(
  db: Transaction,
  input: ConfirmInventoryAdjustmentInput,
  _ctx: CommandContext,
) {
  const { id } = input;

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

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

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

  // 3. Check on-hand for negative adjustments
  if (adjustment.quantityDelta < 0) {
    const stockLevel = await db
      .selectFrom("StockLevel")
      .selectAll()
      .where("itemId", "=", adjustment.itemId)
      .where("storageLocationId", "=", adjustment.storageLocationId)
      .forUpdate()
      .executeTakeFirst();

    const onHand = stockLevel?.onHand ?? 0;
    if (onHand + adjustment.quantityDelta < 0) {
      return err(new InsufficientOnHandError(id));
    }
  }

  // 4. Generate stock movement
  const movementType =
    adjustment.adjustmentType === "SCRAP" ? ("GOODS_ISSUE" as const) : ("GOODS_RECEIPT" as const);
  const stockMovement = await db
    .insertInto("StockMovement")
    .values({
      movementType,
      reasonCode: adjustment.reasonCode,
      status: "DONE",
      executedAt: new Date(),
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

  // 5. Create stock move line
  await db
    .insertInto("StockMoveLine")
    .values({
      stockMovementId: stockMovement!.id,
      itemId: adjustment.itemId,
      quantity: Math.abs(adjustment.quantityDelta),
      sourceLocationId: adjustment.quantityDelta < 0 ? adjustment.storageLocationId : null,
      destinationLocationId: adjustment.quantityDelta >= 0 ? adjustment.storageLocationId : null,
      lotId: adjustment.lotId,
      serialNumberId: adjustment.serialNumberId,
      createdAt: new Date(),
      updatedAt: null,
    })
    .returningAll()
    .executeTakeFirst();

  // 6. SCRAP-specific: update lot stock levels if lot specified
  if (adjustment.adjustmentType === "SCRAP" && adjustment.lotId) {
    const lotStock = await db
      .selectFrom("LotStockLevel")
      .selectAll()
      .where("lotId", "=", adjustment.lotId)
      .where("storageLocationId", "=", adjustment.storageLocationId)
      .forUpdate()
      .executeTakeFirst();

    if (lotStock) {
      await db
        .updateTable("LotStockLevel")
        .set({
          onHand: lotStock.onHand + adjustment.quantityDelta,
          updatedAt: new Date(),
        })
        .where("id", "=", lotStock.id)
        .execute();
    }
  }

  // 7. SCRAP-specific: transition serial to SCRAPPED if serial specified
  if (adjustment.adjustmentType === "SCRAP" && adjustment.serialNumberId) {
    await db
      .updateTable("SerialNumber")
      .set({ status: "SCRAPPED", updatedAt: new Date() })
      .where("id", "=", adjustment.serialNumberId)
      .execute();
  }

  // 8. Update on-hand quantity on StockLevel
  const existingStockLevel = await db
    .selectFrom("StockLevel")
    .selectAll()
    .where("itemId", "=", adjustment.itemId)
    .where("storageLocationId", "=", adjustment.storageLocationId)
    .forUpdate()
    .executeTakeFirst();

  if (existingStockLevel) {
    await db
      .updateTable("StockLevel")
      .set({
        onHand: existingStockLevel.onHand + adjustment.quantityDelta,
        updatedAt: new Date(),
      })
      .where("id", "=", existingStockLevel.id)
      .execute();
  } else {
    await db
      .insertInto("StockLevel")
      .values({
        itemId: adjustment.itemId,
        storageLocationId: adjustment.storageLocationId,
        onHand: adjustment.quantityDelta,
        reserved: 0,
        incoming: 0,
        blocked: 0,
        createdAt: new Date(),
        updatedAt: null,
      })
      .execute();
  }

  // 9. Update inventory valuation
  const valuation = await db
    .selectFrom("ItemValuation")
    .selectAll()
    .where("itemId", "=", adjustment.itemId)
    .forUpdate()
    .executeTakeFirst();

  if (valuation) {
    const newTotalQuantity = valuation.totalQuantity + adjustment.quantityDelta;
    const newTotalValue =
      valuation.costPerUnit != null
        ? newTotalQuantity * valuation.costPerUnit
        : valuation.totalValue;
    await db
      .updateTable("ItemValuation")
      .set({
        totalQuantity: newTotalQuantity,
        totalValue: newTotalValue,
        updatedAt: new Date(),
      })
      .where("id", "=", valuation.id)
      .execute();
  }

  // 10. Update status to CONFIRMED
  const confirmed = await db
    .updateTable("InventoryAdjustment")
    .set({ status: "CONFIRMED", updatedAt: new Date() })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirst();

  return ok({ inventoryAdjustment: confirmed! });
}
