require 'delegate'
require 'fileutils'
require 'bard/git'

module Bard
  class GithubPages < SimpleDelegator
    def deploy server
      @sha = Git.sha_of(Git.current_branch)
      @build_dir = "tmp/github-build-#{@sha}"
      @branch = "gh-pages"
      @domain = server.ping.first

      puts "Starting deployment to GitHub Pages..."

      build_site
      tree_sha = create_tree_from_build
      new_commit = create_commit(tree_sha)
      commit_and_push new_commit
    end

    private

    def build_site
      FileUtils.rm_rf "tmp/github-build-".sub(@sha, "*")
      run! <<~BASH
        set -e
        RAILS_ENV=production bundle exec rails s -p 3000 -d --pid tmp/pids/server.pid
        bundle exec rake assets:clean assets:precompile

        # Create the output directory and enter it
        BUILD=#{@build_dir}
        rm -rf $BUILD
        mkdir -p $BUILD
        cp -R public/assets $BUILD/
        cd $BUILD

        # wait until server responds
        echo waiting...
        curl --retry 5 --retry-delay 2 http://localhost:3000

        echo copying...
        # Mirror the site to the build folder, ignoring links with query params
        wget --reject-regex "(.*)\\?(.*)" -FEmnH http://localhost:3000/
        echo #{@domain} > CNAME
      BASH
    ensure # cleanup
      run! <<~BASH
        cat tmp/pids/server.pid | xargs -I {} kill {}
        rm -rf public/assets
      BASH
    end

    def create_tree_from_build
      # Create temporary index
      git_index_file = ".git/tmp-index"
      git = "GIT_INDEX_FILE=#{git_index_file} git"
      run! "#{git} read-tree --empty"

      # Add build files to temporary index
      Dir.chdir(@build_dir) do
        Dir.glob('**/*', File::FNM_DOTMATCH).each do |file|
          next if file == '.' || file == '..'
          if File.file?(file)
            run! "#{git} update-index --add --cacheinfo 100644 $(#{git} hash-object -w #{file}) #{file}"
          end
        end
      end

      # Create tree object from index
      tree_sha = `#{git} write-tree`.chomp

      # Clean up temporary index
      FileUtils.rm_f(git_index_file)

      tree_sha
    end

    def create_commit tree_sha
      # Get parent commit if branch exists
      parent = get_parent_commit

      # Create commit object
      message = "'Deploying to #{@branch} from @ #{@sha} 🚀'"
      args = ['commit-tree', tree_sha]
      args += ['-p', parent] if parent
      args += ['-m', message]

      commit_sha = `git #{args.join(' ')}`.chomp

      commit_sha
    end

    def get_parent_commit
      sha = Git.sha_of("#{@branch}^{commit}")
      return sha if $?.success?
      nil # Branch doesn't exist yet
    end

    def commit_and_push commit_sha
      if branch_exists?
        run! "git update-ref refs/heads/#{@branch} #{commit_sha}"
      else
        run! "git branch #{@branch} #{commit_sha}"
      end
      run! "git push -f origin #{@branch}:refs/heads/#{@branch}"
    end

    def branch_exists?
      system("git show-ref --verify --quiet refs/heads/#{@branch}")
    end
  end
end