#pragma once

#include "Assets.h"
#include "Profile.h"
#include "Response.h"
#include "Token.h"
#include "TokenList.h"

#include <CesiumAsync/AsyncSystem.h>
#include <CesiumAsync/IAssetAccessor.h>
#include <CesiumAsync/Library.h>

#include <cstdint>

namespace CesiumIonClient {

/**
 * @brief Whether sorted results should be ascending or descending.
 */
enum class SortOrder { Ascending, Descending };

/**
 * @brief Options to be passed to {@link Connection::tokens}.
 */
struct ListTokensOptions {
  /**
   * @brief The maximum number of tokens to return in a single page.
   *
   * Receiving fewer tokens should not be interpreted as the end of the
   * collection. The end of the collection is reached when the response does not
   * contain {@link Response::nextPageUrl}.
   */
  std::optional<int32_t> limit;

  /**
   * @brief The page number, where the first page of results is page 1 (not 0).
   */
  std::optional<int32_t> page;

  /**
   * @brief One or more keywords separated by whitespace by which to
   * filter the list of tokens. The token name will contain each keyword of the
   * search string.
   */
  std::optional<std::string> search;

  /**
   * @brief The property by which to sort results. Valid values are
   * `"NAME"` and `"LAST_USED"`.
   */
  std::optional<std::string> sortBy;

  /**
   * @brief The property by which to sort results. Valid values are
   * `"NAME"` and `"LAST_USED"`.
   */
  std::optional<SortOrder> sortOrder;
};

/**
 * @brief A connection to Cesium ion that can be used to interact with it via
 * its REST API.
 */
class CESIUMASYNC_API Connection {
public:
  /**
   * @brief Authorizes access to Cesium ion on behalf of a user, and returns a
   * {@link Connection} that can be used to interact with ion.
   *
   * Uses the "Authorization Code with PKCE" OAuth2 flow.
   *
   * See [Connecting to Cesium ion with
   * OAuth2](https://cesium.com/learn/ion/ion-oauth2/) for a description of the
   * authorization process.
   *
   * @param asyncSystem The async system used to do work in threads.
   * @param pAssetAccessor The interface used to interact with the Cesium ion
   * REST API.
   * @param friendlyApplicationName A friendly name for the application
   * requesting access. It will be displayed to the user when authorization is
   * complete, informing them that they can return to the original application.
   * @param clientID The client ID that was assigned to your application when
   * you registered it.
   * @param redirectPath The path on http://127.0.0.1 that a user will be
   * redirected to once they authorize your application. This must match the URI
   * provided when you registered your application, without the protocol,
   * hostname, or port.
   * @param scopes The list of scopes that the eventually-granted token should
   * allow access to.
   * @param openUrlCallback A function that is invoked to launch the user's web
   * browser with a given URL so that they can authorize access.
   * @param ionApiUrl The base URL of the Cesium ion API.
   * @param ionAuthorizeUrl The URL of the Cesium ion OAuth authorization page.
   * @return A future that resolves to a Cesium ion {@link Connection} once the
   * user authorizes the application and the token handshake completes.
   */
  static CesiumAsync::Future<Connection> authorize(
      const CesiumAsync::AsyncSystem& asyncSystem,
      const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor,
      const std::string& friendlyApplicationName,
      int64_t clientID,
      const std::string& redirectPath,
      const std::vector<std::string>& scopes,
      std::function<void(const std::string&)>&& openUrlCallback,
      const std::string& ionApiUrl = "https://api.cesium.com/",
      const std::string& ionAuthorizeUrl = "https://cesium.com/ion/oauth");

  /**
   * @brief Creates a connection to Cesium ion using the provided access token.
   *
   * @param asyncSystem The async system used to do work in threads.
   * @param pAssetAccessor The interface used to interact with the Cesium ion
   * REST API.
   * @param accessToken The access token
   * @param apiUrl The base URL of the Cesium ion API.
   */
  Connection(
      const CesiumAsync::AsyncSystem& asyncSystem,
      const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor,
      const std::string& accessToken,
      const std::string& apiUrl = "https://api.cesium.com");

  /**
   * @brief Gets the async system used by this connection to do work in threads.
   */
  const CesiumAsync::AsyncSystem& getAsyncSystem() const noexcept {
    return this->_asyncSystem;
  }

  /**
   * @brief Gets the interface used by this connection to interact with the
   * Cesium ion REST API.
   */
  const std::shared_ptr<CesiumAsync::IAssetAccessor>&
  getAssetAccessor() const noexcept {
    return this->_pAssetAccessor;
  }

  /**
   * @brief Gets the access token used by this connection.
   */
  const std::string& getAccessToken() const noexcept {
    return this->_accessToken;
  }

  /**
   * @brief Gets the Cesium ion API base URL.
   */
  const std::string& getApiUrl() const noexcept { return this->_apiUrl; }

