import { describe, expect, it, vi } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  ItemNotFoundError,
  StorageLocationNotFoundError,
  ReasonCodeRequiredError,
  ScrapQuantityMustBeNegativeError,
  LotNotFoundError,
  SerialNumberNotFoundError,
} from "../lib/errors.generated";
import { baseZone, baseDraftAdjustment } from "../testing/fixtures";
import { run, type ItemManagementQueries } from "./createInventoryAdjustment";
import type { CommandContext } from "@tailor-platform/erp-kit/module";

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

  const mockGetItemFound: ItemManagementQueries = {
    getItem: vi.fn().mockResolvedValue({ ok: true, value: { item: { id: "item-1" } } }),
  };

  const mockGetItemNotFound: ItemManagementQueries = {
    getItem: vi.fn().mockResolvedValue({ ok: true, value: { item: null } }),
  };

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

    const result = await run(
      db,
      {
        itemId: "nonexistent",
        storageLocationId: "location-1",
        quantityDelta: 5,
        reasonCode: "correction",
      },
      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 lookup fails

    const result = await run(
      db,
      {
        itemId: "item-1",
        storageLocationId: "nonexistent",
        quantityDelta: 5,
        reasonCode: "correction",
      },
      ctx,
      mockGetItemFound,
    );

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

  it("returns error when reason code missing", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseZone); // location found

    const result = await run(
      db,
      {
        itemId: "item-1",
        storageLocationId: "location-1",
        quantityDelta: 5,
      },
      ctx,
      mockGetItemFound,
    );

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

  it("returns error when SCRAP type with positive quantityDelta", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseZone); // location found

    const result = await run(
      db,
      {
        adjustmentType: "SCRAP",
        itemId: "item-1",
        storageLocationId: "location-1",
        quantityDelta: 5,
        reasonCode: "expired",
      },
      ctx,
      mockGetItemFound,
    );

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

  it("creates CORRECTION adjustment with positive quantityDelta", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const created = { ...baseDraftAdjustment, adjustmentType: "CORRECTION", quantityDelta: 10 };
    spies.select.mockReturnValueOnce(baseZone); // location found
    spies.insert.mockReturnValue(created);

    const result = await run(
      db,
      {
        adjustmentType: "CORRECTION",
        itemId: "item-1",
        storageLocationId: "location-1",
        quantityDelta: 10,
        reasonCode: "receiving-error",
      },
      ctx,
      mockGetItemFound,
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.inventoryAdjustment.adjustmentType).toBe("CORRECTION");
      expect(result.value.inventoryAdjustment.quantityDelta).toBe(10);
    }
    expect(spies.insert).toHaveBeenCalled();
  });

  it("creates CORRECTION adjustment with negative quantityDelta", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const created = { ...baseDraftAdjustment, adjustmentType: "CORRECTION", quantityDelta: -5 };
    spies.select.mockReturnValueOnce(baseZone); // location found
    spies.insert.mockReturnValue(created);

    const result = await run(
      db,
      {
        adjustmentType: "CORRECTION",
        itemId: "item-1",
        storageLocationId: "location-1",
        quantityDelta: -5,
        reasonCode: "damage",
      },
      ctx,
      mockGetItemFound,
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.inventoryAdjustment.quantityDelta).toBe(-5);
    }
    expect(spies.insert).toHaveBeenCalled();
  });

  it("creates SCRAP adjustment with negative quantityDelta", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const created = { ...baseDraftAdjustment, adjustmentType: "SCRAP", quantityDelta: -3 };
    spies.select.mockReturnValueOnce(baseZone); // location found
    spies.insert.mockReturnValue(created);

    const result = await run(
      db,
      {
        adjustmentType: "SCRAP",
        itemId: "item-1",
        storageLocationId: "location-1",
        quantityDelta: -3,
        reasonCode: "expired",
      },
      ctx,
      mockGetItemFound,
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.inventoryAdjustment.adjustmentType).toBe("SCRAP");
      expect(result.value.inventoryAdjustment.quantityDelta).toBe(-3);
    }
    expect(spies.insert).toHaveBeenCalled();
  });

  it("creates adjustment linked to inventory count", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const created = { ...baseDraftAdjustment, inventoryCountId: "count-1" };
    spies.select.mockReturnValueOnce(baseZone); // location found
    spies.insert.mockReturnValue(created);

    const result = await run(
      db,
      {
        adjustmentType: "CORRECTION",
        itemId: "item-1",
        storageLocationId: "location-1",
        quantityDelta: -2,
        reasonCode: "count-variance",
        inventoryCountId: "count-1",
      },
      ctx,
      mockGetItemFound,
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.inventoryAdjustment.inventoryCountId).toBe("count-1");
    }
    expect(spies.insert).toHaveBeenCalled();
  });

  it("defaults adjustmentType to CORRECTION when not specified", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const created = { ...baseDraftAdjustment, adjustmentType: "CORRECTION" };
    spies.select.mockReturnValueOnce(baseZone); // location found
    spies.insert.mockReturnValue(created);

    const result = await run(
      db,
      {
        itemId: "item-1",
        storageLocationId: "location-1",
        quantityDelta: 5,
        reasonCode: "correction",
      },
      ctx,
      mockGetItemFound,
    );

    expect(result.ok).toBe(true);
    expect(spies.values).toHaveBeenNthCalledWith(
      1,
      expect.objectContaining({ adjustmentType: "CORRECTION" }),
    );
  });

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

    const result = await run(
      db,
      {
        itemId: "item-1",
        storageLocationId: "location-1",
        quantityDelta: 5,
        reasonCode: "correction",
        lotId: "nonexistent-lot",
      },
      ctx,
      mockGetItemFound,
    );

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

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

    const result = await run(
      db,
      {
        itemId: "item-1",
        storageLocationId: "location-1",
        quantityDelta: 5,
        reasonCode: "correction",
        serialNumberId: "nonexistent-serial",
      },
      ctx,
      mockGetItemFound,
    );

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