import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import { Transaction } from "../generated/kysely-tailordb";
import {
  DuplicateBarcodeError,
  UnitNotFoundError,
  ItemNotFoundError,
  NoFieldsToUpdateError,
  UomLockedError,
} from "../lib/errors.generated";
import { baseActiveItem, baseDraftItem, baseInactiveItem } from "../testing/fixtures";
import { getUnit } from "../../primitives/query/getUnit.generated";
import { run } from "./updateItem";
import { type CommandContext } from "@tailor-platform/erp-kit/module";

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

  it("returns error when item does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(undefined);

    const result = await run(db, { id: "nonexistent", name: "New Name" }, ctx, { getUnit });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(ItemNotFoundError);
    }
  });

  it("returns error when no fields to update", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseDraftItem);

    const result = await run(db, { id: baseDraftItem.id }, ctx, { getUnit });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(NoFieldsToUpdateError);
    }
  });

  it("returns error when barcode is duplicate", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseDraftItem) // Item lookup
      .mockReturnValueOnce(baseActiveItem); // Barcode already taken

    const result = await run(db, { id: baseDraftItem.id, barcode: "BAR-002" }, ctx, { getUnit });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(DuplicateBarcodeError);
    }
  });

  it("returns error when changing UoM on non-DRAFT item", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseActiveItem);

    const result = await run(db, { id: baseActiveItem.id, unitId: "unit-g" }, ctx, { getUnit });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(UomLockedError);
    }
  });

  it("returns error when changing UoM on INACTIVE item", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseInactiveItem);

    const result = await run(db, { id: baseInactiveItem.id, unitId: "unit-g" }, ctx, { getUnit });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(UomLockedError);
    }
  });

  it("returns error when new UoM does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseDraftItem) // Item lookup
      .mockReturnValueOnce(undefined); // UoM not found

    const result = await run(db, { id: baseDraftItem.id, unitId: "nonexistent-unit" }, ctx, {
      getUnit,
    });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(UnitNotFoundError);
    }
  });

  it("updates name in any status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedItem = { ...baseActiveItem, name: "Updated Name" };
    spies.select.mockReturnValueOnce(baseActiveItem);
    spies.update.mockReturnValue(updatedItem);

    const result = await run(db, { id: baseActiveItem.id, name: "Updated Name" }, ctx, { getUnit });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.item.name).toBe("Updated Name");
    }
    expect(spies.update).toHaveBeenCalled();
  });

  it("looks up item by sku and updates name", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedItem = { ...baseActiveItem, name: "Updated Name" };
    spies.select.mockReturnValueOnce(baseActiveItem);
    spies.update.mockReturnValue(updatedItem);

    const result = await run(db, { sku: baseActiveItem.sku, name: "Updated Name" }, ctx, {
      getUnit,
    });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.item.name).toBe("Updated Name");
    }
  });

  it("updates barcode in any status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedItem = { ...baseActiveItem, barcode: "NEW-BAR" };
    spies.select
      .mockReturnValueOnce(baseActiveItem) // Item lookup
      .mockReturnValueOnce(undefined); // Barcode unique
    spies.update.mockReturnValue(updatedItem);

    const result = await run(db, { id: baseActiveItem.id, barcode: "NEW-BAR" }, ctx, { getUnit });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.item.barcode).toBe("NEW-BAR");
    }
  });

  it("clears barcode by setting to null", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedItem = { ...baseActiveItem, barcode: null };
    spies.select.mockReturnValueOnce(baseActiveItem);
    spies.update.mockReturnValue(updatedItem);

    const result = await run(db, { id: baseActiveItem.id, barcode: null }, ctx, { getUnit });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.item.barcode).toBeNull();
    }
  });

  it("updates UoM in DRAFT status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const activeUnit = { id: "unit-g", isActive: true };
    const updatedItem = { ...baseDraftItem, unitId: "unit-g" };
    spies.select
      .mockReturnValueOnce(baseDraftItem) // Item lookup
      .mockReturnValueOnce(activeUnit); // UoM valid
    spies.update.mockReturnValue(updatedItem);

    const result = await run(db, { id: baseDraftItem.id, unitId: "unit-g" }, ctx, { getUnit });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.item.unitId).toBe("unit-g");
    }
  });

  it("passes custom fields through to set", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedItem = { ...baseDraftItem, priority: 10 };
    spies.select.mockReturnValueOnce(baseDraftItem);
    spies.update.mockReturnValue(updatedItem);

    await run(db, { id: baseDraftItem.id, priority: 10 }, ctx, { getUnit });

    expect(spies.set).toHaveBeenNthCalledWith(1, expect.objectContaining({ priority: 10 }));
  });
});
