module ManageIQ::CrossRepo
  class Repository
    attr_reader :identifier, :server
    attr_reader :org, :repo, :ref, :sha, :url, :path

    # ManageIQ::CrossRepo::Repository
    #
    # @param identifier [String] the short representation of a repository relative to a server, format [org/]repo[@ref]
    # @param server [String] The git repo server hosting this repository, default: https://github.com
    # @example
    #   Repostory.new("ManageIQ/manageiq@master", server: "https://github.com")
    def initialize(identifier, server: "https://github.com")
      @identifier = identifier
      @server     = server
      @org, @repo, @ref, @sha, @url, @path = parse_identifier
    end

    def core?
      repo.casecmp("manageiq") == 0
    end

    def ==(other)
      repo == other.repo && sha == other.sha
    end

    def ensure_clone
      return if path.exist?

      require "mixlib/archive"
      require "open-uri"
      require "tmpdir"
      require "zlib"

      retries ||= 0

      puts "Fetching #{tarball_url}#{retry_count(retries)}"

      Dir.mktmpdir do |dir|
        Mixlib::Archive.new(open_tarball_url(tarball_url)).extract(dir)

        content_dir = Pathname.new(dir).children.detect(&:directory?)
        FileUtils.mkdir_p(path.dirname)
        FileUtils.mv(content_dir, path)
      end
    rescue => e
      retries += 1
      raise if retries > 3

      sleep 1
      retry
    end

    private

    def parse_identifier
      if local_identifier?
        parse_local_identifier
      elsif url_identifier?
        parse_url_identifier
      else
        parse_repo_identifier
      end
    end

    def local_identifier?
      ["/", "~", "."].include?(identifier[0])
    end

    def parse_local_identifier
      path = Pathname.new(identifier).expand_path
      raise ArgumentError, "Path #{path} does not exist" unless path.exist?

      org  = nil
      repo = path.basename.to_s
      ref  = Dir.chdir(path) { `git rev-parse HEAD`.chomp }
      sha  = ref
      url  = nil

      return org, repo, ref, sha, url, path
    end

    def url_identifier?
      identifier.start_with?(server)
    end

    def parse_url_identifier
      url_path = URI.parse(identifier).path
      _, org, repo, type, commit_branch_or_pr = url_path.split("/")

      case type
      when "tree"
        branch = commit_branch_or_pr
      when "commit"
        commit = commit_branch_or_pr
      when "pull"
        pr = commit_branch_or_pr
      else
        branch = "master"
      end

      url = File.join(server, org, repo)
      sha =
        if pr
          git_pr_to_sha(url, pr)
        elsif branch
          git_branch_to_sha(url, branch)
        else
          commit
        end

      raise ArgumentError, "#{identifier} does not exist" if sha.nil?

      ref  = nil
      path = REPOS_DIR.join("#{org}/#{repo}@#{sha}")

      return org, repo, ref, sha, url, path
    end

    def parse_repo_identifier
      if identifier.include?("#")
        name, pr = identifier.split("#")
      else
        name, ref_or_branch = identifier.split("@")
        if ref_or_branch.nil?
          branch = "master"
        elsif ref_or_branch.match?(/^\h+$/)
          ref = ref_or_branch
        else
          branch = ref_or_branch
        end
      end

      org, repo = name.split("/")
      repo, org = org, "ManageIQ" if repo.nil?

      url = File.join(server, org, repo)

      sha =
        if pr
          git_pr_to_sha(url, pr)
        elsif branch
          git_branch_to_sha(url, branch)
        else
          ref
        end

      raise ArgumentError, "#{identifier} does not exist" if sha.nil?

      path = REPOS_DIR.join("#{org}/#{repo}@#{sha}")

      return org, repo, ref, sha, url, path
    end

    def tarball_url
      url && File.join(url, "tarball", sha)
    end

    def open_tarball_url(url)
      archive = URI.open(tarball_url, "rb")

      if archive.kind_of?(StringIO)
        archive = Tempfile.new('cross_repo').tap do |f|
          f.write(archive.string)
          f.fsync
        end.path
      end

      archive
    end

    def git_branch_to_sha(url, branch)
      `git ls-remote #{url} #{branch}`.split("\t").first
    end

    def git_pr_to_sha(url, pr)
      git_branch_to_sha(url, "refs/pull/#{pr}/merge") || git_branch_to_sha(url, "refs/pull/#{pr}/head")
    end

    def retry_count(num)
      return if num == 0

      "  (retry #{num}/3)"
    end
  end
end