namespace :committed do

task :check_prerequisites do
  # Checks all the settings to make sure they are OK - mostly just checks type
  { committed_user: 'user', committed_repo: 'repository' }.each do |variable, name|
    if fetch(variable).nil? || !fetch(variable).is_a?(String)
      raise TypeError, t('committed.error.prerequisites.nil', variable: variable, name: name)
    end
    if fetch(variable).empty?
      raise ArgumentError, t('committed.error.prerequisites.empty', variable: variable, name: name)
    end
  end

  unless fetch(:committed_github_config).is_a?(Hash)
    raise TypeError, t('committed.error.prerequisites.hash', variable: 'committed_github_config')
  end
end

task :check_report_prerequisites do
  # Checks all the settings to make sure they are OK - mostly just checks type
  unless fetch(:committed_revision_line).is_a?(String)
    raise TypeError, t('committed.error.prerequisites.string', variable: 'committed_revision_line')
  end

  unless fetch(:committed_revision_limit).is_a?(Integer)
    raise TypeError, t('committed.error.prerequisites.integer', variable: 'committed_revision_limit')
  end

  unless fetch(:committed_commit_buffer).is_a?(Integer)
    raise TypeError, t('committed.error.prerequisites.integer', variable: 'committed_commit_buffer')
  end

  if fetch(:committed_output_path).is_a?(String)
    raise TypeError, t('committed.error.deprecated', deprecated: 'committed_output_path', replacement: 'committed_output_text_path')
  end

  unless fetch(:committed_output_text_path).is_a?(String) || fetch(:committed_output_text_path).nil?
    raise TypeError, t('committed.error.prerequisites.string_or_nil', variable: 'committed_output_text_path')
  end

  unless fetch(:committed_output_html_path).is_a?(String) || fetch(:committed_output_html_path).nil?
    raise TypeError, t('committed.error.prerequisites.string_or_nil', variable: 'committed_output_html_path')
  end

  unless fetch(:committed_issue_match).is_a?(String) || fetch(:committed_issue_match).is_a?(Regexp) || fetch(:committed_issue_match).nil?
    raise TypeError, t('committed.error.prerequisites.string_or_regexp_or_nil', variable: 'committed_issue_match')
  end

  unless fetch(:committed_issue_url).is_a?(String) || fetch(:committed_issue_url).nil?
    raise TypeError, t('committed.error.prerequisites.string_or_nil', variable: 'committed_issue_url')
  end
end

# task :register_deployment_pending do
#   invoke 'committed:check_prerequisites'

#   github = ::Capistrano::Committed::GithubApi.new(fetch(:committed_github_config))
#   deployment = github.register_deployment(fetch(:committed_user),
#                                           fetch(:committed_repo),
#                                           fetch(:stage).to_s,
#                                           fetch(:branch).to_s)

#   return if deployment.nil?

#   github.register_status(fetch(:committed_user),
#                          fetch(:committed_repo),
#                          deployment[:id],
#                          'pending')

#   set :committed_deployment_id, deployment[:id]
# end

# task :register_deployment_success do
#   invoke 'committed:check_prerequisites'

#   id = fetch(:committed_deployment_id)
#   return if id.nil?
# end

# task :register_deployment_failure do
#   invoke 'committed:check_prerequisites'

#   id = fetch(:committed_deployment_id)
#   return if id.nil?
# end

