# Description:
#   PagerDuty Integration for checking who's on call, making exceptions, ack, resolve, etc.
#
# Commands:
#
#   hubot who's on call - return the username of who's on call
#   hubot pager me trigger <msg> - create a new incident with <msg>
#   hubot pager me 60 - take the pager for 60 minutes
#   hubot pager me as <email> - remember your pager email is <email>
#   hubot pager me incidents - return the current incidents
#   hubot pager me incident NNN - return the incident NNN
#   hubot pager me note <incident> <content> - add note to incident #<incident> with <content>
#   hubot pager me notes <incident> - show notes for incident #<incident>
#   hubot pager me problems - return all open incidents
#   hubot pager me ack <incident> - ack incident #<incident>
#   hubot pager me resolve <incident1> <incident2> ... <incidentN> - ack all specified incidents
#   hubot pager me ack - ack all triggered incidents 
#   hubot pager me resolve <incident> - resolve incident #<incident>
#   hubot pager me resolve <incident1> <incident2> ... <incidentN>- resolve all specified incidents
#   hubot pager me resolve - resolve all acknowledged incidents
#
# Dependencies:
#  "moment": "1.6.2"
#
# Configuration:
#
#   HUBOT_PAGERDUTY_API_KEY - API Access Key
#   HUBOT_PAGERDUTY_SUBDOMAIN
#   HUBOT_PAGERDUTY_SERVICE_API_KEY - Service API Key from a 'General API Service'
#   HUBOT_PAGERDUTY_SCHEDULE_ID

inspect = require('util').inspect

moment = require('moment')

pagerDutyUsers = {}
pagerDutyApiKey        = process.env.HUBOT_PAGERDUTY_API_KEY
pagerDutySubdomain     = process.env.HUBOT_PAGERDUTY_SUBDOMAIN
pagerDutyBaseUrl       = "https://#{pagerDutySubdomain}.pagerduty.com/api/v1"
pagerDutyServiceApiKey = process.env.HUBOT_PAGERDUTY_SERVICE_API_KEY
pagerDutyScheduleId    = process.env.HUBOT_PAGERDUTY_SCHEDULE_ID

