module ActiveGraph
  # This is here to support the removed functionality of being able to
  # defined indexes and constraints on models
  # This code should be removed later
  module ModelSchema
    MODEL_INDEXES = {}
    MODEL_CONSTRAINTS = {}
    REQUIRED_INDEXES = {}

    class << self
      def add_defined_constraint(model, property_name)
        MODEL_CONSTRAINTS[model] ||= Set.new
        MODEL_CONSTRAINTS[model] << property_name.to_sym
      end

      def add_defined_index(model, property_name)
        MODEL_INDEXES[model] ||= Set.new
        MODEL_INDEXES[model] << property_name.to_sym
      end

      def add_required_index(model, property_name)
        REQUIRED_INDEXES[model] ||= Set.new
        REQUIRED_INDEXES[model] << property_name.to_sym
      end

      def defined_constraint?(model, property_name)
        MODEL_CONSTRAINTS[model] &&
          MODEL_CONSTRAINTS[model].include?(property_name.to_sym)
      end

      def model_constraints
        return @model_constraints if @model_constraints

        constraints = ActiveGraph::Base.constraints.each_with_object({}) do |row, result|
          result[row[:label]] ||= []
          result[row[:label]] << row[:properties]
        end

        @model_constraints = schema_elements_list(MODEL_CONSTRAINTS, constraints)
      end

      def model_indexes
        return @model_indexes if @model_indexes

        indexes = ActiveGraph::Base.indexes.each_with_object({}) do |row, result|
          result[row[:label]] ||= []
          result[row[:label]] << row[:properties]
        end

        @model_indexes = schema_elements_list(MODEL_INDEXES, indexes) +
                         schema_elements_list(REQUIRED_INDEXES, indexes).reject(&:last)
        # reject required indexes which are already in the DB
      end

      # should be private
      def schema_elements_list(by_model, db_results)
        by_model.flat_map do |model, property_names|
          label = model.mapped_label_name.to_sym
          property_names.map do |property_name|
            exists = db_results[label] && db_results[label].include?([property_name])
            [model, label, property_name, exists]
          end
        end
      end

      def ensure_model_data_state!
        # If we load a new model, reset everything
        if @previously_loaded_models_count != ActiveGraph::Node.loaded_classes.size
          # Make sure we've finalized id_property details and have called
          # add_ constraint/index methods above
          ActiveGraph::Node.loaded_classes.each(&:ensure_id_property_info!)
          reload_models_data!
        end
      end

      def reload_models_data!
        @previously_loaded_models_count = ActiveGraph::Node.loaded_classes.size
        @model_indexes = @model_constraints = nil
      end

      def legacy_model_schema_informations
        ensure_model_data_state!
        data = {index: [], constraint: []}
        each_schema_element do |type, model, label, property_name|
          data[type] << {label: label, property_name: property_name, model: model}
        end
        data
      end

      def validate_model_schema!
        ensure_model_data_state!
        messages = {index: [], constraint: []}
        each_schema_element do |type, model, label, property_name, exists|
          if exists
            log_warning!(type, model, property_name) if model.id_property_name.to_sym != property_name
          else
            messages[type] << force_add_message(type, label, property_name)
          end
        end

        return if messages.values.all?(&:empty?)

        fail ::ActiveGraph::DeprecatedSchemaDefinitionError, validation_error_message(messages)
      end

      def validation_error_message(messages)
        <<MSG
          Some schema elements were defined by the model (which is no longer supported), but they do not exist in the database.  Run the following to create them if you haven't already:

#{messages[:constraint].join("\n")}
#{messages[:index].join("\n")}

And then run `rake neo4j:migrate`

(zshell users may need to escape the brackets)
MSG
      end

      def force_add_message(index_or_constraint, label, property_name)
        "rake neo4j:generate_schema_migration[#{index_or_constraint},#{label},#{property_name}]"
      end

      def log_warning!(index_or_constraint, model, property_name)
        ActiveGraph::Base.logger.warn "WARNING: The #{index_or_constraint} option is no longer supported (Defined on #{model.name} for #{property_name})"
      end

      private

      def each_schema_element
        [[:constraint, model_constraints], [:index, model_indexes]].each do |type, schema_elements|
          schema_elements.each do |args|
            yield(type, *args)
          end
        end
      end
    end
  end
end
