import type { CommandContext } from "@tailor-platform/erp-kit/core";
import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  InsufficientAvailableStockError,
  InvalidReservationQuantitiesError,
  ItemNotFoundError,
  SiteNotFoundError,
  StorageLocationNotFoundError,
} from "../lib/errors.generated";
import {
  mockItemManagementQueries,
  mockOrganizationQueries,
  type ItemManagementQueryItem,
  type OrganizationQuerySite,
} from "../testing/commandTestUtils";
import {
  baseActiveItem,
  baseActiveSite,
  baseStockLevel,
  baseStockReservation,
} from "../testing/fixtures";
import { run, type CreateStockReservationInput } from "./createStockReservation";

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

  const mockItemQueries = (item: ItemManagementQueryItem) => mockItemManagementQueries(item);
  const mockOrgQueries = (site: OrganizationQuerySite) => mockOrganizationQueries(site);
  const input: CreateStockReservationInput = {
    sourceType: "SALES_ORDER",
    sourceId: "sales-order-1",
    sourceLineId: "sales-order-line-1",
    itemId: "item-1",
    siteId: "site-1",
    reservedQuantity: "10",
    requiredDate: new Date("2024-01-15T00:00:00.000Z"),
  };

  it("creates reservation successfully", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce([{ id: "location-1" }])
      .mockReturnValueOnce([baseStockLevel])
      .mockReturnValueOnce([]);
    spies.insert.mockReturnValueOnce(baseStockReservation);

    const result = await run(
      db,
      input,
      ctx,
      mockItemQueries(baseActiveItem),
      mockOrgQueries(baseActiveSite),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.stockReservation.reservedQuantity).toBe("10");
      expect(result.value.stockReservation.consumedQuantity).toBe("0");
    }
    expect(spies.insert).toHaveBeenCalled();
  });

  it("appends another reservation row for the same source line", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce([{ id: "location-1" }])
      .mockReturnValueOnce([baseStockLevel])
      .mockReturnValueOnce([{ ...baseStockReservation, reservedQuantity: "5" }]);
    spies.insert.mockReturnValueOnce({ ...baseStockReservation, id: "stock-reservation-2" });

    const result = await run(
      db,
      { ...input, reservedQuantity: "5" },
      ctx,
      mockItemQueries(baseActiveItem),
      mockOrgQueries(baseActiveSite),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.stockReservation.id).toBe("stock-reservation-2");
    }
    const values = spies.values.mock.calls[0][0] as Record<string, unknown>;
    expect(values.sourceLineId).toBe("sales-order-line-1");
  });

  it("returns error when reserved quantity is zero or negative", async () => {
    const { db } = createMockDb<Transaction>();

    const result = await run(
      db,
      { ...input, reservedQuantity: "0" },
      ctx,
      mockItemQueries(baseActiveItem),
      mockOrgQueries(baseActiveSite),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidReservationQuantitiesError);
    }
  });

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

    const result = await run(
      db,
      { ...input, itemId: "nonexistent" },
      ctx,
      mockItemQueries(null),
      mockOrgQueries(baseActiveSite),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(ItemNotFoundError);
    }
  });

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

    const result = await run(db, input, ctx, mockItemQueries(baseActiveItem), mockOrgQueries(null));

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(SiteNotFoundError);
    }
  });

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

    const result = await run(
      db,
      { ...input, storageLocationId: "nonexistent" },
      ctx,
      mockItemQueries(baseActiveItem),
      mockOrgQueries(baseActiveSite),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(StorageLocationNotFoundError);
    }
  });

  it("returns error when insufficient available stock", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce([{ id: "location-1" }])
      .mockReturnValueOnce([baseStockLevel])
      .mockReturnValueOnce([{ ...baseStockReservation, reservedQuantity: "80" }]);

    const result = await run(
      db,
      { ...input, reservedQuantity: "20" },
      ctx,
      mockItemQueries(baseActiveItem),
      mockOrgQueries(baseActiveSite),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InsufficientAvailableStockError);
    }
  });
});
