import { DecodedJwt, createDecoder } from "fast-jwt";

/**
 * RateLimiter
 * Limits the rate of API calls to avoid overwhelming the server
 */
export class RateLimiter {
  private requestsPerSecond: number;
  private requestTimes: number[] = [];

  constructor(requestsPerSecond: number = 10) {
    this.requestsPerSecond = requestsPerSecond;
  }

  /**
   * Throttle requests to stay within rate limit
   * Waits if rate limit would be exceeded
   */
  async throttle(): Promise<void> {
    const now = Date.now();
    // Remove timestamps older than 1 second
    this.requestTimes = this.requestTimes.filter((t) => now - t < 1000);

    if (this.requestTimes.length >= this.requestsPerSecond) {
      const oldestRequest = this.requestTimes[0];
      const waitTime = 1000 - (now - oldestRequest);
      if (waitTime > 0) {
        await new Promise((resolve) => setTimeout(resolve, waitTime));
      }
    }

    this.requestTimes.push(Date.now());
  }

  /**
   * Reset the rate limiter (e.g., after a long pause)
   */
  reset(): void {
    this.requestTimes = [];
  }
}

/**
 * Check if an error is a network error (retryable)
 * @param error - Error to check
 * @returns true if error is network-related and retryable
 */
function isNetworkError(error: any): boolean {
  if (!error) return false;

  // Check for fetch network errors
  if (error.name === "TypeError" && error.message.includes("fetch")) {
    return true;
  }

  // Check for ECONNREFUSED, ETIMEDOUT, etc.
  if (error.code && ["ECONNREFUSED", "ETIMEDOUT", "ECONNRESET", "ENOTFOUND"].includes(error.code)) {
    return true;
  }

  return false;
}

/**
 * Check if an HTTP status code is retryable
 * @param status - HTTP status code
 * @returns true if status code indicates a temporary error
 */
function isRetryableStatus(status: number): boolean {
  // Retry on 5xx errors (server errors) and 429 (rate limit)
  return status >= 500 || status === 429;
}

/**
 * Retry wrapper with exponential backoff
 * Retries failed operations with increasing delay between attempts
 *
 * @param fn - Function to execute
 * @param maxRetries - Maximum number of retry attempts
 * @param baseDelay - Base delay in milliseconds (doubles each retry)
 * @returns Promise with result of fn
 */
export async function withRetry<T>(
  fn: () => Promise<T>,
  maxRetries: number = 3,
  baseDelay: number = 1000,
): Promise<T> {
  let lastError: any;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error: any) {
      lastError = error;

      // Check if error is retryable
      const isRetryable = isNetworkError(error) || isRetryableStatus(error?.status);

      if (!isRetryable) {
        // Don't retry on permanent errors (4xx except 429)
        throw error;
      }

      if (attempt < maxRetries) {
        // Calculate delay with exponential backoff
        const delay = baseDelay * Math.pow(2, attempt);
        console.log(
          `[withRetry] Attempt ${attempt + 1}/${maxRetries + 1} failed, retrying in ${delay}ms...`,
          error.message || error,
        );
        await new Promise((resolve) => setTimeout(resolve, delay));
      }
    }
  }

  throw lastError;
}

/**
 * Fetch with timeout
 * Aborts request if it takes longer than specified timeout
 */
async function fetchWithTimeout(
  url: RequestInfo | URL,
  options: RequestInit = {},
  timeout: number = 10000,
): Promise<Response> {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeout);

  try {
    const response = await fetch(url, {
      ...options,
      signal: controller.signal,
    });
    clearTimeout(timeoutId);
    return response;
  } catch (error: any) {
    clearTimeout(timeoutId);
    if (error.name === "AbortError") {
      throw new Error(`Request timeout after ${timeout}ms`);
    }
    throw error;
  }
}

const KC_URL = process.env.KC_URL;
const KC_REALMS = process.env.KC_REALMS;
const KC_CLIENT_ID = process.env.KC_SERVICE_ACCOUNT_CLIENT_ID;
const KC_SECRET = process.env.KC_SERVICE_ACCOUNT_SECRET;
const AUTH_ACCOUNT_SECRET = process.env.AUTH_ACCOUNT_SECRET;
const API_KEY = process.env.API_KEY;

