#!/usr/bin/env ruby

require 'sinatra/base'

require 'flapjack/data/entity'
require 'flapjack/data/entity_check'

module Flapjack

  module Gateways

    class JSONAPI < Sinatra::Base

      module EntityMethods

        module Helpers

          def checks_for_entity_ids(entity_ids)
            return if entity_ids.nil?
            entity_ids.inject([]) do |memo, entity_id|
              entity = find_entity_by_id(entity_id)
              check_names = entity.check_list.sort
              check_names.each do |check_name|
                memo << find_entity_check(entity, check_name)
              end
              memo
            end
          end
        end

        def self.registered(app)
          app.helpers Flapjack::Gateways::JSONAPI::Helpers
          app.helpers Flapjack::Gateways::JSONAPI::EntityMethods::Helpers

          # Returns all (/entities) or some (/entities/A,B,C) or one (/entities/A) contact(s)
          # NB: only works with good data -- i.e. entity must have an id
          app.get %r{^/entities(?:/)?([^/]+)?$} do
            requested_entities = if params[:captures] && params[:captures][0]
              params[:captures][0].split(',').uniq
            else
              nil
            end

            entities = if requested_entities
              Flapjack::Data::Entity.find_by_ids(requested_entities, :logger => logger, :redis => redis)
            else
              Flapjack::Data::Entity.all(:redis => redis).reject {|e| e.id.nil? || e.id.empty? }
            end
            entities.compact!

            if requested_entities && entities.empty?
              raise Flapjack::Gateways::JSONAPI::EntitiesNotFound.new(requested_entities)
            end

            linked_contact_ids = entities.empty? ? [] : Flapjack::Data::Entity.contact_ids_for(entities.map(&:id), :redis => redis)

            entities_json = entities.collect {|entity|
              entity.to_jsonapi(:contact_ids => linked_contact_ids[entity.id])
            }.join(",")

            '{"entities":[' + entities_json + ']}'
          end

          app.post '/entities' do
            entities = wrapped_params('entities')
            return err(403, "Entity with a nil id detected") if entities.any? {|e| e['id'].nil?}

            entity_ids = entities.collect{|entity_data|
              Flapjack::Data::Entity.add(entity_data, :redis => redis)
              entity_data['id']
            }

            response.headers['Location'] = "#{request.base_url}/entities/#{entity_ids.join(',')}"
            status 201
            entity_ids.to_json
          end

          app.patch '/entities/:id' do
            params[:id].split(',').collect {|entity_id|
              find_entity_by_id(entity_id)
            }.each do |entity|
              apply_json_patch('entities') do |op, property, linked, value|
                case op
                when 'replace'
                  if ['name'].include?(property)
                    # # Name change not supported in Flapjack v1.x, too many changes required
                    # entity.update(property => value)
                  end
                when 'add'
                  if 'contacts'.eql?(linked)
                    contact = Flapjack::Data::Contact.find_by_id(value, :redis => redis)
                    contact.add_entity(entity) unless contact.nil?
                  end
                when 'remove'
                  if 'contacts'.eql?(linked)
                    contact = Flapjack::Data::Contact.find_by_id(value, :redis => redis)
                    contact.remove_entity(entity) unless contact.nil?
                  end
                end
              end
            end

            status 204
          end

          # create a scheduled maintenance period for a check on an entity
          app.post %r{^/scheduled_maintenances/entities/([^/]+)$} do
            scheduled_maintenances = wrapped_params('scheduled_maintenances')
            checks_for_entity_ids(params[:captures][0].split(',')).each do |check|
              scheduled_maintenances.each do |wp|
                start_time = validate_and_parsetime(wp['start_time'])
                halt( err(403, "start time must be provided") ) unless start_time
                check.create_scheduled_maintenance(start_time,
                  wp['duration'].to_i, :summary => wp['summary'])
              end
            end

            status 204
          end

          # create an acknowledgement for a service on an entity
          # NB currently, this does not acknowledge a specific failure event, just
          # the entity-check as a whole
          app.post %r{^/unscheduled_maintenances/entities/([^/]+)$} do
            unscheduled_maintenances = wrapped_params('unscheduled_maintenances', false)
            checks_for_entity_ids(params[:captures][0].split(',')).each do |check|
              unscheduled_maintenances.each do |wp|
                dur = wp['duration'] ? wp['duration'].to_i : nil
                duration = (dur.nil? || (dur <= 0)) ? (4 * 60 * 60) : dur
                summary = wp['summary']

                opts = {:duration => duration}
                opts[:summary] = summary if summary

                Flapjack::Data::Event.create_acknowledgement(
                  check.entity_name, check.check, {:redis => redis}.merge(opts))
              end
            end

            status 204
          end

          app.patch %r{^/unscheduled_maintenances/entities/([^/]+)$} do
            checks_for_entity_ids( params[:captures][0].split(',') ).each do |check|
              apply_json_patch('unscheduled_maintenances') do |op, property, linked, value|
                case op
                when 'replace'
                  if ['end_time'].include?(property)
                    end_time = validate_and_parsetime(value)
                    check.end_unscheduled_maintenance(end_time.to_i)
                  end
                end
              end
            end
            status 204
          end

          app.delete %r{^/scheduled_maintenances/entities/([^/]+)$} do
            start_time = validate_and_parsetime(params[:start_time])
            halt( err(403, "start time must be provided") ) unless start_time

            checks_for_entity_ids(params[:captures][0].split(',')).each do |check|
              check.end_scheduled_maintenance(start_time.to_i)
            end
            status 204
          end

          app.post %r{^/test_notifications/entities/([^/]+)$} do
            test_notifications = wrapped_params('test_notifications', false)
            checks_for_entity_ids(params[:captures][0].split(',')).each do |check|
              test_notifications.each do |wp|
                summary = wp['summary'] ||
                          "Testing notifications to all contacts interested in entity #{check.entity.name}"
                Flapjack::Data::Event.test_notifications(
                  check.entity_name, check.check,
                  :summary => summary,
                  :redis => redis)
              end
            end
            status 204
          end

        end

      end

    end

  end

end