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 { PipelineLockedError, StageNotFoundError } from "../lib/errors.generated";
import {
  inProgressStage,
  basePipeline,
  baseStage,
  doneStage,
  lockedPipeline,
} from "../testing/fixtures";
import { run } from "./reorderPipelineStage";

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

  it("moves stage to a new position", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([inProgressStage], [basePipeline], [baseStage, inProgressStage, doneStage]);

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

    expect(result.ok).toBe(true);
    expect(mock.updates.length).toBeGreaterThan(0);
  });

  it("shifts other stages when reordering", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseStage], [basePipeline], [baseStage, inProgressStage, doneStage]);

    const result = await mock.withTx((trx) =>
      run(trx, { stageId: baseStage.id, position: 3 }, ctx),
    );

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

  it("clamps position to the end when input exceeds stage count", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseStage], [basePipeline], [baseStage, inProgressStage, doneStage]);

    const result = await mock.withTx((trx) =>
      run(trx, { stageId: baseStage.id, position: 99 }, ctx),
    );

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

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

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

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

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

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

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

  it("returns error when pipeline is locked", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseStage], [lockedPipeline]);

    const result = await mock.withTx((trx) =>
      run(trx, { stageId: baseStage.id, position: 2 }, ctx),
    );

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