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 {
  DuplicateLabelNameError,
  InvalidNameError,
  LabelNotFoundError,
} from "../lib/errors.generated";
import { baseLabel } from "../testing/fixtures";
import { run } from "./updatePipelineLabel";

describe("updatePipelineLabel", () => {
  const ctx: CommandContext = {
    actorId: "user-1",
    permissions: ["pipeline-management:label:updatePipelineLabel"],
  };

  it("updates name, color, and description", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseLabel], [], [{ ...baseLabel, name: "critical" }]);

    const input = {
      labelId: baseLabel.id,
      name: "critical",
      color: "#DC2626",
      description: "Updated description",
    };

    const result = await mock.withTx((trx) => run(trx, input, ctx));

    expect(result.ok).toBe(true);
    expect(mock.updates[0].updateValues()).toEqual(
      expect.objectContaining({
        name: "critical",
        color: "#DC2626",
        description: "Updated description",
        updatedAt: expect.any(Date) as unknown as Date,
      }),
    );
  });

  it("clearing color via null is allowed", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseLabel], [{ ...baseLabel, color: null }]);

    const result = await mock.withTx((trx) =>
      run(trx, { labelId: baseLabel.id, color: null }, ctx),
    );

    expect(result.ok).toBe(true);
    expect(mock.updates[0].updateValues()).toEqual(expect.objectContaining({ color: null }));
  });

  it("clearing description via null is allowed", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseLabel], [{ ...baseLabel, description: null }]);

    const result = await mock.withTx((trx) =>
      run(trx, { labelId: baseLabel.id, description: null }, ctx),
    );

    expect(result.ok).toBe(true);
    expect(mock.updates[0].updateValues()).toEqual(expect.objectContaining({ description: null }));
  });

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

    const result = await mock.withTx((trx) => run(trx, { labelId: "missing", name: "x" }, ctx));

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

  it("returns error when new name is blank", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseLabel]);

    const result = await mock.withTx((trx) =>
      run(trx, { labelId: baseLabel.id, name: "   " }, ctx),
    );

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

  it("returns error when renaming to a name that collides on the same pipeline", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const collision = { ...baseLabel, id: "label-2", name: "critical" };
    mock.enqueueResults([baseLabel], [collision]);

    const result = await mock.withTx((trx) =>
      run(trx, { labelId: baseLabel.id, name: "critical" }, ctx),
    );

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

  it("passes extension fields through but strips reserved model columns", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const input = {
      labelId: baseLabel.id,
      icon: "flame",
      pipelineId: "spoofed-pipeline",
      id: "spoofed-id",
    };
    mock.enqueueResults([baseLabel], [baseLabel]);

    const result = await mock.withTx((trx) => run(trx, input, ctx));

    expect(result.ok).toBe(true);
    const values = mock.updates[0].updateValues();
    expect(values).toEqual(expect.objectContaining({ icon: "flame" }));
    expect(values).not.toHaveProperty("pipelineId");
    expect(values).not.toHaveProperty("id");
  });
});
