import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import { Transaction } from "../generated/kysely-tailordb";
import {
  RoleNotFoundError,
  RoleNotActiveError,
  MissingRequiredFieldError,
  RoleAlreadyExistsError,
  InvalidPermissionError,
} from "../lib/errors.generated";
import { baseRole, adminRole, inactiveRole } from "../testing/fixtures";
import { run } from "./updateRole";

describe("updateRole", () => {
  // Error cases
  it("throws when role does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(undefined);

    const result = await run(db, { id: "nonexistent-role", name: "New Name" });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(RoleNotFoundError);
    }
  });

  it("throws when role is INACTIVE", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValue(inactiveRole);

    const result = await run(db, { id: inactiveRole.id, name: "New Name" });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(RoleNotActiveError);
    }
  });

  it("throws when new name already exists", async () => {
    const { db, spies } = createMockDb<Transaction>();
    // First select: find the role being updated
    spies.select.mockReturnValueOnce(baseRole);
    // Second select: find a conflicting role with the same name
    spies.select.mockReturnValueOnce(adminRole);

    const result = await run(db, { id: baseRole.id, name: adminRole.name });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(RoleAlreadyExistsError);
    }
  });

  it("throws when new name is empty", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseRole);

    const result = await run(db, { id: baseRole.id, name: "" });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(MissingRequiredFieldError);
    }
  });

  // Success cases
  it("updates role name successfully", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedRole = { ...baseRole, name: "Updated Manager" };

    // First select: find the role; second select: no conflicting name
    spies.select.mockReturnValueOnce(baseRole);
    spies.select.mockReturnValueOnce(undefined);
    spies.update.mockReturnValue(updatedRole);

    const result = await run(db, { id: baseRole.id, name: "Updated Manager" });
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.role.name).toBe("Updated Manager");
    }
    expect(spies.update).toHaveBeenCalled();
  });

  it("updates role description successfully", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedRole = { ...baseRole, description: "Updated description" };

    spies.select.mockReturnValue(baseRole);
    spies.update.mockReturnValue(updatedRole);

    const result = await run(db, { id: baseRole.id, description: "Updated description" });
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.role.description).toBe("Updated description");
    }
    expect(spies.update).toHaveBeenCalled();
  });

  it("updates role permissions", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const newPermissions = ["sales:salesOrder:create", "inventory:stockOperation:approve"];
    const updatedRole = { ...baseRole, permissions: newPermissions };

    spies.select.mockReturnValueOnce(baseRole);
    spies.update.mockReturnValue(updatedRole);

    const result = await run(db, { id: baseRole.id, permissions: newPermissions });
    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.role.permissions).toEqual(newPermissions);
    }
    expect(spies.update).toHaveBeenCalled();
  });

  it("throws when permissions contain invalid format", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(baseRole);

    const result = await run(db, {
      id: baseRole.id,
      permissions: ["valid:scope:command", "1invalid"],
    });
    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(InvalidPermissionError);
    }
  });

  it("does not affect existing UserRole or RolePermission assignments", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedRole = { ...baseRole, name: "Renamed Role" };

    spies.select.mockReturnValueOnce(baseRole);
    spies.select.mockReturnValueOnce(undefined);
    spies.update.mockReturnValue(updatedRole);

    const result = await run(db, { id: baseRole.id, name: "Renamed Role" });
    expect(result.ok).toBe(true);
    expect(spies.insert).not.toHaveBeenCalled();
    expect(spies.delete).not.toHaveBeenCalled();
  });
});