  /**
   * @brief Retrieves profile information for the access token currently being
   * used to make API calls.
   *
   * This route works with any valid token, but additional information is
   * returned if the token uses the `profile:read` scope.
   *
   * @return A future that resolves to the profile information.
   */
  CesiumAsync::Future<Response<Profile>> me() const;

  /**
   * @brief Gets the list of available assets.
   *
   * @return A future that resolves to the asset information.
   */
  CesiumAsync::Future<Response<Assets>> assets() const;

  /**
   * @brief Invokes the "List tokens" service to get the list of available
   * tokens.
   *
   * Only a single page is returned. To obtain additional pages, use
   * {@link Connection::nextPage} and {@link Connection::previousPage}.
   *
   * @param options Options to include in the "List tokens" request.
   * @return A future that resolves to a page of token information.
   */
  CesiumAsync::Future<Response<TokenList>>
  tokens(const ListTokensOptions& options = {}) const;

  /**
   * @brief Gets details of the asset with the given ID.
   *
   * @param assetID The asset ID.
   * @return A future that resolves to the asset details.
   */
  CesiumAsync::Future<Response<Asset>> asset(int64_t assetID) const;

  /**
   * @brief Gets details of the token with the given ID.
   *
   * @param tokenID The token ID.
   * @return A future that resolves to the token details.
   */
  CesiumAsync::Future<Response<Token>> token(const std::string& tokenID) const;

  /**
   * @brief Gets the next page of results from the "List tokens" service.
   *
   * To get the first page, use {@link Connection::tokens}.
   *
   * @param currentPage The current page from which to get the next page.
   * @return A future that resolves to the next page of tokens, or to a
   * response with an {@link Response::errorCode} of "NoMorePages" if the
   * currentPage is the last one.
   */
  CesiumAsync::Future<Response<TokenList>>
  nextPage(const Response<TokenList>& currentPage) const;

  /**
   * @brief Gets the previous page of results from the "List tokens" service.
   *
   * To get the first page (or a particular page), use {@link Connection::tokens}.
   *
   * @param currentPage The current page from which to get the previous page.
   * @return A future that resolves to the previous page of tokens, or to a
   * response with an {@link Response::errorCode} of "NoMorePages" if the
   * currentPage is the first one.
   */
  CesiumAsync::Future<Response<TokenList>>
  previousPage(const Response<TokenList>& currentPage) const;

  /**
   * @brief Creates a new token.
   *
   * @param name The name of the new token.
   * @param scopes The scopes allowed by this token.
   * @param assetIds The assets that may be accessed by this token. If
   * `std::nullopt`, access to all assets is allowed.
   * @param allowedUrls The URLs from which this token can be accessed. If
   * `std::nullopt`, the token can be accessed from any URL.
   * @return The new token.
   */
  CesiumAsync::Future<Response<Token>> createToken(
      const std::string& name,
      const std::vector<std::string>& scopes,
      const std::optional<std::vector<int64_t>>& assetIds = std::nullopt,
      const std::optional<std::vector<std::string>>& allowedUrls =
          std::nullopt) const;

  /**
   * @brief Modifies a token.
   *
   * @param tokenID The ID of the token to modify.
   * @param newName The new name of the token.
   * @param newAssetIDs The assets that may be accessed by this token. If
   * `std::nullopt`, access to all assets is allowed.
   * @param newScopes The new OAuth scopes allowed by this token.
   * @param newAllowedUrls The new URLs from which this token can be accessed.
   * If `std::nullopt`, the token can be accessed from any URL.
   * @return A value-less response. If the response is successful, the token has
   * been modified.
   */
  CesiumAsync::Future<Response<NoValue>> modifyToken(
      const std::string& tokenID,
      const std::string& newName,
      const std::optional<std::vector<int64_t>>& newAssetIDs,
      const std::vector<std::string>& newScopes,
      const std::optional<std::vector<std::string>>& newAllowedUrls) const;

  /**
   * @brief Decodes a token ID from a token.
   *
   * @param token The token to decode.
   * @return The token ID, or std::nullopt if the token ID cannot be determined
   * from the token.
   */
  static std::optional<std::string> getIdFromToken(const std::string& token);

private:
  static CesiumAsync::Future<Connection> completeTokenExchange(
      const CesiumAsync::AsyncSystem& asyncSystem,
      const std::shared_ptr<CesiumAsync::IAssetAccessor>& pAssetAccessor,
      int64_t clientID,
      const std::string& ionApiUrl,
      const std::string& code,
      const std::string& redirectUrl,
      const std::string& codeVerifier);

  CesiumAsync::Future<Response<TokenList>> tokens(const std::string& url) const;

  CesiumAsync::AsyncSystem _asyncSystem;
  std::shared_ptr<CesiumAsync::IAssetAccessor> _pAssetAccessor;
  std::string _accessToken;
  std::string _apiUrl;
};
} // namespace CesiumIonClient
