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

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

  const mockGetItemFound = {
    getItem: () => Promise.resolve(ok({ item: { id: "item-1" } })),
  };

  const mockGetItemNotFound = {
    getItem: () => Promise.resolve(ok({ item: null })),
  };

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

    const result = await run(
      db,
      { serialNumber: "SN-001", itemId: "nonexistent", storageLocationId: "location-1" },
      ctx,
      mockGetItemNotFound,
    );
    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 not found

    const result = await run(
      db,
      { serialNumber: "SN-001", itemId: "item-1", storageLocationId: "nonexistent" },
      ctx,
      mockGetItemFound,
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(StorageLocationNotFoundError);
    }
  });

  it("returns error when serial already exists for item", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseZone) // location found
      .mockReturnValueOnce(baseAvailableSerial); // duplicate serial found

    const result = await run(
      db,
      { serialNumber: "SN-001", itemId: "item-1", storageLocationId: "location-1" },
      ctx,
      mockGetItemFound,
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(DuplicateSerialNumberError);
    }
  });

  it("assigns serial in AVAILABLE state", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdSerial = {
      ...baseAvailableSerial,
      id: "new-serial-id",
      serialNumber: "SN-NEW",
    };
    spies.select
      .mockReturnValueOnce(baseZone) // location found
      .mockReturnValueOnce(undefined); // no duplicate
    spies.insert.mockReturnValue(createdSerial);

    const result = await run(
      db,
      { serialNumber: "SN-NEW", itemId: "item-1", storageLocationId: "location-1" },
      ctx,
      mockGetItemFound,
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.serialNumber.serialNumber).toBe("SN-NEW");
      expect(result.value.serialNumber.status).toBe("AVAILABLE");
    }
    expect(spies.insert).toHaveBeenCalled();
  });

  it("allows same serial number for different items", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdSerial = {
      ...baseAvailableSerial,
      id: "new-serial-id",
      serialNumber: "SN-001",
      itemId: "item-2",
    };
    spies.select
      .mockReturnValueOnce(baseZone) // location found
      .mockReturnValueOnce(undefined); // no duplicate for item-2
    spies.insert.mockReturnValue(createdSerial);

    const mockGetItem2 = {
      getItem: () => Promise.resolve(ok({ item: { id: "item-2" } })),
    };

    const result = await run(
      db,
      { serialNumber: "SN-001", itemId: "item-2", storageLocationId: "location-1" },
      ctx,
      mockGetItem2,
    );
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.serialNumber.serialNumber).toBe("SN-001");
      expect(result.value.serialNumber.itemId).toBe("item-2");
    }
    expect(spies.insert).toHaveBeenCalled();
  });
});
