# frozen_string_literal: true

module ActsAsFavoritor
  module Favoritable
    def self.included(base)
      base.extend(ClassMethods)
    end

    module ClassMethods
      def acts_as_favoritable
        if ActsAsFavoritor.configuration&.cache
          serialize :favoritable_score, type: Hash
          serialize :favoritable_total, type: Hash
        end

        has_many :favorited, as: :favoritable, dependent: :destroy,
                             class_name: 'Favorite'

        extend ActsAsFavoritor::FavoritorLib
        include ActsAsFavoritor::Favoritable::InstanceMethods
      end
    end

    module InstanceMethods
      # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
      def method_missing(method, *args)
        if method.to_s[/(.+)_favoritors/]
          favoritors_by_type($1.singularize.classify)
        elsif ActsAsFavoritor.configuration.cache &&
              method.to_s[/favoritable_(.+)_score/]
          favoritable_score[$1.singularize.classify]
        elsif ActsAsFavoritor.configuration.cache &&
              method.to_s[/favoritable_(.+)_total/]
          favoritable_total[$1.singularize.classify]
        else
          super
        end
      end
      # rubocop:enable Metrics/AbcSize, Metrics/MethodLength

      def respond_to_missing?(method, include_private = false)
        super || method.to_s[/(.+)_favoritors/] ||
          method.to_s[/favoritable_(.+)_score/] ||
          method.to_s[/favoritable_(.+)_total/]
      end

      def favoritors(scope: ActsAsFavoritor.configuration.default_scope,
                     scopes: nil)
        self.class.build_result_for_scopes(scopes || scope) do |s|
          favorited.includes(:favoritor).unblocked.send("#{s}_list")
                   .map(&:favoritor)
        end
      end

      def favoritors_by_type(favoritor_type,
                             scope: ActsAsFavoritor.configuration.default_scope,
                             scopes: nil)
        self.class.build_result_for_scopes(scopes || scope) do |s|
          favoritor_type.constantize.includes(:favorites)
                        .where(favorites: {
                                 blocked: false, favoritable_id: id,
                                 favoritable_type: self.class.name, scope: s
                               })
        end
      end

      def favorited_by?(favoritor,
                        scope: ActsAsFavoritor.configuration.default_scope,
                        scopes: nil)
        self.class.build_result_for_scopes(scopes || scope) do |s|
          favorited.unblocked.send("#{s}_list").for_favoritor(favoritor)
                   .first.present?
        end
      end

      def block(favoritor, scope: ActsAsFavoritor.configuration.default_scope,
                scopes: nil)
        self.class.build_result_for_scopes(scopes || scope) do |s|
          get_favorite_for(favoritor, s)&.block! || Favorite.create(
            favoritable: self,
            favoritor: favoritor,
            blocked: true,
            scope: scope
          )
        end
      end

      def unblock(favoritor, scope: ActsAsFavoritor.configuration.default_scope,
                  scopes: nil)
        self.class.build_result_for_scopes(scopes || scope) do |s|
          get_favorite_for(favoritor, s)&.update(blocked: false)
        end
      end

      def blocked?(favoritor,
                   scope: ActsAsFavoritor.configuration.default_scope,
                   scopes: nil)
        self.class.build_result_for_scopes(scopes || scope) do |s|
          favorited.blocked.send("#{s}_list").for_favoritor(favoritor).first
                   .present?
        end
      end

      def blocked(scope: ActsAsFavoritor.configuration.default_scope,
                  scopes: nil)
        self.class.build_result_for_scopes(scopes || scope) do |s|
          favorited.includes(:favoritor).blocked.send("#{s}_list")
                   .map(&:favoritor)
        end
      end

      private

      def get_favorite_for(favoritor, scope)
        favorited.send("#{scope}_list").for_favoritor(favoritor).first
      end
    end
  end
end
