require 'logger'
require 'net/http'

require 'tilt/erb'
require 'queryparams'

require_relative 'metadata'
require_relative 'mjolnir'
require_relative 'helpers'
require_relative 'web'
require_relative 'bot'

Thread.abort_on_exception = true


module Bender
  class Main < Mjolnir
    include Helpers

    desc 'version', 'Echo the application version'
    def version
      puts VERSION
    end


    desc 'art', 'View the application art'
    def art
      puts "\n%s\n" % ART
    end



    desc 'bot', 'Start Bender HipChat bot and Web server'
    option :bind, \
      type: :string,
      aliases: %w[ -b ],
      desc: 'Set Sinatra interface',
      default: '0.0.0.0'
    option :port, \
      type: :numeric,
      aliases: %w[ -o ],
      desc: 'Set Sinatra port',
      default: 4567
    option :environment, \
      type: :string,
      aliases: %w[ -e ],
      desc: 'Set Sinatra environment',
      default: 'development'
    option :hipchat_token, \
      type: :string,
      aliases: %w[ -t ],
      desc: 'Set HipChat v1 API token',
      required: true
    option :hipchat_v2_token, \
      type: :string,
      aliases: %w[ -c ],
      desc: 'Set HipChat v2 API token',
      required: true
    option :primary_room_id, \
      type: :string,
      aliases: %w[ -i ],
      desc: 'Set HipChat primary room ID',
      required: true
    option :jid, \
      type: :string,
      aliases: %w[ -j ],
      desc: 'Set HipChat JID',
      required: true
    option :password, \
      type: :string,
      aliases: %w[ -p ],
      desc: 'Set HipChat password',
      required: true
    option :nick, \
      type: :string,
      aliases: %w[ -n ],
      desc: 'Set HipChat nick name',
      required: true
    option :mention, \
      type: :string,
      aliases: %w[ -m ],
      desc: 'Set HipChat mention name',
      required: true
    option :rooms, \
      type: :string,
      aliases: %w[ -r ],
      desc: 'Set HipChat rooms (comma-separated)',
      required: true
    option :database, \
      type: :string,
      aliases: %w[ -d ],
      desc: 'Set path to application database',
      required: true
    option :jira_user, \
      type: :string,
      aliases: %w[ -U ],
      desc: 'Set JIRA username',
      required: true
    option :jira_pass, \
      type: :string,
      aliases: %w[ -P ],
      desc: 'Set JIRA password',
      required: true
    option :jira_site, \
      type: :string,
      aliases: %w[ -S ],
      desc: 'Set JIRA site',
      required: true
    option :jira_project, \
      type: :string,
      aliases: %w[ -J ],
      desc: 'Set JIRA project',
      required: true
    option :jira_group, \
      type: :string,
      aliases: %w[ -G ],
      desc: 'Set JIRA group for write mode',
      required: true
    option :jira_type, \
      type: :string,
      aliases: %w[ -T ],
      desc: 'Set JIRA issue type',
      required: true
    option :user_refresh, \
      type: :numeric,
      aliases: %w[ -R ],
      desc: 'Set JIRA user refresh rate',
      default: 300
    option :issue_refresh, \
      type: :numeric,
      aliases: %w[ -S ],
      desc: 'Set JIRA issue refresh rate',
      default: 5
    option :group_refresh, \
      type: :numeric,
      aliases: %w[ -T ],
      desc: 'Set JIRA group refresh rate',
      default: 60
    option :order, \
      type: :boolean,
      aliases: %w[ -O ],
      desc: 'Reverse order of HipChat API loading (hack)',
      default: false
    include_common_options
    def start
      bot = start_bot
      periodically_refresh_group bot
      periodically_refresh_users bot
      periodically_refresh_incidents bot
      serve_web bot
    end



  private

    def start_bot
      Bot::Connection.configure do |config|
        config.jid = options.jid
        config.password = options.password
        config.nick = options.mention
        config.mention_name = options.nick
        config.rooms = options.rooms.split(',')

        Bot::Storage::YamlStore.file = options.database
        config.store = Bot::Storage::YamlStore

        config.logger = log
      end

      Bot.run! options, log
    end


    def set_room_name_and_topic room_id, incidents, hipchat, bot
      room = hipchat[room_id]
      new_room = room.get_room

      open_incidents = incidents.select do |i|
        status    = normalize_value i['fields']['status']
        severity  = short_severity(i['fields'][SEVERITY_FIELD]['value'])
        is_open   = !(status =~ /resolved|closed/i)
        is_severe = severity =~ /(SEV1|SEV2)/i
        is_open && is_severe
      end

      @room_name  ||= bot.store['primary_room_name']  || new_room['name']
      @room_topic ||= bot.store['primary_room_topic'] || new_room['topic']

      @open = nil unless defined? @open

      log.trace \
        primary_room_name: bot.store['primary_room_name'],
        primary_room_topic: bot.store['primary_room_topic'],
        new_room_name: new_room['name'],
        new_room_topic: new_room['topic'],
        room_name: @room_name,
        room_topic: @room_topic,
        open: @open

      if open_incidents.empty?
        if @open.nil? || @open > 0
          new_room['name'] = '[NONE] Production Incident'
          new_room['topic'] = 'Good news everyone! No high-severity incidents at the moment'
          begin
            room.update_room(new_room)
          rescue NoMethodError
            log.warn 'NoMethodError in set_room_name_and_topic'
          end
        end

        @open = 0

      else
        if @open.nil? || @open.zero?
          @room_name = new_room['name']
          @room_topic = new_room['topic']
        end

        unless @open == open_incidents.size
          new_room['name'] = '[IN PROGRESS] Production Incident'
          tha_news = open_incidents.size == 1 ? "There's a high-severity incident" : "There are #{open_incidents.size} high-severity incidents"
          new_room['topic'] = "Terrible news everyone! #{tha_news}"
          begin
            room.update_room(new_room)
          rescue NoMethodError
            log.warn 'NoMethodError in set_room_name_and_topic'
          end
        end

        @open = open_incidents.size
      end

      bot.store['primary_room_name'] = @room_name
      bot.store['primary_room_topic'] = @room_topic
    end


    def periodically_refresh_incidents bot
      Thread.new do
        if options.order
          hipchat_v1 = HipChat::Client.new \
            options.hipchat_token, api_version: 'v1'
          hipchat_v2 = HipChat::Client.new \
            options.hipchat_v2_token, api_version: 'v2'
        else
          hipchat_v2 = HipChat::Client.new \
            options.hipchat_v2_token, api_version: 'v2'
          hipchat_v1 = HipChat::Client.new \
            options.hipchat_token, api_version: 'v1'
        end

        room_id = options.primary_room_id

        loop do
          is = refresh_incidents bot
          begin
            set_room_name_and_topic room_id, is, hipchat_v2, bot
            sleep options.issue_refresh
          rescue HipChat::UnknownResponseCode
            log.warn 'HipChat::UnknownResponseCode in set_room_name_and_topic'
            sleep 2
          end
        end
      end
    end


    def periodically_refresh_users bot
      req_path = '/rest/api/2/user/assignable/search'
      req_params = QueryParams.encode \
        project: options.jira_project,
        startAt: 0,
        maxResults: 1_000_000

      uri = URI(options.jira_site + req_path + '?' + req_params)
      http = Net::HTTP.new uri.hostname, uri.port

      req = Net::HTTP::Get.new uri
      req.basic_auth options.jira_user, options.jira_pass
      req['Content-Type'] = 'application/json'
      req['Accept'] = 'application/json'

      Thread.new do
        loop do
          begin
            resp = http.request req
            data = JSON.parse(resp.body)
          rescue StandardError => e
            log.error \
              message: 'Could not periodically refresh users',
              error: e
            sleep 5
            next
          end

          users = data.inject({}) do |h, user|
            h[user['key']] = {
              nick: user['key'],
              name: user['displayName'],
              email: user['emailAddress']
            } ; h
          end

          bot.store['users'] = users

          sleep options.user_refresh
        end
      end
    end


    def periodically_refresh_group bot
      req_path = '/rest/api/2/group'
      req_params = QueryParams.encode \
        groupname: options.jira_group,
        expand: 'users'

      uri = URI(options.jira_site + req_path + '?' + req_params)
      http = Net::HTTP.new uri.hostname, uri.port

      req = Net::HTTP::Get.new uri
      req.basic_auth options.jira_user, options.jira_pass
      req['Content-Type'] = 'application/json'
      req['Accept'] = 'application/json'

      Thread.new do
        loop do
          begin
            resp = http.request req
            data = JSON.parse resp.body
          rescue StandardError => e
            log.error \
              message: 'Could not periodically refresh group',
              error: e
            sleep 5
            next
          end

          user_names = data['users']['items'].map { |u| u['displayName'] }
          bot.store['group'] = user_names
          sleep options.group_refresh
        end
      end
    end


    def serve_web bot
      Web.set :environment, options.environment
      Web.set :port, options.port
      Web.set :bind, options.bind
      Web.set :store, options.database

      if log.level >= ::Logger::DEBUG
        Web.set :raise_errors, true
        Web.set :dump_errors, true
        Web.set :show_exceptions, true
        Web.set :logging, ::Logger::DEBUG
      end

      Web.set :bot, bot
      Web.run!
    end


  end
end