# Code flow ---------------------------------------------------------------

#' Authorization
#'
#' Authorize your R session to connect to an ArcGIS Portal. See details.
#'
#' @details
#'
#' ArcGIS Online and Enterprise Portals utilize OAuth2 authorization via their REST APIs.
#'
#' - `auth_code()` is the recommend OAuth2 workflow for interactive sessions
#' - `auth_client()` is the recommended OAuth2 workflow for non-interactive sessions
#' - `auth_user()` uses legacy username and password authorization using the `generateToken` endpoint. It is only recommended for legacy systems that do not implement OAuth2.
#' - `auth_binding()` fetches a token from the active portal set by `arcgisbinding`. Uses `arcgisbinding::arc.check_portal()` to extract the authorization token. Recommended if using arcgisbinding.
#'
#' @param client an OAuth 2.0 developer application client ID. By default uses the
#'  environment variable `ARCGIS_CLIENT`.
#' @param secret an OAuth 2.0 developer application secret. By default uses the environment
#'   variable `ARCGIS_SECRET`.
#' @param host default `"https://www.arcgis.com"`. The host of your ArcGIS Portal.
#' @param expiration the duration of the token in minutes.
#' @inheritParams cli::cli_abort
#'
#' @rdname auth
#' @export
#' @examples
#' \dontrun{
#' auth_code()
#' auth_client()
#' auth_user()
#' auth_key()
#' auth_binding()
#' }
#' @returns an `httr2_token`
auth_code <- function(
  client = Sys.getenv("ARCGIS_CLIENT"),
  host = arc_host()
) {
  check_string(client, allow_empty = FALSE)
  check_string(host, allow_empty = FALSE)

  token_url <- paste(host, "sharing", "rest", "oauth2", "token", sep = "/")

  auth_url <- paste(host, "sharing", "rest", "oauth2", "authorize", sep = "/")

  client <- httr2::oauth_client(
    id = client,
    token_url = token_url,
    name = "arcgisutils"
  )

  auth_url <- httr2::oauth_flow_auth_code_url(
    client,
    auth_url = auth_url,
    redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
  )

  # open browser
  utils::browseURL(auth_url)

  {
    code <- readline(prompt = "Enter code: ")
  }

  # use the internal function from httr2 to complete the token
  # transfer
  fx <- utils::getFromNamespace("oauth_client_get_token", "httr2")

  res <- fx(
    client,
    grant_type = "authorization_code",
    code = code,
    redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
  )

  # add host to the token
  res[["arcgis_host"]] <- host
  res
}
# Client auth -------------------------------------------------------------
#' @export
#' @rdname auth
auth_client <- function(
  client = Sys.getenv("ARCGIS_CLIENT"),
  secret = Sys.getenv("ARCGIS_SECRET"),
  host = arc_host(),
  expiration = 120
) {
  check_string(client, allow_empty = FALSE)
  check_string(secret, allow_empty = FALSE)
  check_string(host, allow_empty = FALSE)
  check_number_whole(expiration, min = 5, max = 20160)

  # https://developers.arcgis.com/documentation/mapping-apis-and-services/security/application-credentials/
  token_url <- paste(
    host,
    "sharing",
    "rest",
    "oauth2",
    "token",
    sep = "/"
  )

  cln <- httr2::oauth_client(
    client,
    token_url,
    secret,
    name = "arcgisutils"
  )

  res <- httr2::oauth_flow_client_credentials(
    cln,
    token_params = list(expiration = expiration)
  )

  # add host to the token
  res[["arcgis_host"]] <- host
  res
}


# arcgisbinding -----------------------------------------------------------

#' @export
#' @rdname auth
auth_binding <- function() {
  rlang::check_installed("arcgisbinding")
  tkn <- utils::getFromNamespace("arc.check_portal", "arcgisbinding")()
  res <- httr2::oauth_token(tkn[["token"]])
  # add host to the token
  res[["arcgis_host"]] <- tkn[["url"]]
  # add the username to the token as well
  res[["username"]] <- tkn[["user"]]
  res
}


# legacy auth -------------------------------------------------------------

