import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import {
  RoleAlreadyExistsError,
  MissingRequiredFieldError,
  InvalidPermissionError,
} from "../lib/errors.generated";
import { baseRole } from "../testing/fixtures";
import { run } from "./createRole";

describe("createRole", () => {
  // Error cases
  it("throws when name is empty", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) => run(trx, { name: "" }));
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldError);
    }
  });

  it("throws when name is whitespace only", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) => run(trx, { name: "   " }));
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldError);
    }
  });

  it("throws when role name already exists", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseRole]);

    const result = await mock.withTx((trx) => run(trx, { name: baseRole.name }));
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(RoleAlreadyExistsError);
    }
  });

  // Success cases
  it("creates role with valid name", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const createdRole = {
      ...baseRole,
      id: "new-role-id",
      name: "Viewer",
      description: null,
    };

    mock.enqueueResults([], [createdRole]);

    const result = await mock.withTx((trx) => run(trx, { name: "Viewer" }));

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.role.name).toBe("Viewer");
    }
    expect(mock.inserts.length).toBeGreaterThan(0);
  });

  it("creates role with description", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const createdRole = {
      ...baseRole,
      id: "new-role-id",
      name: "Editor",
      description: "Can edit content",
    };

    mock.enqueueResults([], [createdRole]);

    const result = await mock.withTx((trx) =>
      run(trx, {
        name: "Editor",
        description: "Can edit content",
      }),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.role.name).toBe("Editor");
      expect(result.value.role.description).toBe("Can edit content");
    }
  });

  it("creates role with initial permissions", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const createdRole = {
      ...baseRole,
      id: "new-role-id",
      name: "Sales Rep",
      permissions: ["sales:salesOrder:create", "sales:salesOrder:read"],
    };

    mock.enqueueResults([], [createdRole]);

    const result = await mock.withTx((trx) =>
      run(trx, {
        name: "Sales Rep",
        permissions: ["sales:salesOrder:create", "sales:salesOrder:read"],
      }),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.role.permissions).toEqual([
        "sales:salesOrder:create",
        "sales:salesOrder:read",
      ]);
    }
  });

  it("throws when permissions contain invalid format", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(trx, {
        name: "Bad Role",
        permissions: ["valid:scope:command", "1invalid"],
      }),
    );
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidPermissionError);
    }
  });

  it("passes custom fields through to insert", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const createdRole = {
      ...baseRole,
      id: "new-role-id",
      name: "Viewer",
      department: "Engineering",
    };

    mock.enqueueResults([], [createdRole]);

    await mock.withTx((trx) => run(trx, { name: "Viewer", department: "Engineering" }));

    expect(mock.inserts[0].insertValues()).toEqual(
      expect.objectContaining({ department: "Engineering" }),
    );
  });
});
