require 'date'
require 'logger'
require 'nokogiri'
require 'typhoeus'

require_relative '../../../common/configuration_keys'
require_relative '../../../common/job_helper'
require_relative '../../../common/models/models'
require_relative '../helpers/configuration_helper'
require_relative '../helpers/worker_helper'
require_relative '../worker'

module ExternalMemory
  module Workers

    # Worker that process jobs about indexation
    class IndexerWorker < Worker

      include ExternalMemory::ConfigurationKeys
      include ExternalMemory::JobHelper
      include ExternalMemory::Workers::Helpers::WorkerHelper
      include ExternalMemory::Workers::Helpers::ConfigurationHelper

      ELASTICSEARCH_BATCH_SIZE_KEY = 'elasticsearch_index_batch_size'

      def services
        [INDEX_ALL_ELEMENTS_SERVICE, INDEX_NEXT_ELEMENTS_SERVICE]
      end

      def initialize(configuration)
        @logger = Logger.new(STDOUT)
        @batch_size = Integer(read_item(configuration, ELASTICSEARCH_BATCH_SIZE_KEY))
        @logger.debug('IndexerWorker') { "Elasticsearch batch size is [#{@batch_size}]" }
        @index_name = read_item(configuration, ELASTICSEARCH_INDEX_NAME_KEY)
        @logger.debug('IndexerWorker') { "Elasticsearch index name is [#{@index_name}]" }
        @type_name = read_item(configuration, ELASTICSEARCH_INDEX_TYPE_NAME_KEY)
        @logger.debug('IndexerWorker') { "Elasticsearch index type name is [#{@type_name}]" }
        elasticsearch_url = read_item(configuration, ELASTICSEARCH_URL_KEY)
        @logger.debug('IndexerWorker') { "Elasticsearch url [#{elasticsearch_url}]" }
        @elasticsearch_bulk_url = "#{elasticsearch_url}/_bulk"

        unless has_job?(INDEX_NEXT_ELEMENTS_SERVICE)
          create_index_next_elements_job
        end

      end

      def process(service_name, execution_parameters, execution_context)
        case service_name
          when INDEX_ALL_ELEMENTS_SERVICE
            process_index_all_elements(execution_context)
          when INDEX_NEXT_ELEMENTS_SERVICE
            process_index_next_elements(execution_context)
          else
            raise "Unknown service [#{service_name}]"
        end
      end

      # Index all elements
      # @param execution_context [ExternalMemory::Workers::ExecutionContext]
      def process_index_all_elements(execution_context)
        transaction do
          ExternalMemory::Element.all.each do |element|
            element.request_indexation
          end
          create_index_next_elements_job(execution_context)
        end
      end

      @next_elements_running = false

      # Index the next elements
      def process_index_next_elements(execution_context)
        if @next_elements_running
          @logger.debug('IndexerWorker') { "#{INDEX_NEXT_ELEMENTS_SERVICE} is already running" }
        else
          @next_elements_running = true
          begin
            transaction do
              index_requests = []

              # Unindexation
              ExternalMemory::ElementUnindexRequest.
                for_update.
                limit(@batch_size).
                order(Sequel.asc(:element_unindex_requests__created_at)).
                each do |element_unindex_request|
                index_requests << {
                  :delete => {
                    :_index => @index_name,
                    :_type => @type_name,
                    :_id => element_unindex_request.element_id
                  }
                }
                element_unindex_request.delete
              end

              # Indexation
              ExternalMemory::ElementIndexRequest.
                for_update.
                limit(@batch_size - index_requests.length).
                order(Sequel.asc(:element_index_requests__created_at)).
                each do |element_index_request|
                index_requests << {
                  :index => {
                    :_index => @index_name,
                    :_type => @type_name,
                    :_id => element_index_request.element_id
                  }
                }
                index_requests << create_index_request(element_index_request)
                element_index_request.delete
              end

              # If we did something then run another job run now
              if index_requests.empty?
                create_job(INDEX_NEXT_ELEMENTS_SERVICE, {}, execution_context, 5*60)
              else
                apply_index_request(index_requests)
                create_index_next_elements_job(execution_context)
              end

            end
          ensure
            @next_elements_running = false
          end
        end
      end

      def create_index_request(element_index_request)
        element = element_index_request.element
        source = element.source
        result = {
          :source_id => element.source_id,
          :urls => [],
          :resources => []
        }
        case source.type
          when ExternalMemory::Source::API_TYPE
            api_element = ExternalMemory::ApiElement.where(:element => element).first
            result[:description] = api_element.description
            result[:custom_ids] = api_element.custom_ids
            result[:authors] = api_element.authors
            result[:tags] = api_element.tags
          when ExternalMemory::Source::TWITTER_TYPE
            tweet_element = ExternalMemory::TweetElement.where(:element => element).first
            result[:tweet_id] = tweet_element.tweet_id
            result[:tweet_content] = Yajl::Parser.parse(tweet_element.content)
          else
            raise "Unknown source type [#{source.type}]"
        end

        element.element_urls.each do |element_url|
          result[:urls] << element_url.url
          resource = element_url.resource
          if resource
            result_resource = {}

            resource_text = ExternalMemory::ResourceText.where(:resource => resource).first
            if resource_text && resource_text.extracted_text
              text = Nokogiri::HTML(resource_text.extracted_text).text
              text = text.gsub(/\r/, ' ').gsub(/\n/, ' ').gsub(/\t/, ' ').squeeze(' ')
              result_resource[:extracted_text] = text
            end

            entity = ExternalMemory::ResourceEntity.where(:resource => resource).first
            if entity && entity.content
              result_resource[:entities] = Yajl::Parser.parse(entity.content)
            end

            result[:resources] << result_resource
          end
        end
        result
      end

      def apply_index_request(index_requests)
        request_content = index_requests.collect { |ir| Yajl::Encoder.encode(ir) }.join("\n")
        request = Typhoeus::Request.new(
          @elasticsearch_bulk_url,
          {
            :method => :post,
            :body => request_content
          })

        request.on_failure do |response|
          process_error(response)
        end

        response = request.run

        if response.success?
          response_content = response.response_body
          status = response.response_code
          if status != 200
            raise "Got a status #{status} [#{response_content}]"
          else
            @logger.debug('IndexerWorker') { "Indexation OK [#{response_content}]" }
            'OK'
          end
        else
          process_error(response)
        end
      end

      def process_error(response)
        @logger.debug('IndexerWorker') { "Got an error [#{response.response_code}] [#{response.status_message}]" }
        raise "Error: [#{response.response_code}] [#{response.status_message}]"
      end

    end
  end
end
