import { ok, err } from "@tailor-platform/erp-kit/core";
import { Transaction, type Updateable } from "../generated/kysely-tailordb";
import {
  RoleNotFoundError,
  RoleNotActiveError,
  MissingRequiredFieldError,
  RoleAlreadyExistsError,
  InvalidPermissionError,
} from "../lib/errors.generated";
import { validatePermissionKey } from "../lib/validatePermissionKey";

export type UpdateRoleInput = { id: string } & {
  name?: string;
  description?: string | null;
  permissions?: string[] | null;
};

/**
 * Function: updateRole
 *
 * Updates an existing role's name or description.
 * Only ACTIVE roles can be updated. Existing UserRole and RolePermission associations remain intact.
 */
export async function run<CF extends Record<string, unknown>>(
  db: Transaction,
  input: UpdateRoleInput & Omit<Partial<CF>, "status">,
) {
  const { id, name, description, permissions, ...customFields } = input;

  const role = await db
    .selectFrom("Role")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();

  if (!role) {
    return err(new RoleNotFoundError(id));
  }

  if (role.status !== "ACTIVE") {
    return err(new RoleNotActiveError(id));
  }

  if (name?.trim() === "") {
    return err(new MissingRequiredFieldError("name"));
  }

  if (permissions) {
    for (const key of permissions) {
      if (!validatePermissionKey(key)) {
        return err(new InvalidPermissionError(key));
      }
    }
  }

  if (name !== undefined && name !== role.name) {
    const existingRole = await db
      .selectFrom("Role")
      .selectAll()
      .where("name", "=", name)
      .forUpdate()
      .executeTakeFirst();

    if (existingRole) {
      return err(new RoleAlreadyExistsError(name));
    }
  }

  const updateBody: Updateable<"Role"> = {
    ...(customFields as Updateable<"Role">),
  };
  if (name !== undefined) updateBody.name = name;
  if (description !== undefined) updateBody.description = description;
  if (permissions !== undefined) updateBody.permissions = permissions;
  if (Object.keys(updateBody).length === 0) {
    return ok({ role });
  }

  const updatedRole = await db
    .updateTable("Role")
    .set(updateBody)
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirstOrThrow();

  return ok({ role: updatedRole });
}