let token: string | null = null;
let decoded: DecodedJwt | null = null;

const jwtDecode = createDecoder({ complete: true });

/**
 * Check if token is expired or will expire in 30 seconds
 * @returns true if expire or can't get exp, false otherwise
 */
export function isTokenExpired(token: string, beforeExpire: number = 30) {
  decoded = jwtDecode(token);

  if (decoded && decoded.payload.exp) {
    return Date.now() / 1000 >= decoded.payload.exp - beforeExpire;
  }

  return true;
}

/**
 * Get token from keycloak if needed
 * Returns null if Keycloak is unavailable
 */
export async function getToken(): Promise<string | null> {
  if (!KC_CLIENT_ID || !KC_SECRET) {
    console.error("[getToken] KC_CLIENT_ID and KC_SECRET are required");
    return null;
  }

  if (token && !isTokenExpired(token)) return token;

  const body = new URLSearchParams();

  body.append("client_id", KC_CLIENT_ID);
  body.append("client_secret", KC_SECRET);
  body.append("grant_type", "client_credentials");

  try {
    const res = await fetchWithTimeout(
      `${KC_URL}/realms/${KC_REALMS}/protocol/openid-connect/token`,
      {
        method: "POST",
        body: body,
      },
      10000,
    );

    if (!res.ok) {
      console.error(`[getToken] Keycloak token request failed: ${res.status}`);
      return null;
    }

    const data = (await res.json()) as any;

    if (data && data.access_token) {
      token = data.access_token;
      console.log(`[getToken] Token refreshed successfully`);
      return token;
    }

    console.error("[getToken] No access_token in response");
    return null;
  } catch (error: any) {
    console.error(`[getToken] Failed to get token: ${error.message}`);
    return null;
  }
}

/**
 * Create keycloak user by given username and password with roles
 *
 * Client must have permission to manage realm's user
 *
 * @returns user uuid or true if success, false otherwise.
 */
export async function createUser(
  username: string,
  password: string,
  opts?: Record<string, any>,
  token?: string,
) {
  const authToken = token || (await getToken());
  if (!authToken) {
    console.error("[createUser] Failed to get Keycloak token");
    return false;
  }

  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${authToken}`,
      "content-type": `application/json`,
    },
    method: "POST",
    body: JSON.stringify({
      enabled: true,
      credentials: [{ type: "password", value: password, temporary: false }],
      username,
      ...opts,
    }),
  }).catch((e) => console.log("Keycloak Error: ", e));

  if (!res) return false;
  if (!res.ok) {
    return await res.json();
  }

  const path = res.headers.get("Location");
  const id = path?.split("/").at(-1);
  return id || true;
}

/**
 * Get keycloak user by uuid
 *
 * Client must have permission to manage realm's user
 *
 * @returns user if success, false otherwise.
 */
export async function getUser(userId: string, token?: string) {
  const authToken = token || (await getToken());
  if (!authToken) {
    console.error("[getUser] Failed to get Keycloak token");
    return false;
  }

  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${authToken}`,
      "content-type": `application/json`,
    },
  }).catch((e) => console.log("Keycloak Error: ", e));
  if (!res) return false;
  if (!res.ok) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }
  return await res.json();
}

/**
 * Get keycloak user by Username (citizenId)
 *
 * Client must have permission to manage realm's user
 *
 * @returns user if success, false otherwise.
 */
export async function getUserByUsername(citizenId: string, token?: string) {
  const authToken = token || (await getToken());
  if (!authToken) {
    console.error("[getUserByUsername] Failed to get Keycloak token");
    return false;
  }

  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users?username=${citizenId}`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${authToken}`,
      "content-type": `application/json`,
    },
  }).catch((e) => console.log("Keycloak Error: ", e));
  if (!res) return false;
  if (!res.ok) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }
  return await res.json();
}

/**
 * Get keycloak user list
 *
 * Client must have permission to manage realm's user
 *
 * @returns user list if success, false otherwise.
 */
