#!/usr/bin/env ruby

require "optparse"
require "net/https"
require "uri"
require "json"

OPTIONS = [:channel, :username, :icon, :state, :message,
  :message_file, :file, :title, :tail, :webhook].freeze

options = {}

OptionParser.new do |opts|
  opts.banner = "Usage: #{File.basename(__FILE__)} [options]"

  opts.on("-c", "--channel CHAN", "Send to channel") do |c|
    options[:channel] = c
  end

  opts.on("-u", "--username USER", "Send as username") do |u|
    options[:username] = u
  end

  opts.on("-i", "--icon URL", "User icon image") do |i|
    options[:icon] = i
  end

  opts.on("-s", "--state STATE", "Message state (success, warn, error, or color code)") do |s|
    options[:state] = s
  end

  opts.on("-m", "--message MESSAGE", "Message to send") do |m|
    options[:message] = m
  end

  opts.on("-M", "--message-file MESSAGE_FILE", "Use file contents as message") do |m|
    options[:message_file] = m
  end

  opts.on("-f", "--file MESSAGE_FILE", "Send raw contents of file in message") do |f|
    options[:file] = f
  end

  opts.on("-t", "--title TITLE", "Message title") do |t|
    options[:title] = t
  end

  opts.on("-T", "--tail N", "Send last N lines of content from raw message file") do |t|
    options[:tail] = t
  end

  opts.on("-w", "--webhook HOOK", "Slack webhook") do |w|
    options[:webhook] = w
  end

  opts.on("-h", "--help", "Print help") do
    puts opts
    exit
  end
end.parse!

OPTIONS.each do |key|
  if !options.key?(key)
    env_key = "SLACK_#{key.to_s.upcase}"
    if ENV[env_key]
      options[key] = ENV[env_key]
    end
  end
end

if !options[:webhook]
  $stderr.puts "ERROR: Webhook is required!"
  exit 1
end

if ENV["CIRCLECI"]
  options[:icon] = "https://emoji.slack-edge.com/TF1GCKJNM/circleci/054b58d488e65138.png" unless options[:icon]
  options[:username] = "circleci" unless options[:username]
  options[:footer] = "CircleCI - <#{ENV["CIRCLE_BUILD_URL"]}|#{ENV["CIRCLE_PROJECT_USERNAME"]}/#{ENV["CIRCLE_PROJECT_REPONAME"]}>"
  options[:footer_icon] = "https://emoji.slack-edge.com/TF1GCKJNM/circleci/054b58d488e65138.png"
end

if ENV["GITHUB_ACTIONS"]
  options[:icon] = "https://ca.slack-edge.com/T024UT03C-WG8NDATGT-f82ae03b9fca-48" unless options[:icon]
  options[:username] = "github" unless options[:username]
  options[:footer] = "Actions - <https://github.com/#{ENV["GITHUB_REPOSITORY"]}/commit/#{ENV["GITHUB_SHA"]}/checks|#{ENV["GITHUB_REPOSITORY"]}>"
  options[:footer_icon] = "https://ca.slack-edge.com/T024UT03C-WG8NDATGT-f82ae03b9fca-48"
end

options[:state] = "success" unless options[:state]

case options[:state]
when "success", "good"
  options[:state] = "good"
when "warn", "warning"
  options[:state] = "warning"
when "error", "danger"
  options[:state] = "danger"
else
  if !options[:state].start_with?("#")
    $stderr.puts "ERROR: Invalid value for `state` (#{options[:state]})"
    exit 1
  end
end

msg = options[:message]

# NOTE: Message provided from CLI argument will end up with
#       double escaped newlines so remove one
msg.gsub!("\\n", "\n") if msg

if options[:message_file]
  if !File.exist?(options[:message_file])
    $stderr.puts "ERROR: Message file does not exist `#{options[:message_file]}`"
    exit 1
  end
  msg_c = File.read(options[:message_file])
  msg = msg ? "#{msg}\n\n#{msg_c}" : msg_c
end

if options[:file]
  if !File.exist?(options[:file])
    $stderr.puts "ERROR: Message file does not exist `#{options[:file]}`"
    exit 1
  end
  if (tail = options[:tail].to_i) > 0
    content = ""
    buffer = 0
    File.open(options[:file], "r") do |f|
      until (content.split("\n").size > tail) || buffer >= f.size
        buffer += 1000
        buffer = f.size if buffer > f.size
        f.seek(f.size - buffer)
        content = f.read
      end
    end
    parts = content.split("\n")
    if parts.size > tail
      parts = parts.slice(-tail, tail)
    end
    fmsg = parts ? parts.join("\n") : ""
  else
    fmsg = File.read(options[:file])
  end
  fmsg = "```\n#{fmsg}\n```"
  if msg
    msg = msg << "\n\n" << fmsg
  end
end

if msg.to_s.empty?
  $stderr.puts "ERROR: No message content provided!"
  exit 1
end

attach = {text: msg, fallback: msg, color: options[:state], mrkdn: true}
attach[:title] = options[:title] if options[:title]
attach[:footer] = options[:footer] if options[:footer]
attach[:footer_icon] = options[:footer_icon] if options[:footer_icon]
attach[:ts] = Time.now.to_i

payload = {}.tap do |pd|
  pd[:username] = options.fetch(:username, "packet-exec")
  pd[:channel] = options[:channel] if options[:channel]
  pd[:icon_url] = options[:icon] if options[:icon]
  pd[:attachments] = [attach]
end

result = Net::HTTP.post(URI(options[:webhook]), payload.to_json, "Content-Type" => "application/json")

if !result.code.start_with?("2")
  $stderr.puts "Failed to send slack message"
  exit 1
else
  $stdout.puts "ok"
end
