import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  ItemNotFoundError,
  StorageLocationNotFoundError,
  StockLevelNotFoundError,
  InsufficientAvailableStockError,
} from "../lib/errors.generated";
import { baseStockLevel, baseZone } from "../testing/fixtures";
import { run } from "./reserveStock";
import type { CommandContext } from "@tailor-platform/erp-kit/module";

describe("reserveStock", () => {
  const ctx: CommandContext = {
    actorId: "test-actor",
    permissions: ["inventory:reserveStock"],
  };

  const mockItemQueries = (item: { id: string } | null) => ({
    getItem: async () => await Promise.resolve({ ok: true as const, value: { item } }),
  });

  it("returns error when item not found", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(
      db,
      { itemId: "nonexistent", storageLocationId: "location-1", quantity: 10 },
      ctx,
      mockItemQueries(null),
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(ItemNotFoundError);
    }
  });

  it("returns error when location not found", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined); // Location lookup

    const result = await run(
      db,
      { itemId: "item-1", storageLocationId: "nonexistent", quantity: 10 },
      ctx,
      mockItemQueries({ id: "item-1" }),
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(StorageLocationNotFoundError);
    }
  });

  it("returns error when no stock level exists", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseZone) // Location exists
      .mockReturnValueOnce(undefined); // StockLevel lookup

    const result = await run(
      db,
      { itemId: "item-1", storageLocationId: "location-1", quantity: 10 },
      ctx,
      mockItemQueries({ id: "item-1" }),
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(StockLevelNotFoundError);
    }
  });

  it("returns error when insufficient available stock", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // baseStockLevel: onHand=100, reserved=10, blocked=5 => available=85
    spies.select
      .mockReturnValueOnce(baseZone) // Location exists
      .mockReturnValueOnce(baseStockLevel); // StockLevel found

    const result = await run(
      db,
      { itemId: "item-1", storageLocationId: "location-1", quantity: 90 },
      ctx,
      mockItemQueries({ id: "item-1" }),
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InsufficientAvailableStockError);
    }
  });

  it("reserves stock successfully", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // baseStockLevel: onHand=100, reserved=10, blocked=5 => available=85
    const updatedStockLevel = { ...baseStockLevel, reserved: 30, updatedAt: new Date() };

    spies.select
      .mockReturnValueOnce(baseZone) // Location exists
      .mockReturnValueOnce(baseStockLevel); // StockLevel found
    spies.update.mockReturnValue(updatedStockLevel);

    const result = await run(
      db,
      { itemId: "item-1", storageLocationId: "location-1", quantity: 20 },
      ctx,
      mockItemQueries({ id: "item-1" }),
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.stockLevel.reserved).toBe(30);
    }
    expect(spies.update).toHaveBeenCalled();
  });

  it("updates available quantity correctly", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // baseStockLevel: onHand=100, reserved=10, blocked=5 => available=85
    // Reserve exactly 85 (all available)
    const updatedStockLevel = { ...baseStockLevel, reserved: 95, updatedAt: new Date() };

    spies.select
      .mockReturnValueOnce(baseZone) // Location exists
      .mockReturnValueOnce(baseStockLevel); // StockLevel found
    spies.update.mockReturnValue(updatedStockLevel);

    const result = await run(
      db,
      { itemId: "item-1", storageLocationId: "location-1", quantity: 85 },
      ctx,
      mockItemQueries({ id: "item-1" }),
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      // reserved went from 10 to 95 (10 + 85)
      expect(result.value.stockLevel.reserved).toBe(95);
      // available is now 0: onHand(100) - reserved(95) - blocked(5) = 0
      const sl = result.value.stockLevel;
      expect(sl.onHand - sl.reserved - sl.blocked).toBe(0);
    }
  });
});
