import { error } from '@sveltejs/kit';
import type { ItemCount, TodoItem, Token, User } from './types';
import { Endpoint } from './types';
import type { StringMapping } from '$lib/utils/types';
import { getTokenFromLocalstorage } from '$lib/auth/session';

/**
 * A factory class for creating `Endpoint` instances to interact with the backend API.
 *
 * This class provides methods to create specific `Endpoint` instances for different API operations,
 * such as retrieving user data, retrieving todo items, and more. It follows the factory pattern to
 * encapsulate the creation of `Endpoint` instances with the necessary configurations and headers
 * for making API requests.
 *
 * The `EndpointFactory` constructor accepts an optional `fetchFunction` parameter, which is the
 * function used for making API requests. If no `fetchFunction` is provided, the default `fetch`
 * function is used.
 *
 * Example usage:
 * ```typescript
 * const endpointFactory = new EndpointFactory();
 *
 * const readUserEndpoint = endpointFactory.createReadUserEndpoint(123);
 * const users = await readUserEndpoint.call();
 *
 * const readTodosEndpoint = endpointFactory.createReadTodosEndpoint(123);
 * const todos = await readTodosEndpoint.call();
 * ```
 */
export class EndpointFactory {

  /** The function to use for making API requests. */
  readonly fetchFunction: Function;

  // The JSON web token to send as an authorization bearer token.
  private _jwt: string | null;

  /**
   * Constructs a new `EndpointFactory` instance.
   * @param fetchFunction - The function Endpoints created by this factory use for making API requests. (Default: fetch)
   */
  constructor(fetchFunction: Function = fetch) {
    this.fetchFunction = fetchFunction;
    this._jwt = getTokenFromLocalstorage();
  }

  private _getDefaultHeaders(): StringMapping {
    return {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
    }
  }

  private _getDefaultHeadersWithAuth(): StringMapping {
    if (this._jwt === null) {
      throw error(401, 'You are not logged in.');
    }

    let headers = this._getDefaultHeaders();
    headers['Authorization'] = `Bearer: ${this._jwt}`;

    return headers;
  }

  /**
   * Creates an Endpoint which retrieves the user with the specified ID from the backend API.
   *
   * @param {number} userId - The ID of the user whom to retrieve.
   * @throws {error} If the user is not currently logged in.
   */
  createReadUserEndpoint(userId: number): Endpoint<User> {
    return new Endpoint<User>(
      `/api/users/${userId}`,
      'GET',
      this._getDefaultHeadersWithAuth(),
      this.fetchFunction
    );
  }

  /**
   * Creates an Endpoint which retrieves the list of all users from the backend API.
   *
   * @throws {error} If the user is not currently logged in.
   */
  createReadUsersEndpoint(): Endpoint<User[]> {
    return new Endpoint<User[]>(
      `/api/admin/users/`,
      'GET',
      this._getDefaultHeadersWithAuth(),
      this.fetchFunction
    );
  }

  /**
   * Creates an Endpoint which retrieves the total user count from the backend API.
   *
   * @throws {error} If the user is not currently logged in.
   */
  createReadUserCountEndpoint(): Endpoint<ItemCount> {
    return new Endpoint<ItemCount>(
      `/api/admin/users/total/`,
      'GET',
      this._getDefaultHeadersWithAuth(),
      this.fetchFunction
    );
  }

  /**
   * Creates an Endwpoint which retrieves the list of all todo-items for the user with the specified ID from the backend API.
   *
   * @param {number} userId - The ID of the user whose todo-items to retrieve.
   * @throws {error} If the user is not currently logged in.
   */
  createReadTodosEndpoint(userId: number): Endpoint<TodoItem[]> {
    return new Endpoint<TodoItem[]>(
      `/api/todos/user/${userId}`,
      'GET',
      this._getDefaultHeadersWithAuth(),
      this.fetchFunction
    );
  }

  /**
   * Creates an Endpoint which retrieves the total todo-item count for the user with the specified ID from the backend API.
   *
   * @param {number} userId - The ID of the user whose todo-items to retrieve.
   * @throws {error} If the user is not currently logged in.
   */
  createReadTodoCountEndpoint(userId: number): Endpoint<ItemCount> {
    return new Endpoint<ItemCount>(
      `/api/todos/user/${userId}/total/`,
      'GET',
      this._getDefaultHeadersWithAuth(),
      this.fetchFunction
    );
  }

  /**
   * Creates an Endpoint which retrieves the todo-item with the specified ID from the backend API.
   *
   * @param {number} todoId - The ID of the todo-item to be retrieved.
   * @throws {error} If the user is not currently logged in.
   */
  createReadTodoEndpoint(todoId: number): Endpoint<TodoItem> {
    return new Endpoint<TodoItem>(
      `/api/todos/${todoId}`,
      'GET',
      this._getDefaultHeadersWithAuth(),
      this.fetchFunction
    );
  }

  /**
   * Creates an Endpoint which sends an API request containing the specified login credentials
   * to the backend, and returns the JWT retrieved from the response on sucess.
   *
   * @throws {error} - If the request fails or is not permitted.
   */
  createLoginEndpoint(): Endpoint<Token> {
    return new Endpoint<Token>(
      `/api/auth/login/`,
      'POST',
      this._getDefaultHeaders(),
      this.fetchFunction
    );
  }
}
