# frozen_string_literal: true

class UserAvatarsController < ApplicationController
  skip_before_action :preload_json,
                     :redirect_to_login_if_required,
                     :redirect_to_profile_if_required,
                     :check_xhr,
                     :verify_authenticity_token,
                     only: %i[show show_letter show_proxy_letter]

  before_action :apply_cdn_headers, only: %i[show show_letter show_proxy_letter]

  def refresh_gravatar
    user = User.find_by(username_lower: params[:username].downcase)
    guardian.ensure_can_edit!(user)

    if user
      hijack do
        user.create_user_avatar(user_id: user.id) unless user.user_avatar
        user.user_avatar.update_gravatar!

        gravatar =
          if user.user_avatar.gravatar_upload_id
            {
              gravatar_upload_id: user.user_avatar.gravatar_upload_id,
              gravatar_avatar_template:
                User.avatar_template(user.username, user.user_avatar.gravatar_upload_id),
            }
          else
            { gravatar_upload_id: nil, gravatar_avatar_template: nil }
          end

        render json: gravatar
      end
    else
      raise Discourse::NotFound
    end
  end

  def show_proxy_letter
    is_asset_path

    if SiteSetting.external_system_avatars_url !~ %r{\A/letter_avatar_proxy}
      raise Discourse::NotFound
    end

    params.require(:letter)
    params.require(:color)
    params.require(:version)
    params.require(:size)

    hijack do
      begin
        proxy_avatar(
          "https://avatars.discourse-cdn.com/#{params[:version]}/letter/#{params[:letter]}/#{params[:color]}/#{params[:size]}.png",
          Time.new(1990, 01, 01),
        )
      rescue OpenURI::HTTPError
        render_blank
      end
    end
  end

  def show_letter
    is_asset_path

    params.require(:username)
    params.require(:version)
    params.require(:size)

    no_cookies

    return render_blank if params[:version] != LetterAvatar.version

    hijack do
      image = LetterAvatar.generate(params[:username].to_s, params[:size].to_i)

      response.headers["Last-Modified"] = File.ctime(image).httpdate
      response.headers["Content-Length"] = File.size(image).to_s
      immutable_for(1.year)
      send_file image, disposition: nil
    end
  end

  def show
    is_asset_path

    # we need multisite support to keep a single origin pull for CDNs
    RailsMultisite::ConnectionManagement.with_hostname(params[:hostname]) do
      hijack { show_in_site(params[:hostname]) }
    end
  end

  protected

  def show_in_site(hostname)
    username = params[:username].to_s
    return render_blank unless user = User.find_by(username_lower: username.downcase)

    upload_id, version = params[:version].split("_")

    version = (version || OptimizedImage::VERSION).to_i

    # old versions simply get new avatar
    return render_blank if version > OptimizedImage::VERSION

    upload_id = upload_id.to_i
    return render_blank if upload_id <= 0

    size = params[:size].to_i
    return render_blank if size < 8 || size > 1000

    if !Discourse.avatar_sizes.include?(size) && Discourse.store.external?
      closest = Discourse.avatar_sizes.to_a.min { |a, b| (size - a).abs <=> (size - b).abs }
      avatar_url =
        UserAvatar.local_avatar_url(
          hostname,
          user.encoded_username(lower: true),
          upload_id,
          closest,
        )
      return redirect_to cdn_path(avatar_url), allow_other_host: true
    end

    upload = Upload.find_by(id: upload_id) if user&.user_avatar&.contains_upload?(upload_id)
    upload ||= user.uploaded_avatar if user.uploaded_avatar_id == upload_id

    if user.uploaded_avatar && !upload
      avatar_url =
        UserAvatar.local_avatar_url(
          hostname,
          user.encoded_username(lower: true),
          user.uploaded_avatar_id,
          size,
        )
      return redirect_to cdn_path(avatar_url), allow_other_host: true
    elsif upload && optimized = get_optimized_image(upload, size)
      if optimized.local?
        optimized_path = Discourse.store.path_for(optimized)
        image = optimized_path if File.exist?(optimized_path)
      elsif GlobalSetting.redirect_avatar_requests
        return redirect_s3_avatar(Discourse.store.cdn_url(optimized.url))
      else
        return proxy_avatar(Discourse.store.cdn_url(optimized.url), upload.created_at)
      end
    end

    if image
      response.headers["Last-Modified"] = File.ctime(image).httpdate
      response.headers["Content-Length"] = File.size(image).to_s
      immutable_for 1.year
      send_file image, disposition: nil
    else
      render_blank
    end
  rescue OpenURI::HTTPError
    render_blank
  end

  # Allow plugins to overwrite max file size value
  def max_file_size
    1.megabyte
  end

  PROXY_PATH = Rails.root + "tmp/avatar_proxy"
  def proxy_avatar(url, last_modified)
    url = (SiteSetting.force_https ? "https:" : "http:") + url if url[0..1] == "//"

    sha = Digest::SHA1.hexdigest(url)
    filename = "#{sha}#{File.extname(url)}"
    path = "#{PROXY_PATH}/#{filename}"

    unless File.exist? path
      FileUtils.mkdir_p PROXY_PATH
      tmp =
        FileHelper.download(
          url,
          max_file_size: max_file_size,
          tmp_file_name: filename,
          follow_redirect: true,
          read_timeout: 10,
        )

      return render_blank if tmp.nil?

      FileUtils.mv tmp.path, path
    end

    response.headers["Last-Modified"] = last_modified.httpdate
    response.headers["Content-Length"] = File.size(path).to_s
    immutable_for(1.year)
    send_file path, disposition: nil
  end

  def redirect_s3_avatar(url)
    response.cache_control[:max_age] = 1.hour.to_i
    response.cache_control[:public] = true
    response.cache_control[:extras] = ["immutable", "stale-while-revalidate=#{1.day.to_i}"]
    redirect_to url, allow_other_host: true
  end

  # this protects us from a DoS
  def render_blank
    path = Rails.root + "public/images/avatar.png"
    expires_in 10.minutes, public: true
    response.headers["Last-Modified"] = Time.new(1990, 01, 01).httpdate
    response.headers["Content-Length"] = File.size(path).to_s
    send_file path, disposition: nil
  end

  protected

  # consider removal of hacks some time in 2019

  def get_optimized_image(upload, size)
    return if !upload
    return upload if upload.extension == "svg"

    upload.get_optimized_image(size, size)
    # TODO decide if we want to detach here
  end
end