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

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

  it("returns error when item does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseDraftItem]);

    const result = await mock.withTx((trx) => run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseDraftItem], // Item lookup
      [baseActiveItem], // Barcode already taken
    );

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseActiveItem]);

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseInactiveItem]);

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [baseDraftItem], // Item lookup
      [], // UoM not found
    );

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    const updatedItem = { ...baseActiveItem, name: "Updated Name" };
    mock.enqueueResults([baseActiveItem], [updatedItem]);

    const result = await mock.withTx((trx) =>
      run(trx, { 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(mock.updates.length).toBeGreaterThan(0);
  });

  it("looks up item by sku and updates name", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const updatedItem = { ...baseActiveItem, name: "Updated Name" };
    mock.enqueueResults([baseActiveItem], [updatedItem]);

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    const updatedItem = { ...baseActiveItem, barcode: "NEW-BAR" };
    mock.enqueueResults(
      [baseActiveItem], // Item lookup
      [], // Barcode unique
      [updatedItem],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    const updatedItem = { ...baseActiveItem, barcode: null };
    mock.enqueueResults([baseActiveItem], [updatedItem]);

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    const activeUnit = { id: "unit-g", status: "ACTIVE" as const };
    const updatedItem = { ...baseDraftItem, unitId: "unit-g" };
    mock.enqueueResults(
      [baseDraftItem], // Item lookup
      [activeUnit], // UoM valid
      [updatedItem],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    const updatedItem = { ...baseDraftItem, priority: 10 };
    mock.enqueueResults([baseDraftItem], [updatedItem]);

    await mock.withTx((trx) => run(trx, { id: baseDraftItem.id, priority: 10 }, ctx, { getUnit }));

    expect(mock.updates[0].updateValues()).toEqual(expect.objectContaining({ priority: 10 }));
  });
});