# uses request ip does not support other forms
# use oauth as the recommended approach
#' @export
#' @rdname auth
#' @param username default `Sys.getenv("ARCGIS_USER")`.
#'  Your username to login. **Do not** hard code this value.
#' @param password default `Sys.getenv("ARCGIS_PASSWORD")`.
#'   Your password to login. **Do not** hard code this value.
auth_user <- function(
  username = Sys.getenv("ARCGIS_USER"),
  password = Sys.getenv("ARCGIS_PASSWORD"),
  host = arc_host(),
  expiration = 60
) {
  check_string(username, allow_empty = FALSE)
  check_string(password, allow_empty = FALSE)
  check_string(host, allow_empty = FALSE)
  check_number_whole(expiration, min = 5, max = 20160)

  if (expiration > 21600) {
    cli::cli_abort("{.arg expiration} cannot be more than 15 days (21600)")
  }

  burl <- paste0(host, "/sharing/rest/generateToken")
  b_req <- httr2::request(burl)

  req <- httr2::req_body_form(
    b_req,
    username = username,
    password = password,
    client = "referer",
    referer = host,
    expiration = expiration,
    f = "json"
  )

  # set the user agent
  req <- arc_agent(req)

  # perform request
  resp <- httr2::req_perform(req)

  # fetch token
  token_raw <- httr2::resp_body_string(resp)

  # parse the response
  token <- RcppSimdJson::fparse(token_raw)

  # detect the errors
  detect_errors(token)

  # return the token
  httr2::oauth_token(
    token[["token"]],
    expires_in = expiration,
    username = username,
    arcgis_host = host,
  )
}


# API Key ----------------------------------------------------------------

#' @param api_key default `Sys.getenv("ARCGIS_API_KEY")`. A character scalar of an ArcGIS Developer API key.
#' @export
#' @rdname auth
auth_key <- function(
  api_key = Sys.getenv("ARCGIS_API_KEY"),
  host = arc_host()
) {
  check_string(
    api_key,
    allow_empty = FALSE,
    allow_na = FALSE,
    allow_null = FALSE
  )

  httr2::oauth_token(
    api_key,
    arcgis_host = host
  )
}

# refreshment mmm tasty --------------------------------------------------------

#' @param token an `httr2_token` as created by `auth_code()` or similar
#' @rdname auth
#' @export
refresh_token <- function(
  token,
  client = Sys.getenv("ARCGIS_CLIENT"),
  host = arc_host()
) {
  # validate the object is a token
  obj_check_token(token)
  check_string(client, allow_empty = FALSE)

  # extract host from token
  host <- token[["arcgis_host"]]

  token_url <- paste(
    host,
    "sharing",
    "rest",
    "oauth2",
    "token",
    sep = "/"
  )

  cln <- httr2::oauth_client(client, token_url, name = "arcgisutils")

  # get the current time
  cur_time <- as.numeric(Sys.time())

  if (is.null(token[["refresh_token"]])) {
    cli::cli_abort(
      "{.arg token} has expired and no {.field refresh_token} available"
    )
  } else if ((cur_time + token[["refresh_token_expires_in"]]) < cur_time) {
    # if it has a refresh check to see if refresh hasn't expired
    cli::cli_abort("Token's {.field refresh_token} has expired.")
  }

  # should be able to refresh, go ahead.
  res <- httr2::oauth_flow_refresh(cln, token[["refresh_token"]])

  # set the host back into the token
  # Note we don't do this for username because only `auth_code()` provides
  # a refresh token
  res[["arcgis_host"]] <- host
  res
}

#' @rdname auth
#' @export
#' @param refresh_threshold default `10`. If token expiry is within this
#'  threshold (in seconds) the token will be refreshed only if a
#'  `refresh_token` is available. Token refreshing is only possible with
#'  `auth_code()` flow.
validate_or_refresh_token <- function(
  token,
  client = Sys.getenv("ARCGIS_CLIENT"),
  host = arc_host(),
  refresh_threshold = 10,
  call = rlang::caller_env()
) {
  # validate the object is a token
  obj_check_token(token, call = call)
  check_string(client, allow_empty = TRUE)
  check_string(host, allow_empty = FALSE)
  check_number_whole(refresh_threshold, min = 0, max = 3600)

  cur_time <- as.numeric(Sys.time())

  # if there isn't a client token value then we can return the token
  if (!nzchar(client)) {
    return(token)
  }

  # provide an error if there is an expired token
  if (token[["expires_at"]] <= cur_time) {
    cli::cli_abort("Provided token has expired", call = call)
  }

  # check if token is expired or expires within threshold
  # the idea being if the token is going to expire soon, refresh it

  if (
    token[["expires_at"]] - refresh_threshold <= cur_time &&
      !is.null(token[["refresh_token"]])
  ) {
    # if it is refresh the token
    token <- refresh_token(client, host, token)
  } else {
    # if not return token
    token
  }
}

#' Determines Portal Host
#'
#' Returns a scalar character indicating the host to make requests to.
#'
#' By default, the host is ArcGIS Online <`https://www.arcgis.com`>. If the
#' environment variable `ARCGIS_HOST` is set, it will be returned.
#'
#' @export
#' @examples
#' arc_host()
#' @returns
#' A scalar character, `"https://www.arcgis.com"` by default.
arc_host <- function() {
  host <- Sys.getenv("ARCGIS_HOST")
  check_string(host, allow_empty = TRUE)
  if (host == "") {
    host <- "https://www.arcgis.com"
  }

  host
}
