require 'sanitize'

module Locomotive
  module Steam

    class ContentEntryService

      include Locomotive::Steam::Services::Concerns::Decorator

      attr_accessor_initialize :content_type_repository, :repository, :locale

      def all(type_slug, conditions = {}, as_json = false)
        with_repository(type_slug) do |_repository|
          _repository.all(conditions).map do |entry|
            _decorate(entry, as_json)
          end
        end
      end

      def find(type_slug, id_or_slug, as_json = false)
        with_repository(type_slug) do |_repository|
          entry = _repository.by_slug(id_or_slug) || _repository.find(id_or_slug)
          _decorate(entry, as_json)
        end
      end

      # Warning: do not work with localized and file fields
      def create(type_slug, attributes, as_json = false)
        with_repository(type_slug) do |_repository|
          entry = _repository.build(clean_attributes(attributes))

          yield(entry) if block_given?

          decorated_entry = i18n_decorate { entry }

          if validate(_repository, decorated_entry)
            _repository.create(entry)
          end

          logEntryOperation(type_slug, decorated_entry)

          _json_decorate(decorated_entry, as_json)
        end
      end

      # Warning: do not work with localized and file fields
      def update(type_slug, id_or_slug, attributes, as_json = false)
        with_repository(type_slug) do |_repository|
          entry = _repository.by_slug(id_or_slug) || _repository.find(id_or_slug)
          decorated_entry = i18n_decorate { entry.change(clean_attributes(attributes)) }

          if validate(_repository, decorated_entry)
            _repository.update(entry)
          end

          logEntryOperation(type_slug, decorated_entry)

          _json_decorate(decorated_entry, as_json)
        end
      end

      def update_decorated_entry(decorated_entry, attributes)
        with_repository(decorated_entry.content_type) do |_repository|
          entry = decorated_entry.__getobj__

          entry.change(clean_attributes(attributes))

          _repository.update(entry)

          logEntryOperation(decorated_entry.content_type.slug, decorated_entry)

          decorated_entry
        end
      end

      def delete(type_slug, id_or_slug)
        with_repository(type_slug) do |_repository|
          entry = _repository.by_slug(id_or_slug) || _repository.find(id_or_slug)
          _repository.delete(entry)
        end
      end

      def get_type(slug)
        return nil if slug.blank?

        content_type_repository.by_slug(slug)
      end

      def logger
        Locomotive::Common::Logger
      end

      private

      def logEntryOperation(type_slug, entry)
        if (json = entry.as_json)['errors'].blank?
          logger.info "[#{type_slug}] Entry persisted with success. #{json}"
        else
          logger.error "[#{type_slug}] Failed to persist entry. #{json}"
        end
      end

      def with_repository(type_or_slug)
        type = type_or_slug.respond_to?(:fields) ? type_or_slug : get_type(type_or_slug)

        return if type.nil?

        yield(repository.with(type))
      end

      def _decorate(entry, as_json)
        decorated_entry = i18n_decorate { entry }
        _json_decorate(decorated_entry, as_json)
      end

      def _json_decorate(entry, as_json)
        as_json ? entry.as_json : entry
      end

      def clean_attributes(attributes)
        attributes.each do |key, value|
          next unless value.is_a?(String)
          attributes[key] = Sanitize.clean(value, Sanitize::Config::BASIC)
        end
        attributes
      end

      def validate(_repository, entry)
        # simple validations (existence of values) first
        entry.valid?

        # check if the entry has unique values for its
        # fields marked as unique
        content_type_repository.look_for_unique_fields(entry.content_type).each do |name, _|
          if _repository.exists?(name => entry.send(name))
            entry.errors.add(name, :taken)
          end
        end

        entry.errors.empty?
      end

    end

  end
end