export async function getUserList(first = "", max = "", search = "") {
  const res = await fetch(
    // `${KC_URL}/admin/realms/${KC_REALMS}/users`.concat(!!search ? `?search=${search}` : ""),
    `${KC_URL}/admin/realms/${KC_REALMS}/users?first=${first || "0"}&max=${max || "-1"}${search ? `&search=${search}` : ""}`,
    {
      // prettier-ignore
      headers: {
      "authorization": `Bearer ${await getToken()}`,
      "content-type": `application/json`,
    },
    },
  ).catch((e) => console.log("Keycloak Error: ", e));

  if (!res) return false;
  if (!res.ok) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  return ((await res.json()) as any[]).map((v: Record<string, string>) => ({
    id: v.id,
    username: v.username,
    firstName: v.firstName,
    lastName: v.lastName,
    email: v.email,
    enabled: v.enabled,
  }));
}

export async function getUserCount(first = "", max = "", search = "") {
  const res = await fetch(
    `${KC_URL}/admin/realms/${KC_REALMS}/users/count?first=${first || "0"}&max=${max || "-1"}${search ? `&search=${search}` : ""}`,
    {
      headers: {
        authorization: `Bearer ${await getToken()}`,
        "content-type": `application/json`,
      },
    },
  ).catch((e) => console.log("Keycloak Error: ", e));

  if (!res) return false;
  if (!res.ok) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  return await res.json();
}

/**
 * Get keycloak user list
 *
 * Client must have permission to manage realm's user
 *
 * @returns user list if success, false otherwise.
 */
export async function getUserListOrg(first = "", max = "", search = "", userIds: string[] = []) {
  const userIdsParam = userIds.join(",");
  const res = await fetch(
    // `${KC_URL}/admin/realms/${KC_REALMS}/users`.concat(!!search ? `?search=${search}` : ""),
    `${KC_URL}/admin/realms/${KC_REALMS}/users?first=${first || "0"}&max=${max || "-1"}${search ? `&search=${search}` : ""}${userIdsParam ? `&id=${userIdsParam}` : ""}`,
    {
      // prettier-ignore
      headers: {
      "authorization": `Bearer ${await getToken()}`,
      "content-type": `application/json`,
    },
    },
  ).catch((e) => console.log("Keycloak Error: ", e));

  if (!res) return false;
  if (!res.ok) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  return ((await res.json()) as any[]).map((v: Record<string, string>) => ({
    id: v.id,
    username: v.username,
    firstName: v.firstName,
    lastName: v.lastName,
    email: v.email,
    enabled: v.enabled,
  }));
}

export async function getUserCountOrg(first = "", max = "", search = "", userIds: string[] = []) {
  const userIdsParam = userIds.join(",");
  const res = await fetch(
    `${KC_URL}/admin/realms/${KC_REALMS}/users/count?first=${first || "0"}&max=${max || "-1"}${search ? `&search=${search}` : ""}${userIdsParam && userIdsParam != "" ? `&id=${userIdsParam}` : ""}`,
    {
      headers: {
        authorization: `Bearer ${await getToken()}`,
        "content-type": `application/json`,
      },
    },
  ).catch((e) => console.log("Keycloak Error: ", e));

  if (!res) return false;
  if (!res.ok) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  return await res.json();
}

/**
 * Update keycloak user by uuid
 *
 * Client must have permission to manage realm's user
 *
 * @returns user uuid or true if success, false otherwise.
 */
export async function editUser(userId: string, opts: Record<string, any>) {
  const { password, ...rest } = opts;

  const token = await getToken();
  if (!token) {
    console.error("[editUser] Failed to get Keycloak token");
    return false;
  }

  // Get existing user data to preserve other fields
  const existingUser = await getUser(userId, token);
  if (!existingUser) {
    console.error(`[editUser] User ${userId} not found in Keycloak`);
    return false;
  }

  // Merge existing user data with updated fields
  const updatedUser = {
    ...existingUser,
    ...rest,
    credentials: (password && [{ type: "password", value: opts?.password }]) || undefined,
  };

  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${token}`,
      "content-type": `application/json`,
    },
    method: "PUT",
    body: JSON.stringify(updatedUser),
  }).catch((e) => console.log("Keycloak Error: ", e));

  if (!res) return false;
  if (!res.ok) {
    return await res.json();
  }

  const path = res.headers.get("Location");
  const id = path?.split("/").at(-1);
  return id || true;
}
/**
 * Update keycloak user by uuid
 *
 * Client must have permission to manage realm's user
 *
 * @returns user uuid or true if success, false otherwise.
 */
export async function updateName(
  userId: string,
  firstName: string,
  lastName: string,
  prefix: string,
  // opts: Record<string, any>,
) {
  // const { password, ...rest } = opts;

  // Get existing user data to preserve other fields
  const existingUser = await getUser(userId);
  if (!existingUser) {
    console.error(`[updateName] User ${userId} not found in Keycloak`);
    return false;
  }

  // Merge existing user data with updated name fields
  const updatedUser = {
    ...existingUser,
    firstName,
    lastName,
    attributes: {
      ...(existingUser.attributes || {}),
      prefix,
    },
  };

  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${await getToken()}`,
      "content-type": `application/json`,
    },
    method: "PUT",
    body: JSON.stringify(updatedUser),
  }).catch((e) => console.log("Keycloak Error: ", e));

  if (!res) return false;
  if (!res.ok) {
    // return Boolean(console.error("Keycloak Error Response: ", await res.json()));
    return await res.json();
  }

  const path = res.headers.get("Location");
  const id = path?.split("/").at(-1);
  return id || true;
}

