require 'pdk'

module PDK
  module Template
    module Fetcher
      class Git < PDK::Template::Fetcher::AbstractFetcher
        # Whether the passed uri is fetchable by Git.
        # @see PDK::Template::Fetcher.instance
        # @return [Boolean]
        def self.fetchable?(uri, _options = {})
          PDK::Util::Git.repo?(uri.bare_uri)
        end

        # @see PDK::Template::Fetcher::AbstractTemplateFetcher.fetch!
        def fetch!
          return if fetched

          super

          # Default metadata for all git fetching methods
          @metadata['template-url'] = uri.metadata_format

          # We don't do a checkout of local-path repos. There are lots of edge
          # cases or user un-expectations.
          if PDK::Util::Git.work_tree?(uri.shell_path)
            PDK.logger.warn format("Repository '%{repo}' has a work-tree; skipping git reset.", repo: uri.shell_path)
            @path = uri.shell_path
            @temporary = false
            @metadata['template-ref'] = describe_path_and_ref(@path)
            return
          end

          # This is either a bare local repo or a remote. either way it needs cloning.
          # A "remote" can also be git repo on the local filsystem.
          # @todo When switching this over to using rugged, cache the cloned
          # template repo in `%AppData%` or `$XDG_CACHE_DIR` and update before
          # use.
          require 'pdk/util'
          require 'pdk/util/git'

          temp_dir = PDK::Util.make_tmpdir_name('pdk-templates')
          @temporary = true
          origin_repo = uri.bare_uri
          git_ref = uri.uri_fragment

          # Clone the repository
          clone_result = PDK::Util::Git.git('clone', origin_repo, temp_dir)
          unless clone_result[:exit_code].zero?
            PDK.logger.error clone_result[:stdout]
            PDK.logger.error clone_result[:stderr]
            raise PDK::CLI::FatalError, format("Unable to clone git repository at '%{repo}' into '%{dest}'.", repo: origin_repo, dest: temp_dir)
          end
          @path = PDK::Util.canonical_path(temp_dir)

          # Checkout the git reference
          if PDK::Util::Git.work_dir_clean?(temp_dir)
            Dir.chdir(temp_dir) do
              full_ref = PDK::Util::Git.ls_remote(temp_dir, git_ref)
              @metadata['template-ref'] = describe_path_and_ref(temp_dir, full_ref)
              reset_result = PDK::Util::Git.git('reset', '--hard', full_ref)
              return if reset_result[:exit_code].zero?

              PDK.logger.error reset_result[:stdout]
              PDK.logger.error reset_result[:stderr]
              raise PDK::CLI::FatalError, format("Unable to checkout '%{ref}' of git repository at '%{path}'.", ref: git_ref, path: temp_dir)
            end
          else
            PDK.logger.warn format("Uncommitted changes found when attempting to checkout '%{ref}' of git repository at '%{path}'; skipping git reset.", ref: git_ref, path: temp_dir)
            @metadata['template-ref'] = describe_path_and_ref(temp_dir)
          end
        end

        private

        # :nocov: This is a just a wrapper for another method
        def describe_path_and_ref(path, ref = nil)
          require 'pdk/util/git'
          PDK::Util::Git.describe(File.join(path, '.git'), ref)
        end
        # :nocov:
      end
    end
  end
end