module.exports = (robot) ->
  robot.respond /pager( me)?$/i, (msg) ->
    if missingEnvironmentForApi(msg)
      return

    emailNote = if msg.message.user.pagerdutyEmail
                  "You've told me your PagerDuty email is #{msg.message.user.pagerdutyEmail}"
                else if msg.message.user.email_address
                  "I'm assuming your PagerDuty email is #{msg.message.user.email_address}. Change it with `#{robot.name} pager me as you@yourdomain.com`"
                else
                  "I don't know your PagerDuty email. Change it with `#{robot.name} pager me as you@yourdomain.com`"

    cmds = robot.helpCommands()
    cmds = (cmd for cmd in cmds when cmd.match(/(pager me |who's on call)/))
    msg.send emailNote, cmds.join("\n")

  robot.respond /pager(?: me)? as (.*)$/i, (msg) ->
    email = msg.match[1]
    msg.message.user.pagerdutyEmail = email
    msg.send "Okay, I'll remember your PagerDuty email is #{email}"

  # Assumes your Campfire usernames and PagerDuty names are identical
  robot.respond /pager( me)? (\d+)/i, (msg) ->
    withPagerDutyUsers msg, (users) ->

      userId = pagerDutyUserId(msg, users)
      return unless userId

      start     = moment().format()
      minutes   = parseInt msg.match[2]
      end       = moment().add('minutes', minutes).format()
      override  = {
        'start':     start,
        'end':       end,
        'user_id':   userId
      }
      withCurrentOncall msg, (old_username) ->
        data = { 'override': override }
        pagerDutyPost msg, "/schedules/#{pagerDutyScheduleId}/overrides", data, (json) ->
          if json.override
            start = moment(json.override.start)
            end = moment(json.override.end)
            msg.send "Rejoice, #{old_username}! #{json.override.user.name} has the pager until #{end.format()}"

  robot.respond /(pager|major)( me)? incident (.*)$/, (msg) ->
    pagerDutyIncident msg, msg.match[3], (incident) ->
      msg.send formatIncident(incident)

  robot.respond /(pager|major)( me)? (inc|incidents|sup|problems)$/i, (msg) ->
    pagerDutyIncidents msg, "triggered,acknowledged", (incidents) ->
      if incidents.length > 0
        buffer = "Triggered:\n----------\n"
        for junk, incident of incidents.reverse()
          if incident.status == 'triggered'
            buffer = buffer + formatIncident(incident)
        buffer = buffer + "\nAcknowledged:\n-------------\n"
        for junk, incident of incidents.reverse()
          if incident.status == 'acknowledged'
            buffer = buffer + formatIncident(incident)
        msg.send buffer
      else
        msg.send "No open incidents"

  robot.respond /(pager|major)( me)? (?:trigger|page) (.+)$/i, (msg) ->
    user = msg.message.user.name
    reason = msg.match[3]

    description = "#{reason} - @#{user}"
    pagerDutyIntegrationAPI msg, "trigger", description, (json) ->
      msg.reply "#{json.status}, key: #{json.incident_key}"

  robot.respond /(?:pager|major)(?: me)? ack(?:nowledge)? (.+)$/i, (msg) ->
    incidentNumbers = parseIncidentNumbers(msg.match[1])

    # only acknowledge triggered things, since it doesn't make sense to re-acknowledge if it's already in re-acknowledge
    # if it ever doesn't need acknowledge again, it means it's timed out and has become 'triggered' again anyways
    updateIncidents(msg, incidentNumbers, 'triggered,acknowledged', 'acknowledged')

  robot.respond /(pager|major)( me)? ack(nowledge)?$/i, (msg) ->
    pagerDutyIncidents msg, 'triggered,acknwowledged', (incidents) ->
      incidentNumbers = (incident.incident_number for incident in incidents)
      if incidentNumbers.length < 1
        msg.send "Nothing to acknowledge"
        return

      # only acknowledge triggered things
      updateIncidents(msg, incidentNumbers, 'triggered,acknowledged', 'acknowledged')

  robot.respond /(?:pager|major)(?: me)? res(?:olve)?(?:d)? (.+)$/i, (msg) ->
    incidentNumbers = parseIncidentNumbers(msg.match[1])

    # allow resolving of triggered and acknowedlge, since being explicit
    updateIncidents(msg, incidentNumbers, 'triggered,acknowledged', 'resolved')

  robot.respond /(pager|major)( me)? res(olve)?(d)?$/i, (msg) ->
    pagerDutyIncidents msg, "acknowledged", (incidents) ->
      incidentNumbers = (incident.incident_number for incident in incidents)
      if incidentNumbers.length < 1
        msg.send "Nothing to resolve"
        return

      # only resolve things that are acknowledged 
      updateIncidents(msg, incidentNumbers, 'acknowledged', 'resolved')

  robot.respond /(pager|major)( me)? notes (.+)$/i, (msg) ->
    incidentId = msg.match[3]
    pagerDutyGet msg, "/incidents/#{incidentId}/notes", {}, (json) ->
      buffer = ""
      for note in json.notes
        buffer += "#{note.created_at} #{note.user.name}: #{note.content}\n"
      msg.send buffer


  robot.respond /(pager|major)( me)? note ([\d\w]+) (.+)$/i, (msg) ->
    incidentId = msg.match[3]
    content = msg.match[4]

    withPagerDutyUsers msg, (users) ->

      userId = pagerDutyUserId(msg, users)
      return unless userId

      data =
        note:
          content: content
        requester_id: userId

      pagerDutyPost msg, "/incidents/#{incidentId}/notes", data, (json) ->
        if json && json.note
          msg.send "Got it! Note created: #{json.note.content}"
        else
          msg.send "Sorry, I couldn't do it :("


  # who is on call?
  robot.respond /who('s|s| is)? (on call|oncall)/i, (msg) ->
    withCurrentOncall msg, (username) ->
      msg.reply "#{username} is on call"

  parseIncidentNumbers = (match) ->
    match.split(/[ ,]+/).map (incidentNumber) ->
      parseInt(incidentNumber)

  missingEnvironmentForApi = (msg) ->
    missingAnything = false
    unless pagerDutySubdomain?
      msg.send "PagerDuty Subdomain is missing:  Ensure that HUBOT_PAGERDUTY_SUBDOMAIN is set."
      missingAnything |= true
    unless pagerDutyApiKey?
      msg.send "PagerDuty API Key is missing:  Ensure that HUBOT_PAGERDUTY_API_KEY is set."
      missingAnything |= true
    unless pagerDutyScheduleId?
      msg.send "PagerDuty Schedule ID is missing:  Ensure that HUBOT_PAGERDUTY_SCHEDULE_ID is set."
      missingAnything |= true
    missingAnything


  pagerDutyUserId = (msg, users) ->
    email  = msg.message.user.pagerdutyEmail || msg.message.user.email_address
    unless email
      msg.send "Sorry, I can't figure out your email address :( Can you tell me with `#{robot.name} pager me as you@yourdomain.com`?"
      return

    user = users[email]

    unless user
      msg.send "Sorry, I couldn't find a PagerDuty user for #{email}. Double check you have a user, and that I know your PagerDuty email with `#{robot.name} pager me as you@yourdomain.com`"
      return

    users[email].id

  pagerDutyGet = (msg, url, query, cb) ->
    if missingEnvironmentForApi(msg)
      return

    auth = "Token token=#{pagerDutyApiKey}"
    msg.http(pagerDutyBaseUrl + url)
      .query(query)
      .headers(Authorization: auth, Accept: 'application/json')
      .get() (err, res, body) ->
        json_body = null
        switch res.statusCode
          when 200 then json_body = JSON.parse(body)
          else
            console.log res.statusCode
            console.log body
            json_body = null
        cb json_body

  pagerDutyPut = (msg, url, data, cb) ->
    if missingEnvironmentForApi(msg)
      return

    json = JSON.stringify(data)
    auth = "Token token=#{pagerDutyApiKey}"
    msg.http(pagerDutyBaseUrl + url)
      .headers(Authorization: auth, Accept: 'application/json')
      .header("content-type","application/json")
      .header("content-length",json.length)
      .put(json) (err, res, body) ->
        json_body = null
        switch res.statusCode
          when 200 then json_body = JSON.parse(body)
          else
            console.log res.statusCode
            console.log body
            json_body = null
        cb json_body

  pagerDutyPost = (msg, url, data, cb) ->
    if missingEnvironmentForApi(msg)
      return

    json = JSON.stringify(data)
    auth = "Token token=#{pagerDutyApiKey}"
    msg.http(pagerDutyBaseUrl + url)
      .headers(Authorization: auth, Accept: 'application/json')
      .header("content-type","application/json")
      .header("content-length",json.length)
      .post(json) (err, res, body) ->
        json_body = null
        switch res.statusCode
          when 201 then json_body = JSON.parse(body)
          else
            console.log res.statusCode
            console.log body
            json_body = null
        cb json_body

  withCurrentOncall = (msg, cb) ->
    oneHour = moment().add('hours', 1).format()
    now = moment().format()

    query = {
      since: now,
      until: oneHour,
      overflow: 'true'
    }
    pagerDutyGet msg, "/schedules/#{pagerDutyScheduleId}/entries", query, (json) ->
      if json.entries and json.entries.length > 0
        cb(json.entries[0].user.name)

  withPagerDutyUsers = (msg, cb) ->
    if pagerDutyUsers['loaded'] != true
      pagerDutyGet msg, "/users", {}, (json) ->
        pagerDutyUsers['loaded'] = true
        for user in json.users
          pagerDutyUsers[user.id] = user
          pagerDutyUsers[user.email] = user
          pagerDutyUsers[user.name] = user
        cb(pagerDutyUsers)
    else
      cb(pagerDutyUsers)

  pagerDutyIncident = (msg, incident, cb) ->
    pagerDutyGet msg, "/incidents/#{encodeURIComponent incident}", {}, (json) ->
      cb(json)

  pagerDutyIncidents = (msg, status, cb) ->
    query =
      status:  status
      sort_by: "incident_number:asc"
    pagerDutyGet msg, "/incidents", query, (json) ->
      cb(json.incidents)

  pagerDutyIntegrationAPI = (msg, cmd, description, cb) ->
    unless pagerDutyServiceApiKey?
      msg.send "PagerDuty API service key is missing."
      msg.send "Ensure that HUBOT_PAGERDUTY_SERVICE_API_KEY is set."
      return

    data = null
    switch cmd
      when "trigger"
        data = JSON.stringify { service_key: pagerDutyServiceApiKey, event_type: "trigger", description: description}
        pagerDutyIntergrationPost msg, data, (json) ->
          cb(json)

  formatIncident = (inc) ->
     # { pd_nagios_object: 'service',
     #   HOSTNAME: 'fs1a',
     #   SERVICEDESC: 'snapshot_repositories',
     #   SERVICESTATE: 'CRITICAL',
     #   HOSTSTATE: 'UP' },
    
    summary = if inc.trigger_summary_data
              # email services
              if inc.trigger_summary_data.subject
                inc.trigger_summary_data.subject
              else if inc.trigger_summary_data.description
                inc.trigger_summary_data.description
              else if inc.trigger_summary_data.pd_nagios_object == 'service'
                 "#{inc.trigger_summary_data.HOSTNAME}/#{inc.trigger_summary_data.SERVICEDESC}"
              else if inc.trigger_summary_data.pd_nagios_object == 'host'
                 "#{inc.trigger_summary_data.HOSTNAME}/#{inc.trigger_summary_data.HOSTSTATE}"
              else
                ""
            else
              ""
    assigned_to = if inc.assigned_to_user
                    "- assigned to #{inc.assigned_to_user.name}"
                  else
                    ""
                    

    "#{inc.incident_number}: #{inc.created_on} #{summary} #{assigned_to}\n"

  updateIncidents = (msg, incidentNumbers, statusFilter, updatedStatus) ->
    withPagerDutyUsers msg, (users) ->
      requesterId = pagerDutyUserId(msg, users)
      return unless requesterId

      pagerDutyIncidents msg, statusFilter, (incidents) ->
        foundIncidents = []
        for incident in incidents
          # FIXME this isn't working very consistently
          if incidentNumbers.indexOf(incident.incident_number) > -1
            foundIncidents.push(incident)

        if foundIncidents.length == 0
          msg.reply "Couldn't find incidents #{incidentNumbers.join(', ')} in #{inspect incidents}"
        else
          # loljson
          data = {
            requester_id: requesterId
            incidents: foundIncidents.map (incident) ->
              {
                'id':     incident.id,
                'status': updatedStatus
              }
          }

          pagerDutyPut msg, "/incidents", data , (json) ->
            if json?.incidents
              buffer = "Incident"
              buffer += "s" if json.incidents.length > 1
              buffer += " "
              buffer += (incident.incident_number for incident in json.incidents).join(", ")
              buffer += " #{updatedStatus}"
              msg.reply buffer
            else
              msg.reply "Problem updating incidents #{incidentNumbers.join(',')}"


  pagerDutyIntergrationPost = (msg, json, cb) ->
    msg.http('https://events.pagerduty.com/generic/2010-04-15/create_event.json')
      .header("content-type","application/json")
      .header("content-length", json.length)
      .post(json) (err, res, body) ->
        switch res.statusCode
          when 200
            json = JSON.parse(body)
            cb(json)
          else
            console.log res.statusCode
            console.log body
