module Agents
  class PushbulletAgent < Agent
    include FormConfigurable

    cannot_be_scheduled!
    cannot_create_events!
    no_bulk_receive!

    before_validation :create_device, on: :create

    API_BASE = 'https://api.pushbullet.com/v2/'
    TYPE_TO_ATTRIBUTES = {
      'note' => [:title, :body],
      'link' => [:title, :body, :url],
      'address' => [:name, :address]
    }
    class Unauthorized < StandardError; end

    description <<~MD
      The Pushbullet agent sends pushes to a pushbullet device

      To authenticate you need to either the `api_key` or create a `pushbullet_api_key` credential, you can find yours at your account page:

      `https://www.pushbullet.com/account`

      If you do not select an existing device, Huginn will create a new one with the name 'Huginn'.

      To push to all of your devices, select `All Devices` from the devices list.

      You have to provide a message `type` which has to be `note`, `link`, or `address`. The message types `checklist`, and `file` are not supported at the moment.

      Depending on the message `type` you can use additional fields:

      * note: `title` and `body`
      * link: `title`, `body`, and `url`
      * address: `name`, and `address`

      In every value of the options hash you can use the liquid templating, learn more about it at the [Wiki](https://github.com/huginn/huginn/wiki/Formatting-Events-using-Liquid).
    MD

    def default_options
      {
        'api_key' => '',
        'device_id' => '',
        'title' => "{{title}}",
        'body' => '{{body}}',
        'type' => 'note',
      }
    end

    form_configurable :api_key, roles: :validatable
    form_configurable :device_id, roles: :completable
    form_configurable :type, type: :array, values: ['note', 'link', 'address']
    form_configurable :title
    form_configurable :body, type: :text
    form_configurable :url
    form_configurable :name
    form_configurable :address

    def validate_options
      errors.add(:base, "you need to specify a pushbullet api_key") if options['api_key'].blank?
      errors.add(:base, "you need to specify a device_id") if options['device_id'].blank?
      errors.add(:base, "you need to specify a valid message type") if options['type'].blank? ||
        !['note', 'link', 'address'].include?(options['type'])
      TYPE_TO_ATTRIBUTES[options['type']].each do |attr|
        errors.add(:base,
                   "you need to specify '#{attr}' for the type '#{options['type']}'") if options[attr].blank?
      end
    end

    def validate_api_key
      devices
      true
    rescue Unauthorized
      false
    end

    def complete_device_id
      devices
        .map { |d| { text: d['nickname'], id: d['iden'] } }
        .unshift(text: 'All Devices', id: '__ALL__')
    end

    def working?
      received_event_without_error?
    end

    def receive(incoming_events)
      incoming_events.each do |event|
        safely do
          response = request(:post, 'pushes', query_options(event))
        end
      end
    end

    private

    def safely
      yield
    rescue Unauthorized => e
      error(e.message)
    end

    def request(http_method, method, options)
      response = JSON.parse(HTTParty.send(http_method, API_BASE + method, options).body)
      raise Unauthorized, response['error']['message'] if response['error'].present?

      response
    end

    def devices
      response = request(:get, 'devices', basic_auth)
      response['devices'].select { |d| d['pushable'] == true }
    rescue Unauthorized
      []
    end

    def create_device
      return if options['device_id'].present?

      safely do
        response = request(:post, 'devices', basic_auth.merge(body: { nickname: 'Huginn', type: 'stream' }))
        self.options[:device_id] = response['iden']
      end
    end

    def basic_auth
      { basic_auth: { username: interpolated[:api_key].presence || credential('pushbullet_api_key'), password: '' } }
    end

    def query_options(event)
      mo = interpolated(event)
      dev_ident = mo[:device_id] == "__ALL__" ? '' : mo[:device_id]
      basic_auth.merge(body: { device_iden: dev_ident, type: mo[:type] }.merge(payload(mo)))
    end

    def payload(mo)
      Hash[TYPE_TO_ATTRIBUTES[mo[:type]].map { |k| [k, mo[k]] }]
    end
  end
end
