import type { CommandContext } from "@tailor-platform/erp-kit/core";
import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import { ItemNotOpenError, ItemNotFoundError } from "../lib/errors.generated";
import { baseItem, closedItem } from "../testing/fixtures";
import { run } from "./reorderPipelineItem";

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

  const second = { ...baseItem, id: "item-2", position: 2 };
  const third = { ...baseItem, id: "item-3", position: 3 };

  it("moves item to a new position within the same stage", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([third], [baseItem, second, third]);

    const result = await mock.withTx((trx) => run(trx, { itemId: third.id, position: 1 }, ctx));

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.item.position).toBe(1);
    }
  });

  it("shifts sibling items when reordering", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([third], [baseItem, second, third]);

    await mock.withTx((trx) => run(trx, { itemId: third.id, position: 1 }, ctx));

    const setCalls = mock.updates.map((u) => u.updateValues());
    expect(setCalls).toEqual(
      expect.arrayContaining([
        expect.objectContaining({ position: 1 }),
        expect.objectContaining({ position: 2 }),
        expect.objectContaining({ position: 3 }),
      ]),
    );
  });

  it("clamps position to the end when input exceeds list length", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseItem], [baseItem, second, third]);

    const result = await mock.withTx((trx) =>
      run(trx, { itemId: baseItem.id, position: 999 }, ctx),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.item.position).toBe(3);
    }
  });

  it("clamps position to 1 when input is zero or negative", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([third], [baseItem, second, third]);

    const result = await mock.withTx((trx) => run(trx, { itemId: third.id, position: 0 }, ctx));

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.item.position).toBe(1);
    }
  });

  it("does not change stageId", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([third], [baseItem, second, third]);

    await mock.withTx((trx) => run(trx, { itemId: third.id, position: 1 }, ctx));

    for (const update of mock.updates) {
      expect(update.updateValues()).not.toHaveProperty("stageId");
    }
  });

  it("does not create a PipelineStageTransition record", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([third], [baseItem, second, third]);

    await mock.withTx((trx) => run(trx, { itemId: third.id, position: 1 }, ctx));

    expect(mock.inserts.length).toBe(0);
  });

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

    const result = await mock.withTx((trx) =>
      run(trx, { itemId: "missing-item", position: 1 }, ctx),
    );

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

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

    const result = await mock.withTx((trx) =>
      run(trx, { itemId: closedItem.id, position: 1 }, ctx),
    );

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

  it("returns error when item is not OPEN (DRAFT)", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([
      {
        ...closedItem,
        id: "item-draft-reorder",
        lifecycle: "DRAFT",
      },
    ]);

    const result = await mock.withTx((trx) =>
      run(trx, { itemId: "item-draft-reorder", position: 1 }, ctx),
    );

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