#' Get content item thumbnail
#'
#' Download the thumbnail for a content item on Connect to a file on your
#' computer.
#'
#' @param content A content item.
#' @param path Optional. A path to a file used to write the thumbnail image. If
#' no path is provided, a temporary file with the correct file extension is
#' created.
#'
#' @returns The path to the downloaded image file, if `content` has a thumbnail; otherwise `NA`.
#'
#' @examples
#' \dontrun{
#' client <- connect()
#' item <- content_item(client, "8f37d6e0-3395-4a2c-aa6a-d7f2fe1babd0")
#' thumbnail <- get_thumbnail(item)
#' }
#'
#' @family thumbnail functions
#' @family content functions
#' @include utils.R
#' @export
get_thumbnail <- function(content, path = NULL) {
  validate_R6_class(content, "Content")
  guid <- content$content$guid
  con <- content$connect

  # Connect 2024.09.0 introduced public endpoints for content thumbnails. We
  # prefer those, falling back to the unversioned endpoints if unavailable.
  # The "content/{guid}/__thumbnail__" URL is not under "__api__".
  res <- con$GET(
    url = con$server_url("content", guid, "__thumbnail__"),
    parser = NULL
  )
  if (httr::status_code(res) == 404) {
    res <- con$GET(
      unversioned_fallback_url("applications", guid, "image"),
      parser = NULL
    )
  }
  con$raise_error(res)

  if (httr::status_code(res) == 204) {
    return(NA_character_)
  }

  # Guess file extension
  if (is.null(path)) {
    path <- tempfile(
      pattern = glue::glue("content_image_{content$content$guid}_")
    )
  }
  ct <- httr::headers(res)$`content-type`
  if (ct %in% mime::mimemap) {
    exts <- names(mime::mimemap[mime::mimemap == ct])
    # Append the correct extension to the path if it does not match any of the
    # MIME type's valid file extensions.
    if (!any(sapply(exts, function(x) endsWith(path, x)))) {
      path <- paste(path, exts[1], sep = ".")
    }
  } else {
    warning(glue::glue(
      "Could not infer file extension from content type: {ct}."
    ))
  }

  writeBin(httr::content(res, as = "raw"), path)

  return(path)
}


#' Delete content item thumbnail
#'
#' Delete the thumbnail from a content item on Connect.
#'
#' @param content A content item.
#'
#' @returns The content item (invisibly).
#'
#' @examples
#' \dontrun{
#' client <- connect()
#' item <- content_item(client, "8f37d6e0-3395-4a2c-aa6a-d7f2fe1babd0")
#' thumbnail <- get_thumbnail(item)
#' }
#'
#' @family thumbnail functions
#' @family content functions
#' @export
delete_thumbnail <- function(content) {
  validate_R6_class(content, "Content")
  guid <- content$content$guid
  con <- content$connect

  # Connect 2024.09.0 introduced public endpoints for content thumbnails. We
  # prefer those, falling back to the unversioned endpoints if unavailable.
  res <- con$DELETE(
    v1_url("content", guid, "thumbnail"),
    parser = NULL
  )
  # API error code 17 indicates that the request was successful but the thumbnail does not exist.
  # In this case, we don't need to make another request.
  # https://docs.posit.co/connect/api/#overview--api-error-codes
  if (httr::status_code(res) == 404 && error_code(res) != 17) {
    res <- con$DELETE(
      unversioned_fallback_url("applications", guid, "image"),
      parser = NULL
    )
  }

  # API error code 17 indicates that the request was successful but the thumbnail does not exist.
  # We do not want to throw an error in this case.
  # https://docs.posit.co/connect/api/#overview--api-error-codes
  if (httr::status_code(res) == 404 && error_code(res) != 17) {
    con$raise_error(res)
  }

  invisible(content)
}

#' Check content item thumbnail
#'
#' Check whether a content item has a thumbnail.
#'
#' @param content A content item.
#'
#' @returns `TRUE` if the content item has a thumbnail, otherwise `FALSE`.
#'   Throws an error if you do not have permission to view the thumbnail.
#' @examples
#' \dontrun{
#' client <- connect()
#' item <- content_item(client, "8f37d6e0-3395-4a2c-aa6a-d7f2fe1babd0")
#' has_thumbnail(item)
#' }
#'
#' @family thumbnail functions
#' @family content functions
#' @export
has_thumbnail <- function(content) {
  validate_R6_class(content, "Content")
  guid <- content$content$guid
  con <- content$connect

  # Connect 2024.09.0 introduced public endpoints for content thumbnails. We
  # prefer those, falling back to the unversioned endpoints if unavailable.
  # The "content/{guid}/__thumbnail__" URL is not under "__api__".
  res <- con$GET(
    url = con$server_url("content", guid, "__thumbnail__"),
    parser = NULL
  )
  if (httr::status_code(res) == 404) {
    res <- con$GET(
      unversioned_fallback_url("applications", guid, "image"),
      parser = NULL
    )
  }
  con$raise_error(res)
  httr::status_code(res) != 204
}

#' Set content item thumbnail
#'
#' Set the thumbnail for a content item.
#'
#' @param content A content item.
#' @param path Either a path to a local file or a URL to an image available over
#' HTTP/HTTPS. If `path` is an HTTP or HTTPS URL, the image will first
#' be downloaded.
#'
#' @returns The content item (invisibly).
#'
#' @examples
#' \dontrun{
#' client <- connect()
#' item <- content_item(client, "8f37d6e0-3395-4a2c-aa6a-d7f2fe1babd0")
#' set_thumbnail(item, "resources/image.png")
#' }
#'
#' @family thumbnail functions
#' @family content functions
#' @export
set_thumbnail <- function(content, path) {
  validate_R6_class(content, "Content")

  valid_path <- NULL
  if (file.exists(path)) {
    valid_path <- path
  } else {
    parsed <- httr::parse_url(path)
    if (!is.null(parsed$scheme) && parsed$scheme %in% c("http", "https")) {
      valid_path <- tempfile(
        pattern = "image",
        fileext = paste0(".", tools::file_ext(parsed[["path"]]))
      )
      res <- httr::GET(path, httr::write_disk(valid_path))
      on.exit(unlink(valid_path))
      if (httr::http_error(res)) {
        stop(glue::glue(
          "Could not download image from {path}: {httr::http_status(res)$message}"
        ))
      }
    }
  }
  if (is.null(valid_path)) {
    stop(glue::glue("Could not locate image at path: {path}"))
  }

  guid <- content$content$guid
  con <- content$connect

  # Connect 2024.09.0 introduced public endpoints for content thumbnails. We
  # prefer those, falling back to the unversioned endpoints if unavailable.
  res <- con$PUT(
    path = v1_url("content", guid, "thumbnail"),
    body = httr::upload_file(valid_path),
    parser = NULL
  )
  if (httr::status_code(res) == 404) {
    res <- con$POST(
      path = unversioned_fallback_url("applications", guid, "image"),
      body = httr::upload_file(valid_path),
      parser = NULL
    )
  }
  con$raise_error(res)

  # return the input (in case it inherits more than just Content)
  invisible(content)
}