/**
 * enable keycloak user by uuid
 *
 * Client must have permission to manage realm's user
 *
 * @returns user uuid or true if success, false otherwise.
 */
export async function enableStatus(userId: string, status: boolean) {
  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
    headers: {
      authorization: `Bearer ${await getToken()}`,
      "content-type": `application/json`,
    },
    method: "PUT",
    body: JSON.stringify({
      enabled: status,
    }),
  }).catch((e) => console.log("Keycloak Error: ", e));

  if (!res) return false;
  if (!res.ok) {
    return await res.json();
  }

  const path = res.headers.get("Location");
  const id = path?.split("/").at(-1);
  return id || true;
}

/**
 * Delete keycloak user by uuid
 *
 * Client must have permission to manage realm's user
 *
 * @returns user true if success, false otherwise.
 */
export async function deleteUser(userId: string, token?: string) {
  const authToken = token || (await getToken());
  if (!authToken) {
    console.error("[deleteUser] Failed to get Keycloak token");
    return false;
  }

  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${authToken}`,
      "content-type": `application/json`,
    },
    method: "DELETE",
  }).catch((e) => console.log("Keycloak Error: ", e));

  if (!res) return false;
  if (!res.ok) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }
  return true;
}

/**
 * Get keycloak user by uuid
 *
 * Client must have permission to manage realm's user
 *
 * @returns user if success, false otherwise.
 */
export async function getRoleMappings(userId: string) {
  const res = await fetch(
    `${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/role-mappings/realm`,
    {
      headers: {
        authorization: `Bearer ${await getToken()}`,
        "content-type": `application/json`,
      },
    },
  ).catch((e) => console.log("Keycloak Error: ", e));
  if (!res) return false;
  if (!res.ok) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  return await res.json();
}

/**
 * Get roles list or specific role data
 *
 * Client must have permission to get realms roles
 *
 * @returns role's info (array if not specify name) if success, null if not found, false otherwise.
 */
export async function getRoles(name?: string, token?: string) {
  const res = await fetch(
    `${KC_URL}/admin/realms/${KC_REALMS}/roles`.concat((name && `/${name}`) || ""),
    {
      // prettier-ignore
      headers: {
        "authorization": `Bearer ${token || await getToken()}`,
      },
    },
  ).catch((e) => console.log(e));

  if (!res) return false;
  if (!res.ok && res.status !== 404) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  if (res.status === 404) {
    return null;
  }

  const data = (await res.json()) as any;

  if (Array.isArray(data)) {
    return data.map((v: Record<string, string>) => ({
      id: v.id,
      name: v.name,
      description: v.description,
    }));
  }

  // return {
  //   id: data.id,
  //   name: data.name,
  // };
}

/**
 * Get roles list of user
 *
 * Client must have permission to get realms roles
 *
 * @returns role's info (array if not specify name) if success, null if not found, false otherwise.
 */
export async function getUserRoles(userId: string) {
  const res = await fetch(
    `${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/role-mappings/realm`,
    {
      // prettier-ignore
      headers: {
        "authorization": `Bearer ${await getToken()}`,
      },
    },
  ).catch((e) => console.log(e));

  if (!res) return false;
  if (!res.ok && res.status !== 404) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  if (res.status === 404) {
    return null;
  }

  const data = (await res.json()) as any;

  if (Array.isArray(data)) {
    return data.map((v: Record<string, string>) => ({ id: v.id, name: v.name }));
  }

  return {
    id: data.id,
    name: data.name,
  };
}

/**
 * Assign role to user
 *
 * Client must have permission to manage realm's user roles
 *
 * @returns true if success, false otherwise.
 */
export async function addUserRoles(
  userId: string,
  roles: { id: string; name: string }[],
  token?: string,
) {
  const res = await fetch(
    `${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/role-mappings/realm`,
    {
      // prettier-ignore
      headers: {
        "authorization": `Bearer ${token || await getToken()}`,
        "content-type": `application/json`,
      },
      method: "POST",
      body: JSON.stringify(roles),
    },
  ).catch((e) => console.log(e));

  if (!res) return false;
  if (!res.ok) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }
  return true;
}

/**
 * Remove role from user
 *
 * Client must have permission to manage realm's user roles
 *
 * @returns true if success, false otherwise.
 */
export async function removeUserRoles(userId: string, roles: { id: string; name: string }[]) {
  const res = await fetch(
    `${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/role-mappings/realm`,
    {
      // prettier-ignore
      headers: {
        "authorization": `Bearer ${await getToken()}`,
        "content-type": `application/json`,
      },
      method: "DELETE",
      body: JSON.stringify(roles),
    },
  ).catch((e) => console.log(e));

  if (!res) return false;
  if (!res.ok) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }
  return true;
}

/**
 * Get group list or specific group data
 *
 * Client must have permission to manage realms group
 *
 * @returns group's info (array if not specify name) if success, null if not found, false otherwise.
 */
export async function getGroups(id?: string) {
  const res = await fetch(
    `${KC_URL}/admin/realms/${KC_REALMS}/groups`.concat((id && `/${id}`) || ""),
    {
      // prettier-ignore
      headers: {
        "authorization": `Bearer ${await getToken()}`,
      },
    },
  ).catch((e) => console.log(e));

  if (!res) return false;
  if (!res.ok && res.status !== 404) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  if (res.status === 404) {
    return null;
  }

  const data = (await res.json()) as any;

  if (Array.isArray(data)) {
    return data.map((v: Record<string, string>) => ({ id: v.id, name: v.name }));
  }

  return {
    id: data.id,
    name: data.name,
  };
}

/**
 * Create group
 *
 * Client must have permission to manage realms group
 *
 * @returns true if success, false otherwise.
 */
export async function createGroup(name: string) {
  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/groups`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${await getToken()}`,
      "content-type": `application/json`,
    },
    method: "POST",
    body: JSON.stringify({ name }),
  }).catch((e) => console.log(e));

  if (!res) return false;
  if (!res.ok && res.status !== 404) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  return true;
}

/**
 * Edit group
 *
 * Client must have permission to manage realms group
 *
 * @returns true if success, false otherwise.
 */
export async function editGroup(id: string, name: string) {
  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/groups/${id}`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${await getToken()}`,
      "content-type": `application/json`,
    },
    method: "PUT",
    body: JSON.stringify({ name }),
  }).catch((e) => console.log(e));

  if (!res) return false;
  if (!res.ok && res.status !== 404) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  return true;
}

/**
 * Delete group
 *
 * Client must have permission to manage realms group
 *
 * @returns true if success, false otherwise.
 */
export async function deleteGroup(id: string) {
  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/groups/${id}`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${await getToken()}`,
    },
    method: "DELETE",
  }).catch((e) => console.log(e));

  if (!res) return false;
  if (!res.ok && res.status !== 404) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  return true;
}

/**
 * Get group list or specific group data
 *
 * Client must have permission to manage realms group
 *
 * @returns group's info (array if not specify name) if success, null if not found, false otherwise.
 */
export async function getUserGroups(userId?: string) {
  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/groups`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${await getToken()}`,
    },
  }).catch((e) => console.log(e));

  if (!res) return false;
  if (!res.ok && res.status !== 404) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  if (res.status === 404) {
    return null;
  }

  const data = (await res.json()) as any;

  if (Array.isArray(data)) {
    return data.map((v: Record<string, string>) => ({ id: v.id, name: v.name }));
  }

  return {
    id: data.id,
    name: data.name,
  };
}

/**
 * Add group to user
 *
 * Client must have permission to manage user group
 *
 * @returns true if success, false otherwise.
 */
export async function addUserGroup(userId: string, groupId: string) {
  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/groups/${groupId}`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${await getToken()}`,
    },
    method: "PUT",
  }).catch((e) => console.log(e));

  if (!res) return false;
  if (!res.ok && res.status !== 404) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  return true;
}

/**
 * Delete group from user
 *
 * Client must have permission to manage user group
 *
 * @returns true if success, false otherwise.
 */
export async function removeUserGroup(userId: string, groupId: string) {
  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/groups/${groupId}`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${await getToken()}`,
    },
    method: "PUT",
  }).catch((e) => console.log(e));

  if (!res) return false;
  if (!res.ok && res.status !== 404) {
    return Boolean(console.error("Keycloak Error Response: ", await res.json()));
  }

  return true;
}

// Function to change user password
export async function changeUserPassword(userId: string, newPassword: string) {
  try {
    const token = await getToken();
    if (!token) {
      console.error("[changeUserPassword] Failed to get Keycloak token");
      return false;
    }

    const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/reset-password`, {
      // prettier-ignore
      headers: {
          "authorization": `Bearer ${token}`,
          "content-type": `application/json`,
        },
      method: "PUT",
      body: JSON.stringify({
        type: "password",
        value: newPassword,
        temporary: false, // Set to true if the user must reset their password on next login
      }),
    }).catch((e) => console.log("Keycloak Error: ", e));

    if (!res) {
      console.error("[changeUserPassword] No response from Keycloak");
      return false;
    }
    if (!res.ok) {
      console.error(`[changeUserPassword] Failed to change password: ${res.status}`);
      return false;
    }

    return true;
  } catch (error) {
    console.error("Error changing password:", error);
    return false;
  }
}

// Function to reset password
export async function resetPassword(username: string) {
  try {
    const token = await getToken();
    if (!token) {
      console.error("[resetPassword] Failed to get Keycloak token");
      return false;
    }

    const users = await fetchWithTimeout(
      `${KC_URL}/admin/realms/${KC_REALMS}/users?email=${encodeURIComponent(username)}`,
      {
        headers: {
          authorization: `Bearer ${token}`,
          "content-type": `application/json`,
        },
      },
      10000,
    );

    if (!users.ok) {
      const errorText = await users.text();
      console.error(
        `[resetPassword] Failed to search user. Status: ${users.status}, Error: ${errorText}`,
      );
      return false;
    }

    const usersData = await users.json();

    if (!usersData || usersData.length === 0) {
      console.error(`[resetPassword] User not found with email: ${username}`);
      return false;
    }

    const userId = usersData[0].id;

    const resetResponse = await fetchWithTimeout(
      `${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}/execute-actions-email`,
      {
        method: "PUT",
        headers: {
          Authorization: `Bearer ${await getToken()}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(["UPDATE_PASSWORD"]),
      },
      10000,
    );

    if (!resetResponse.ok) {
      const errorText = await resetResponse.text();
      console.error(
        `[resetPassword] Failed to send reset email. Status: ${resetResponse.status}, Error: ${errorText}`,
      );
      return false;
    }

    console.log(`[resetPassword] Password reset email sent successfully to: ${username}`);
    return { message: "Password reset email sent" };
  } catch (error: any) {
    console.error(`[resetPassword] Error triggering password reset: ${error.message}`);
    return false;
  }
}

export async function updateUserAttributes(
  userId: string,
  attributes: Record<string, string[]>,
): Promise<boolean> {
  try {
    const token = await getToken();
    if (!token) {
      console.error("[updateUserAttributes] Failed to get Keycloak token");
      return false;
    }

    // Get existing user data to preserve other attributes
    const existingUser = await getUser(userId, token);

    if (!existingUser) {
      console.error(`User ${userId} not found in Keycloak`);
      return false;
    }

    // Merge existing attributes with new attributes
    // IMPORTANT: Spread all existing user fields to preserve firstName, lastName, email, etc.
    // The Keycloak PUT endpoint performs a full update, so we must include all fields
    const updatedAttributes = {
      ...existingUser,
      attributes: {
        ...(existingUser.attributes || {}),
        ...attributes,
      },
    };

    console.log(
      `[updateUserAttributes] Sending to Keycloak:`,
      JSON.stringify(updatedAttributes, null, 2),
    );

    const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users/${userId}`, {
      headers: {
        authorization: `Bearer ${token}`,
        "content-type": "application/json",
      },
      method: "PUT",
      body: JSON.stringify(updatedAttributes),
    }).catch((e) => {
      console.error(`[updateUserAttributes] Network error:`, e);
      return null;
    });

    if (!res) {
      console.error(`[updateUserAttributes] No response from Keycloak`);
      return false;
    }

    if (!res.ok) {
      const errorText = await res.text();
      console.error(`[updateUserAttributes] Keycloak Error (${res.status}):`, errorText);
      return false;
    }

    // console.log(`[updateUserAttributes] Successfully updated attributes for user ${userId}`);
    return true;
  } catch (error) {
    console.error(`[updateUserAttributes] Error updating attributes for user ${userId}:`, error);
    return false;
  }
}

export async function getAllUsersPaginated(
  search: string = "",
  batchSize: number = 100,
): Promise<
  | Array<{
      id: string;
      username: string;
      firstName?: string;
      lastName?: string;
      email?: string;
      enabled: boolean;
    }>
  | false
> {
  const allUsers: any[] = [];
  let first = 0;
  let hasMore = true;

  while (hasMore) {
    const res = await fetch(
      `${KC_URL}/admin/realms/${KC_REALMS}/users?first=${first}&max=${batchSize}${search ? `&search=${search}` : ""}`,
      {
        headers: {
          authorization: `Bearer ${await getToken()}`,
          "content-type": `application/json`,
        },
      },
    ).catch((e) => console.log("Keycloak Error: ", e));

    if (!res) return false;
    if (!res.ok) {
      const errorText = await res.text();
      console.error("Keycloak Error Response: ", errorText);
      return false;
    }

    const rawText = await res.text();

    try {
      const batch = JSON.parse(rawText) as any[];

      if (batch.length === 0) {
        hasMore = false;
      } else {
        allUsers.push(...batch);
        first += batch.length;
        hasMore = batch.length === batchSize;

        // Log progress for large datasets
        if (allUsers.length % 500 === 0) {
          console.log(`[getAllUsersPaginated] Fetched ${allUsers.length} users so far...`);
        }
      }
    } catch (error) {
      console.error(`[getAllUsersPaginated] Failed to parse JSON response at offset ${first}:`);
      console.error(
        `[getAllUsersPaginated] Response preview (first 500 chars):`,
        rawText.substring(0, 500),
      );
      console.error(
        `[getAllUsersPaginated] Response preview (last 200 chars):`,
        rawText.slice(-200),
      );
      throw new Error(`Failed to parse Keycloak response as JSON at batch starting at ${first}.`);
    }
  }

  console.log(`[getAllUsersPaginated] Total users fetched: ${allUsers.length}`);

  return allUsers.map((v: any) => ({
    id: v.id,
    username: v.username,
    firstName: v.firstName,
    lastName: v.lastName,
    email: v.email,
    enabled: v.enabled === true || v.enabled === "true",
  }));
}

/**
 * Create keycloak user by given username and password with roles
 *
 * Client must have permission to manage realm's user
 *
 * @returns user uuid or true if success, false otherwise.
 */
export async function createUserHaveProfile(
  username: string,
  password: string,
  profileId: string,
  prefix: string,
  opts?: Record<string, any>,
  token?: string,
) {
  const res = await fetch(`${KC_URL}/admin/realms/${KC_REALMS}/users`, {
    // prettier-ignore
    headers: {
      "authorization": `Bearer ${token || await getToken()}`,
      "content-type": `application/json`,
    },
    method: "POST",
    body: JSON.stringify({
      enabled: true,
      credentials: [{ type: "password", value: password, temporary: false }],
      username,
      ...opts,
    }),
  }).catch((e) => console.log("Keycloak Error: ", e));

  if (!res) return false;
  if (!res.ok) {
    // return Boolean(console.error("Keycloak Error Response: ", await res.json()));
    return await res.json();
  }

  const path = res.headers.get("Location");
  const id = path?.split("/").at(-1);
  return id || true;
}
