import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import { Decimal } from "decimal.js";
import { serialNumberLifecycle } from "../db/serialNumber.lifecycle.generated";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  ItemNotFoundError,
  ItemNotActiveError,
  StorageLocationNotFoundError,
  StorageLocationNotActiveError,
  InvalidQuantityError,
  InsufficientStockError,
  InsufficientBlockedStockError,
  LotNotFoundError,
  SerialNotFoundError,
  SerialNotAvailableError,
} from "../lib/errors.generated";
import { applyValuation } from "../lib/valuation";

export interface IssueStockInput {
  itemId: string;
  storageLocationId: string;
  quantity: string;
  sourceId: string;
  lotId?: string | null;
  serialNumberId?: string | null;
  fromBlocked?: boolean;
}

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

/**
 * Function: issueStock
 *
 * Records the issuance of goods out of inventory, creating InventoryLedger
 * entries and updating StockLevel in a single atomic operation.
 * Supports issuing from available stock (fromBlocked=false) or from
 * blocked/quarantined stock (fromBlocked=true).
 */
export async function run(
  db: Transaction,
  input: IssueStockInput,
  ctx: CommandContext,
  itemManagementQueries: Pick<ItemManagementQueries, "getItem">,
) {
  const {
    itemId,
    storageLocationId,
    quantity,
    sourceId,
    lotId,
    serialNumberId,
    fromBlocked = false,
  } = input;

  // 1. Validate item exists and is ACTIVE
  const { item } = (await itemManagementQueries.getItem(db, { id: itemId }, ctx)).value;

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

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

  // 2. Validate storage location exists and is ACTIVE
  const location = await db
    .selectFrom("StorageLocation")
    .selectAll()
    .where("id", "=", storageLocationId)
    .executeTakeFirst();

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

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

  // 3. Validate quantity > 0
  if (new Decimal(quantity).lte(0)) {
    return err(new InvalidQuantityError(quantity));
  }

  // 4. Validate lot if provided
  if (lotId) {
    const lot = await db.selectFrom("Lot").selectAll().where("id", "=", lotId).executeTakeFirst();

    if (!lot) {
      return err(new LotNotFoundError(lotId));
    }
  }

  // 5. Validate serial if provided
  const serial = serialNumberId
    ? await db
        .selectFrom("SerialNumber")
        .selectAll()
        .where("id", "=", serialNumberId)
        .executeTakeFirst()
    : undefined;
  if (serialNumberId) {
    if (!serial) {
      return err(new SerialNotFoundError(serialNumberId));
    }

    // For fromBlocked=false, serial must be able to transition via issueStock (AVAILABLE)
    if (!fromBlocked) {
      const nextSerialStatus = serialNumberLifecycle.tryTransition(serial.status, "issueStock");
      if (!nextSerialStatus) {
        return err(new SerialNotAvailableError(serialNumberId));
      }
    }
  }

  // 6. Look up StockLevel and validate sufficient stock
  const stockLevel = await db
    .selectFrom("StockLevel")
    .selectAll()
    .where("itemId", "=", itemId)
    .where("storageLocationId", "=", storageLocationId)
    .forUpdate()
    .executeTakeFirst();

  if (!fromBlocked) {
    const available = stockLevel
      ? new Decimal(stockLevel.onHand).minus(stockLevel.reserved).minus(stockLevel.blocked)
      : new Decimal(0);
    if (available.lt(quantity)) {
      return err(new InsufficientStockError(`${itemId}:${storageLocationId}`));
    }
  } else {
    const blocked = stockLevel ? new Decimal(stockLevel.blocked) : new Decimal(0);
    if (blocked.lt(quantity)) {
      return err(new InsufficientBlockedStockError(`${itemId}:${storageLocationId}`));
    }
  }

  // 7. Create InventoryLedger entries
  const now = new Date();
  const inventoryLedgerEntries: {
    id: string;
    sourceType: string;
    dcIndicator: string;
    amount: string | null;
  }[] = [];

  // Entry 1: GOODS_ISSUE / CR
  const ledgerEntry1 = await db
    .insertInto("InventoryLedger")
    .values({
      sourceType: "GOODS_ISSUE",
      sourceId,
      dcIndicator: "CR",
      itemId,
      storageLocationId,
      quantity,
      amount: null, // Valuation handles the cost calculation
      executedAt: now,
      lotId: lotId ?? null,
      serialNumberId: serialNumberId ?? null,
      createdAt: now,
    })
    .returningAll()
    .executeTakeFirstOrThrow();

  inventoryLedgerEntries.push(ledgerEntry1);

  if (fromBlocked) {
    // Entry 2: UNBLOCK / CR
    const ledgerEntry2 = await db
      .insertInto("InventoryLedger")
      .values({
        sourceType: "UNBLOCK",
        sourceId,
        dcIndicator: "CR",
        itemId,
        storageLocationId,
        quantity,
        amount: null,
        executedAt: now,
        lotId: lotId ?? null,
        serialNumberId: serialNumberId ?? null,
        createdAt: now,
      })
      .returningAll()
      .executeTakeFirstOrThrow();

    inventoryLedgerEntries.push(ledgerEntry2);
  }

  // 8. Update StockLevel
  if (stockLevel) {
    await db
      .updateTable("StockLevel")
      .set({
        onHand: new Decimal(stockLevel.onHand).minus(quantity).toString(),
        blocked: fromBlocked
          ? new Decimal(stockLevel.blocked).minus(quantity).toString()
          : stockLevel.blocked,
        updatedAt: now,
      })
      .where("id", "=", stockLevel.id)
      .returningAll()
      .executeTakeFirst();
  }

  // 9. Apply valuation (CR)
  await applyValuation(db, {
    itemId,
    inventoryLedgerId: ledgerEntry1.id,
    dcIndicator: "CR",
    quantity,
  });

  // 10. Update LotStockLevel if lotId provided
  if (lotId) {
    const existingLotStockLevel = await db
      .selectFrom("LotStockLevel")
      .selectAll()
      .where("lotId", "=", lotId)
      .where("storageLocationId", "=", storageLocationId)
      .forUpdate()
      .executeTakeFirst();

    if (existingLotStockLevel) {
      await db
        .updateTable("LotStockLevel")
        .set({
          onHand: Decimal.max(
            0,
            new Decimal(existingLotStockLevel.onHand).minus(quantity),
          ).toString(),
          blocked: fromBlocked
            ? Decimal.max(0, new Decimal(existingLotStockLevel.blocked).minus(quantity)).toString()
            : existingLotStockLevel.blocked,
          updatedAt: now,
        })
        .where("id", "=", existingLotStockLevel.id)
        .returningAll()
        .executeTakeFirst();
    }
  }

  // 11. Transition SerialNumber AVAILABLE -> ISSUED if applicable
  if (serial) {
    await db
      .updateTable("SerialNumber")
      .set({
        status: serialNumberLifecycle.transitions.issueStock.to,
        updatedAt: now,
      })
      .where("id", "=", serial.id)
      .returningAll()
      .executeTakeFirst();
  }

  return ok({ inventoryLedgerEntries });
}
