import { randomUUID } from "crypto";
import { graphql } from "../../src/graphql";
import { resolveTailorAuth } from "./auth-machine-user";

const UPDATE_ROLE_MUTATION = graphql(`
  mutation E2eUpdateRole(
    $roleId: ID!
    $name: String
    $description: String
    $permissions: [String!]!
  ) {
    updateRole(roleId: $roleId, name: $name, description: $description, permissions: $permissions) {
      roleId
    }
  }
`);

const CREATE_ROLE_MUTATION = graphql(`
  mutation E2eCreateRole($name: String!, $description: String, $permissions: [String!]!) {
    createRole(name: $name, description: $description, permissions: $permissions) {
      id
      name
      description
    }
  }
`);

interface RoleFactoryInput {
  name?: string;
  description?: string;
  permissions?: string[];
}

function buildRoleInput(overrides: RoleFactoryInput = {}) {
  const id = randomUUID().slice(0, 8);
  return {
    name: overrides.name ?? `e2e-role-${id}`,
    description: overrides.description ?? "",
    permissions: overrides.permissions ?? ["user-management:user"],
  };
}

async function mutationWithRetry<T>(fn: () => T | Promise<T>, retries = 4): Promise<Awaited<T>> {
  let lastError: unknown;
  let backoff = 500;
  for (let attempt = 0; attempt < retries; attempt += 1) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;
      const message = error instanceof Error ? error.message : String(error);
      if (!/backend read error|network/i.test(message) || attempt === retries - 1) {
        throw error;
      }
      await new Promise((resolve) => setTimeout(resolve, backoff));
      backoff = Math.min(backoff * 2, 4_000);
    }
  }
  throw lastError;
}

export async function createRole(inputOverrides: RoleFactoryInput = {}) {
  const { graphqlClient } = resolveTailorAuth();
  const roleInput = buildRoleInput(inputOverrides);

  const result = await mutationWithRetry(() =>
    graphqlClient.mutation(CREATE_ROLE_MUTATION, roleInput),
  );

  if (result.error) {
    throw new Error(`Failed to create role: ${result.error.message}`);
  }

  return {
    roleId: result.data!.createRole.id,
    name: result.data!.createRole.name,
    description: result.data!.createRole.description,
    permissions: roleInput.permissions,
  };
}

interface UpdateRoleInput {
  roleId: string;
  name?: string;
  description?: string;
  permissions: string[];
}

export async function updateRole(input: UpdateRoleInput) {
  const { graphqlClient } = resolveTailorAuth();

  const result = await mutationWithRetry(() => graphqlClient.mutation(UPDATE_ROLE_MUTATION, input));

  if (result.error) {
    throw new Error(`Failed to update role: ${result.error.message}`);
  }

  return { roleId: result.data!.updateRole.roleId };
}
