require 'faraday' require 'json'

namespace :slack do

start = Time.now
elapsed_time = -> { sprintf('%.2f', Time.now - start) }

set :slack_token, nil
set :slack_channel, '#general'

set :slack_endpoint, 'https://slack.com'
set :slack_username, 'capistrano'
set :slack_icon_url, 'https://raw.githubusercontent.com/linyows/capistrano-slack_notification/master/misc/capistrano-icon.png'

set :slack_deployer, -> {
  username = `git config --get user.name`.strip
  username = `whoami`.strip unless username
  username
}

set :slack_post_message_api_endpoint, -> {
  "/api/chat.postMessage?token=#{fetch(:slack_token)}"
}

set :slack_channel_list_api_endpoint, -> {
  "/api/channels.list?exclude_archived=1&token=#{fetch(:slack_token)}"
}

set :slack_path, -> { fetch(:slack_post_message_api_endpoint) }

set :slack_stage, -> {
  stage = fetch(:stage)
  stage.to_s == 'production' ? ":warning: #{stage}" : stage
}

set :slack_default_body, -> {
  {
    username: fetch(:slack_username),
    channel: fetch(:slack_channel),
    icon_url: fetch(:slack_icon_url),
    text: '',
    link_names: 1,
    mrkdwn: true
  }
}

set :slack_start_body, -> {
  text = "Started deploying to #{fetch(:slack_stage)} by @#{fetch(:slack_deployer)}" +
    " (branch #{fetch(:branch)})"

  build_http_body({
    attachments: [{
      color: "warning",
      title: fetch(:application),
      text: text,
      fallback: text,
      mrkdwn_in: ['text']
    }]
  })
}

set :slack_failure_body, -> {
  text = "Failed deploying to #{fetch(:slack_stage)} by @#{fetch(:slack_deployer)}" +
    " (branch #{fetch(:branch)} at #{fetch(:current_revision)} / #{elapsed_time.call} sec)"

  build_http_body({
    attachments: [{
      color: 'danger',
      title: fetch(:application),
      text: text,
      fallback: text,
      mrkdwn_in: ['text']
    }]
  })
}

set :slack_success_body, -> {
  task = fetch(:deploying) ? 'deployment' : '*rollback*'
  text = "Successful #{task} to #{fetch(:slack_stage)} by @#{fetch(:slack_deployer)}" +
    " (branch #{fetch(:branch)} at #{fetch(:current_revision)} / #{elapsed_time.call} sec)"

  build_http_body({
    attachments: [{
      color: 'good',
      title: fetch(:application),
      text: text,
      fallback: text,
      mrkdwn_in: ['text']
    }]
  })
}

set :slack_client, -> {
  Faraday.new(fetch :slack_endpoint) do |c|
    c.request :url_encoded
    c.adapter Faraday.default_adapter

    v = Faraday::VERSION.split('.')
    if v.join('.').to_f >= 0.9
      c.options.timeout = 5
      c.options.open_timeout = 5
    end
  end
}

def build_http_body(body)
  if fetch(:slack_token)
    fetch(:slack_default_body).merge(JSON.dump(body))
  else
    JSON.dump(fetch(:slack_default_body).merge(body))
  end
end

def post_to_slack_with(body)
  run_locally do
    res = fetch(:slack_client).post fetch(:slack_path), body

    if ENV['DEBUG']
      require 'awesome_print'
      ap body
      ap res
    end
  end
end

desc 'Post message to Slack (ex. cap production "slack:post[yo!]")'
task :post, :message do |t, args|
  attachments = [{ text: args[:message] }]
  post_to_slack_with build_http_body(attachments)
end

desc 'Get channel ID by channel name from Slack (ex. cap production "slack:channel[general])"'
task :channel, :channel_name do |t, args|
  run_locally do
    res = fetch(:slack_client).post fetch(:slack_channel_list_api_endpoint)
    body = JSON.load(res.body)
    channel = body['channels'].find { |ch| ch['name'] == args[:channel_name] }
    puts "##{args[:channel_name]}: #{channel['id']}"
  end
end

namespace :deploy do
  desc 'Notify a deploy starting to Slack'
  task :start do
    post_to_slack_with fetch(:slack_start_body)
  end

  desc 'Notify a deploy rollback to Slack'
  task :rollback do
    post_to_slack_with fetch(
      :"slack_#{fetch(:deploying) ? :failure : :success}_body")
  end

  desc 'Notify a deploy finish to Slack'
  task :finish do
    post_to_slack_with fetch(:slack_success_body)
  end
end

end