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

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

const CREATE_USER_MUTATION = graphql(`
  mutation E2eCreateUser($name: String!, $email: String!, $roleIds: [ID]) {
    createUser(name: $name, email: $email, roleIds: $roleIds) {
      id
      email
      name
      status
      initialPassword
    }
  }
`);

interface UserFactoryInput {
  name?: string;
  email?: string;
  permissions?: string[];
}

/** A user created for E2E tests, with login credentials. */
export type TestUser = Awaited<ReturnType<typeof createUser>>;

function buildUserInput(overrides: UserFactoryInput = {}) {
  const id = randomUUID();
  return {
    name: overrides.name ?? "E2E Test User",
    email: overrides.email ?? `playwright-${id}@example.com`,
    permissions: overrides.permissions,
  };
}

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 createUser(inputOverrides: UserFactoryInput = {}) {
  const { graphqlClient } = resolveTailorAuth();
  const userInput = buildUserInput(inputOverrides);

  // 1. Create role with permissions if specified
  let roleId: string | undefined;
  if (userInput.permissions?.length) {
    const roleName = `e2e-role-${randomUUID().slice(0, 8)}`;
    const roleResult = await mutationWithRetry(() =>
      graphqlClient.mutation(CREATE_ROLE_MUTATION, {
        name: roleName,
        permissions: userInput.permissions ?? [],
      }),
    );

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

    roleId = roleResult.data!.createRole.id;
  }

  // 2. Create user (also creates IDP user and returns initialPassword)
  const createResult = await mutationWithRetry(() =>
    graphqlClient.mutation(CREATE_USER_MUTATION, {
      name: userInput.name,
      email: userInput.email,
      roleIds: roleId ? [roleId] : undefined,
    }),
  );

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

  const created = createResult.data!.createUser;

  return {
    name: created.name,
    email: created.email,
    password: created.initialPassword,
    userId: created.id,
    roleId,
  };
}