desc 'Generetes a report of commits and pull requests on the current stage'
task :generate do
  invoke 'committed:check_prerequisites'
  invoke 'committed:check_report_prerequisites'

  ::Capistrano::Committed.import_settings(
    branch:            fetch(:branch),
    user:              fetch(:committed_user),
    repo:              fetch(:committed_repo),
    revision_line:     fetch(:committed_revision_line),
    github_config:     fetch(:committed_github_config),
    revision_limit:    fetch(:committed_revision_limit),
    commit_buffer:     fetch(:committed_commit_buffer),
    output_text_path:  fetch(:committed_output_text_path),
    output_html_path:  fetch(:committed_output_html_path),
    issue_match:       fetch(:committed_issue_match),
    issue_postprocess: fetch(:committed_issue_postprocess),
    issue_url:         fetch(:committed_issue_url),
    deployments:       fetch(:committed_deployments),
    deployment_id:     fetch(:committed_deployment_id)
  )

  # Only do this on the primary web server
  on primary :web do
    # Get the Capistrano revision log
    lines = capture(:cat, revision_log).split("\n").reverse

    # Build the revisions hash
    revisions = ::Capistrano::Committed.get_revisions_from_lines(lines)

    # No revisions, no log
    if revisions.empty?
      error t('committed.error.runtime.revisions_empty',
              branch: fetch(:branch).to_s,
              stage: fetch(:stage).to_s)
    end

    # Initialize the GitHub API client
    github = ::Capistrano::Committed::GithubApi.new(fetch(:committed_github_config))

    # Get the actual date of the commit referenced to by the revision
    revisions = ::Capistrano::Committed.add_dates_to_revisions(revisions, github)

    # Get the earliest revision date
    earliest_date = ::Capistrano::Committed.get_earliest_date_from_revisions(revisions)

    # No commit data on revisions, no log
    if earliest_date.nil?
      error t('committed.error.runtime.revision_commit_missing',
              branch: fetch(:branch).to_s,
              stage: fetch(:stage).to_s)
    end

    # Go back an extra N days
    earliest_date = ::Capistrano::Committed.add_buffer_to_time(earliest_date)
    revisions[:previous][:date] = earliest_date

    # Get all the commits on this branch
    commits = github.get_commits_since(fetch(:committed_user),
                                       fetch(:committed_repo),
                                       earliest_date,
                                       fetch(:branch).to_s)

    # No commits, no log
    if commits.empty?
      error t('committed.error.runtime.commits_empty',
              branch: fetch(:branch).to_s,
              stage: fetch(:stage).to_s,
              time: earliest_date)
    end

    # Map commits to a hash keyed by sha
    commits = Hash[commits.map { |commit| [commit[:sha], commit] }]

    # Get all pull requests listed in the commits
    revision_index = 0
    commits.each do |sha, commit|
      # Match to GitHub generated commit message, or don't
      message = /^Merge pull request \#([0-9]+)/
      matches = message.match(commit[:commit][:message])
      next unless matches && matches[1]

      # Get the pull request from GitHub
      pull_request = github.get_pull_request(fetch(:committed_user),
                                             fetch(:committed_repo),
                                             matches[1].to_i)

      # Get the previous revisions commit time and the merge time of the pull
      # request
      previous_revision = revisions[revisions.keys[revision_index + 1]]
      previous_revision_date = Time.parse(previous_revision[:date])
      merged_at = Time.parse(pull_request[:info][:merged_at])

      # Unless this pull request was merged before the previous release
      # reference was committed
      unless merged_at > previous_revision_date
        # Move to the previous revision
        revision_index += 1
      end

      # Push pull request data in to the revision entries hash
      key = revisions.keys[revision_index]
      sub_commits = []
      pull_request[:commits].each do |c|
        sub_commits << {
          type: :commit,
          info: c
        }
      end
      revisions[key][:entries][pull_request[:info][:merged_at]] = [{
        type:     :pull_request,
        info:     pull_request[:info],
        commits:  sub_commits
      }]

      # Delete commits which are in this pull request from the hash of commits
      commits.delete(sha)
      next if pull_request[:commits].empty?
      pull_request[:commits].each do |c|
        commits.delete(c[:sha])
      end
    end

    # Loop through remaining commits and push them into the revision entries
    # hash
    revision_index = 0
    commits.each do |_sha, commit|
      previous_revision = revisions[revisions.keys[revision_index + 1]]
      previous_revision_date = Time.parse(previous_revision[:date])
      date = commit[:commit][:committer][:date]
      committed_at = Time.parse(date)

      revision_index += 1 unless committed_at > previous_revision_date

      key = revisions.keys[revision_index]
      if revisions[key][:entries][date].nil?
        revisions[key][:entries][date] = []
      end
      revisions[key][:entries][date] << {
        type: :commit,
        info: commit
      }
    end

    # Send the text output to screen, or to a file on the server

    # Create the mustache instance and plug in the revisions
    output = ::Capistrano::Committed::Output.new
    output[:revisions] = revisions.values
    output[:page_title] = t('committed.output.page_title',
                            repo: format('%<user>s/%<repo>s',
                                         user: fetch(:committed_user),
                                         repo: fetch(:committed_repo)))

    # Send the text output to a file on the server
    if fetch(:committed_output_text_path).nil?
      # Just print to STDOUT
      puts output.render
    else
      # Determine the output path and upload the output there
      output_text_path = format(fetch(:committed_output_text_path), current_path)
      upload! StringIO.new(output.render), output_text_path

      # Make sure the report is world readable
      execute(:chmod, 'a+r', output_text_path)
    end

    # Send the html output to a file on the server
    unless fetch(:committed_output_html_path).nil?
      # Switch to the HTML template
      output.template_file = output.get_output_template_path('html')

      # Determine the output path and upload the output there
      output_html_path = format(fetch(:committed_output_html_path), current_path)
      upload! StringIO.new(output.render), output_html_path

      # Make sure the report is world readable
      execute(:chmod, 'a+r', output_html_path)
    end
  end
end

end

# Load the default settings namespace :load do

task :defaults do
  # See README for descriptions of each setting
  set :committed_user,              -> { nil }
  set :committed_repo,              -> { nil }
  set :committed_revision_line,     -> { t('revision_log_message') }
  set :committed_github_config,     -> { {} }
  set :committed_revision_limit,    -> { 10 }
  set :committed_commit_buffer,     -> { 1 }
  set :committed_output_text_path,  -> { '%s/public/committed.txt' }
  set :committed_output_html_path,  -> { '%s/public/committed.html' }
  set :committed_issue_match,       -> { '\[\s?([a-zA-Z0-9]+\-[0-9]+)\s?\]' }
  set :committed_issue_postprocess, -> { [] }
  set :committed_issue_url,         -> { 'https://example.jira.com/browse/%s' }
  set :committed_deployments,       -> { false }
  set :committed_deployment_id,     -> { nil }
end

end