# frozen_string_literal: true

require "final_destination"
require "mini_mime"
require "open-uri"

class FileHelper

  def self.log(log_level, message)
    Rails.logger.public_send(
      log_level,
      "#{RailsMultisite::ConnectionManagement.current_db}: #{message}"
    )
  end

  def self.is_supported_image?(filename)
    filename.match?(supported_images_regexp)
  end

  def self.is_inline_image?(filename)
    filename.match?(inline_images_regexp)
  end

  def self.is_supported_media?(filename)
    filename.match?(supported_media_regexp)
  end

  class FakeIO
    attr_accessor :status
  end

  def self.download(url,
                    max_file_size:,
                    tmp_file_name:,
                    follow_redirect: false,
                    read_timeout: 5,
                    skip_rate_limit: false,
                    verbose: false,
                    validate_uri: true,
                    retain_on_max_file_size_exceeded: false)

    url = "https:" + url if url.start_with?("//")
    raise Discourse::InvalidParameters.new(:url) unless url =~ /^https?:\/\//

    tmp = nil

    fd = FinalDestination.new(
      url,
      max_redirects: follow_redirect ? 5 : 0,
      skip_rate_limit: skip_rate_limit,
      verbose: verbose,
      validate_uri: validate_uri,
      timeout: read_timeout
    )

    fd.get do |response, chunk, uri|
      if tmp.nil?
        # error handling
        if uri.blank?
          if response.code.to_i >= 400
            # attempt error API compatibility
            io = FakeIO.new
            io.status = [response.code, ""]
            raise OpenURI::HTTPError.new("#{response.code} Error: #{response.body}", io)
          else
            log(:error, "FinalDestination did not work for: #{url}") if verbose
            throw :done
          end
        end

        if response.content_type.present?
          ext = MiniMime.lookup_by_content_type(response.content_type)&.extension
          ext = "jpg" if ext == "jpe"
          tmp_file_ext = "." + ext if ext.present?
        end

        tmp_file_ext ||= File.extname(uri.path)
        tmp = Tempfile.new([tmp_file_name, tmp_file_ext])
        tmp.binmode
      end

      tmp.write(chunk)

      if tmp.size > max_file_size
        unless retain_on_max_file_size_exceeded
          tmp.close
          tmp = nil
        end

        throw :done
      end
    end

    tmp&.rewind
    tmp
  end

  def self.optimize_image!(filename, allow_pngquant: false)
    image_optim(
      allow_pngquant: allow_pngquant,
      strip_image_metadata: SiteSetting.strip_image_metadata
    ).optimize_image!(filename)
  end

  def self.image_optim(allow_pngquant: false, strip_image_metadata: true)
    # memoization is critical, initializing an ImageOptim object is very expensive
    # sometimes up to 200ms searching for binaries and looking at versions
    memoize("image_optim", allow_pngquant, strip_image_metadata) do
      pngquant_options = false
      if allow_pngquant
        pngquant_options = { allow_lossy: true }
      end

      ImageOptim.new(
        # GLOBAL
        timeout: 15,
        skip_missing_workers: true,
        # PNG
        optipng: { level: 2, strip: strip_image_metadata },
        advpng: false,
        pngcrush: false,
        pngout: false,
        pngquant: pngquant_options,
        # JPG
        jpegoptim: { strip: strip_image_metadata ? "all" : "none" },
        jpegtran: false,
        jpegrecompress: false,
        # Skip looking for gifsicle, svgo binaries
        gifsicle: false,
        svgo: false
      )
    end
  end

  def self.memoize(*args)
    (@memoized ||= {})[args] ||= yield
  end

  def self.supported_gravatar_extensions
    @@supported_gravatar_images ||= Set.new(%w{jpg jpeg png gif})
  end

  def self.supported_images
    @@supported_images ||= Set.new %w{jpg jpeg png gif svg ico webp}
  end

  def self.inline_images
    # SVG cannot safely be shown as a document
    @@inline_images ||= supported_images - %w{svg}
  end

  def self.supported_audio
    @@supported_audio ||= Set.new %w{mp3 ogg wav m4a}
  end

  def self.supported_video
    @@supported_video ||= Set.new %w{mov mp4 webm ogv}
  end

  def self.supported_images_regexp
    @@supported_images_regexp ||= /\.(#{supported_images.to_a.join("|")})$/i
  end

  def self.inline_images_regexp
    @@inline_images_regexp ||= /\.(#{inline_images.to_a.join("|")})$/i
  end

  def self.supported_media_regexp
    @@supported_media_regexp ||=
      begin
        media = supported_images | supported_audio | supported_video
        /\.(#{media.to_a.join("|")})$/i
      end
  end
end
