#!/usr/bin/env ruby

# A sample pre-deploy hook
#
# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds.
#
# Fails unless the combined status is "success"
#
# These environment variables are available:
# KAMAL_RECORDED_AT
# KAMAL_PERFORMER
# KAMAL_VERSION
# KAMAL_HOSTS
# KAMAL_COMMAND
# KAMAL_SUBCOMMAND
# KAMAL_ROLE (if set)
# KAMAL_DESTINATION (if set)

# Only check the build status for production deployments
if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production"
  exit 0
end

require "bundler/inline"

# true = install gems so this is fast on repeat invocations
gemfile(true, quiet: true) do
  source "https://rubygems.org"

  gem "octokit"
  gem "faraday-retry"
end

MAX_ATTEMPTS = 72
ATTEMPTS_GAP = 10

def exit_with_error(message)
  $stderr.puts message
  exit 1
end

class GithubStatusChecks
  attr_reader :remote_url, :git_sha, :github_client, :combined_status

  def initialize
    @remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/")
    @git_sha = `git rev-parse HEAD`.strip
    @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
    refresh!
  end

  def refresh!
    @combined_status = github_client.combined_status(remote_url, git_sha)
  end

  def state
    combined_status[:state]
  end

  def first_status_url
    first_status = combined_status[:statuses].find { |status| status[:state] == state }
    first_status && first_status[:target_url]
  end

  def complete_count
    combined_status[:statuses].count { |status| status[:state] != "pending"}
  end

  def total_count
    combined_status[:statuses].count
  end

  def current_status
    if total_count > 0
      "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..."
    else
      "Build not started..."
    end
  end
end


$stdout.sync = true

puts "Checking build status..."
attempts = 0
checks = GithubStatusChecks.new

begin
  loop do
    case checks.state
    when "success"
      puts "Checks passed, see #{checks.first_status_url}"
      exit 0
    when "failure"
      exit_with_error "Checks failed, see #{checks.first_status_url}"
    when "pending"
      attempts += 1
    end

    exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS

    puts checks.current_status
    sleep(ATTEMPTS_GAP)
    checks.refresh!
  end
rescue Octokit::NotFound
  exit_with_error "Build status could not be found"
end
