module MultiRepo::Service
  class Artifactory
    def self.api_token
      @api_token ||= ENV.fetch("ARTIFACTORY_API_TOKEN")
    end

    def self.api_token=(token)
      @api_token = token
    end

    def self.api_endpoint
      @api_endpoint ||= ENV.fetch("ARTIFACTORY_API_ENDPOINT")
    end

    def self.api_endpoint=(endpoint)
      @api_endpoint = endpoint
    end

    def self.auth_header
      {"X-JFrog-Art-Api" => api_token}
    end

    attr_accessor :dry_run, :cache

    def initialize(dry_run: false, cache: true)
      require "rest-client"
      require "json"

      @dry_run = dry_run
      @cache   = cache
    end

    def clear_cache
      FileUtils.rm_f(Dir.glob("/tmp/artifactory-*"))
    end

    # https://www.jfrog.com/confluence/display/JFROG/RPM+Repositories
    def get(path, **kwargs)
      path = path.to_s
      request(:get, path, **kwargs)
    end

    def list(folder, cache: @cache, **kwargs)
      folder = folder.to_s
      cache_file = "/tmp/artifactory-#{folder.tr("/", "_")}-#{Date.today}.txt"
      if cache && File.exist?(cache_file)
        File.readlines(cache_file, :chomp => true)
      else
        data = raw_list(folder, cache: cache, **kwargs)
        uri  = data["uri"]

        data["files"].map { |d| File.join(uri, d["uri"]) }.tap do |d|
          File.write(cache_file, d.join("\n")) if cache
        end
      end
    end

    def raw_list(folder, cache: @cache, **kwargs)
      folder = folder.to_s
      cache_file = "/tmp/artifactory-#{folder.tr("/", "_")}-raw-#{Date.today}.json"
      if cache && File.exist?(cache_file)
        JSON.parse(File.read(cache_file))
      else
        get("/api/storage/#{folder}?list&deep=1", **kwargs).tap do |d|
          File.write(cache_file, JSON.pretty_generate(d)) if cache
        end
      end
    end

    def delete(file, **kwargs)
      file = file.to_s
      request(:delete, strip_api_prefix(file), **kwargs)
    rescue RestClient::NotFound => err
      # Ignore deletes on a 404 because it's already deleted
      raise unless err.http_code == 404
    end

    def move(file, to, **kwargs)
      file = file.to_s
      to = to.to_s
      request(:post, File.join("/api/move", "#{strip_api_prefix(file)}?to=/#{strip_api_prefix(to)}"), **kwargs)
    end

    def copy(file, to, **kwargs)
      file = file.to_s
      to = to.to_s
      request(:post, File.join("/api/copy", "#{strip_api_prefix(file)}?to=/#{strip_api_prefix(to)}"), **kwargs)
    end

    private

    def request(verb, path, body: nil, headers: nil, verbose: true)
      headers ||= self.class.auth_header.merge(
        "Accept"       => "application/json",
        "Content-Type" => "application/json"
      )
      path = File.join(self.class.api_endpoint, path)

      puts "+ #{verb.to_s.upcase} #{path}".light_black if verbose
      if dry_run && %i[delete put post patch].include?(verb)
        puts "+ dry_run: #{verb.to_s.upcase} #{path}".light_black if verbose
        {}
      else
        response =
          if %i[put post patch].include?(verb)
            RestClient.send(verb, path, body, headers)
          else
            RestClient.send(verb, path, headers)
          end
        response.empty? ? {} : JSON.parse(response)
      end
    end

    def api_file_prefix
      File.join(self.class.api_endpoint, "api/storage")
    end

    def strip_api_prefix(path)
      path.start_with?(api_file_prefix) ? path.sub(api_file_prefix, "") : path
    end
  end
end