# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

module Elasticsearch
  module Model
    module Adapter

      # An adapter for ActiveRecord-based models
      #
      module ActiveRecord

        Adapter.register self,
                         lambda { |klass| !!defined?(::ActiveRecord::Base) && klass.respond_to?(:ancestors) && klass.ancestors.include?(::ActiveRecord::Base) }

        module Records
          attr_writer :options

          def options
            @options ||= {}
          end

          # Returns an `ActiveRecord::Relation` instance
          #
          def records
            sql_records = klass.where(klass.primary_key => ids)
            sql_records = sql_records.includes(self.options[:includes]) if self.options[:includes]

            # Re-order records based on the order from Elasticsearch hits
            # by redefining `to_a`, unless the user has called `order()`
            #
            sql_records.instance_exec(response.response['hits']['hits']) do |hits|
              ar_records_method_name = :to_a
              ar_records_method_name = :records if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 5

              define_singleton_method(ar_records_method_name) do
                if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::MAJOR >= 4
                  self.load
                else
                  self.__send__(:exec_queries)
                end
                if !self.order_values.present?
                  @records.sort_by { |record| hits.index { |hit| hit['_id'].to_s == record.id.to_s } }
                else
                  @records
                end
              end if self
            end

            sql_records
          end

          # Prevent clash with `ActiveSupport::Dependencies::Loadable`
          #
          def load
            records.__send__(:load)
          end
        end

        module Callbacks

          # Handle index updates (creating, updating or deleting documents)
          # when the model changes, by hooking into the lifecycle
          #
          # @see http://guides.rubyonrails.org/active_record_callbacks.html
          #
          def self.included(base)
            base.class_eval do
              after_commit lambda { __elasticsearch__.index_document  },  on: :create
              after_commit lambda { __elasticsearch__.update_document },  on: :update
              after_commit lambda { __elasticsearch__.delete_document },  on: :destroy
            end
          end
        end

        module Importing

          # Fetch batches of records from the database (used by the import method)
          #
          #
          # @see http://api.rubyonrails.org/classes/ActiveRecord/Batches.html ActiveRecord::Batches.find_in_batches
          #
          def __find_in_batches(options={}, &block)
            query = options.delete(:query)
            named_scope = options.delete(:scope)
            preprocess = options.delete(:preprocess)

            scope = self
            scope = scope.__send__(named_scope) if named_scope
            scope = scope.instance_exec(&query) if query

            scope.find_in_batches(**options) do |batch|
              batch = self.__send__(preprocess, batch) if preprocess
              yield(batch) if batch.present?
            end
          end

          def __transform
            lambda { |model|  { index: { _id: model.id, data: model.__elasticsearch__.as_indexed_json } } }
          end
        end
      end
    end
  end
end
