app/controllers/api/v1/search_controller.rb
# frozen_string_literal: true

module Api
  module V1
    class SearchController < Api::V1::BaseController
      USER_FILTER_CONTACTS = "contacts"
      USER_FILTER_RECEIVING_CONTACTS = "contacts:receiving"
      USER_FILTER_SHARING_CONTACTS = "contacts:sharing"
      USER_FILTER_ASPECTS_PREFIX = "aspect:"
      USER_FILTERS_EXACT_MATCH = [USER_FILTER_CONTACTS, USER_FILTER_RECEIVING_CONTACTS,
                                  USER_FILTER_SHARING_CONTACTS].freeze
      USER_FILTERS_PREFIX_MATCH = [USER_FILTER_ASPECTS_PREFIX].freeze

      before_action do
        require_access_token %w[public:read]
      end

      rescue_from RuntimeError do |e|
        render_error 422, e.message
      end

      def user_index
        user_page = index_pager(people_query).response
        user_page[:data] = user_page[:data].map {|p| PersonPresenter.new(p).as_api_json }
        render_paged_api_response user_page
      end

      def post_index
        posts_page = time_pager(posts_query, "posts.created_at", "created_at").response
        posts_page[:data] = posts_page[:data].map {|post| PostPresenter.new(post).as_api_response }
        render_paged_api_response posts_page
      end

      def tag_index
        tags_page = index_pager(tags_query).response
        tags_page[:data] = tags_page[:data].pluck(:name)
        render_paged_api_response tags_page
      end

      private

      def time_pager(query, query_time_field, data_time_field)
        Api::Paging::RestPaginatorBuilder.new(query, request).time_pager(params, query_time_field, data_time_field)
      end

      def people_query
        tag = params[:tag]
        name_or_handle = params[:name_or_handle]
        raise "Parameters tag and name_or_handle are exclusive" if tag.present? && name_or_handle.present?

        query = if tag.present?
                  # scope filters to only searchable people already
                  Person.profile_tagged_with(tag)
                elsif name_or_handle.present?
                  Person.searchable(contacts_read? && current_user) # rubocop:disable Rails/DynamicFindBy
                        .find_by_substring(name_or_handle)
                else
                  raise "Missing parameter tag or name_or_handle"
                end

        query = query.where(closed_account: false)

        user_filters.each do |filter|
          query = query.contacts_of(current_user) if filter == USER_FILTER_CONTACTS

          if filter == USER_FILTER_RECEIVING_CONTACTS
            query = query.contacts_of(current_user).where(contacts: {receiving: true})
          end

          if filter == USER_FILTER_SHARING_CONTACTS
            query = query.contacts_of(current_user).where(contacts: {sharing: true})
          end

          if filter.start_with?(USER_FILTER_ASPECTS_PREFIX) # rubocop:disable Style/Next
            _, ids = filter.split(":", 2)
            ids = ids.split(",").map {|id|
              Integer(id) rescue raise("Invalid aspect filter") # rubocop:disable Style/RescueModifier
            }

            raise "Invalid aspect filter" unless current_user.aspects.where(id: ids).count == ids.size

            query = Person.where(id: query.all_from_aspects(ids, current_user).select(:id))
          end
        end

        query.distinct
      end

      def posts_query
        opts = {}
        opts[:public_only] = !private_read?
        Stream::Tag.new(current_user, params.require(:tag), opts).stream_posts
      end

      def tags_query
        ActsAsTaggableOn::Tag.autocomplete(params.require(:query))
      end

      def user_filters
        @user_filters ||= Array(params[:filter]).uniq.tap do |filters|
          raise "Invalid filter" unless filters.all? {|filter|
            USER_FILTERS_EXACT_MATCH.include?(filter) ||
            USER_FILTERS_PREFIX_MATCH.any? {|prefix| filter.start_with?(prefix) }
          }

          # For now all filters require contacts:read
          require_access_token %w[contacts:read] unless filters.empty?
        end
      end

      def contacts_read?
        access_token? %w[contacts:read]
      end
    end
  end
end
