# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Account::Search do
  describe '.search_for' do
    before do
      _missing = Fabricate(
        :account,
        display_name: 'Missing',
        username: 'missing',
        domain: 'missing.com'
      )
    end

    it 'does not return suspended users' do
      Fabricate(
        :account,
        display_name: 'Display Name',
        username: 'username',
        domain: 'example.com',
        suspended: true
      )

      results = Account.search_for('username')
      expect(results).to eq []
    end

    it 'does not return unapproved users' do
      match = Fabricate(
        :account,
        display_name: 'Display Name',
        username: 'username'
      )

      match.user.update(approved: false)

      results = Account.search_for('username')
      expect(results).to eq []
    end

    it 'does not return unconfirmed users' do
      match = Fabricate(
        :account,
        display_name: 'Display Name',
        username: 'username'
      )

      match.user.update(confirmed_at: nil)

      results = Account.search_for('username')
      expect(results).to eq []
    end

    it 'accepts ?, \, : and space as delimiter' do
      match = Fabricate(
        :account,
        display_name: 'A & l & i & c & e',
        username: 'username',
        domain: 'example.com'
      )

      results = Account.search_for('A?l\i:c e')
      expect(results).to eq [match]
    end

    it 'finds accounts with matching display_name' do
      match = Fabricate(
        :account,
        display_name: 'Display Name',
        username: 'username',
        domain: 'example.com'
      )

      results = Account.search_for('display')
      expect(results).to eq [match]
    end

    it 'finds accounts with matching username' do
      match = Fabricate(
        :account,
        display_name: 'Display Name',
        username: 'username',
        domain: 'example.com'
      )

      results = Account.search_for('username')
      expect(results).to eq [match]
    end

    it 'finds accounts with matching domain' do
      match = Fabricate(
        :account,
        display_name: 'Display Name',
        username: 'username',
        domain: 'example.com'
      )

      results = Account.search_for('example')
      expect(results).to eq [match]
    end

    it 'limits via constant by default' do
      stub_const('Account::Search::DEFAULT_LIMIT', 1)
      2.times.each { Fabricate(:account, display_name: 'Display Name') }
      results = Account.search_for('display')
      expect(results.size).to eq 1
    end

    it 'accepts arbitrary limits' do
      2.times.each { Fabricate(:account, display_name: 'Display Name') }
      results = Account.search_for('display', limit: 1)
      expect(results.size).to eq 1
    end

    it 'ranks multiple matches higher' do
      matches = [
        { username: 'username', display_name: 'username' },
        { display_name: 'Display Name', username: 'username', domain: 'example.com' },
      ].map(&method(:Fabricate).curry(2).call(:account))

      results = Account.search_for('username')
      expect(results).to eq matches
    end
  end

  describe '.advanced_search_for' do
    let(:account) { Fabricate(:account) }

    context 'when limiting search to followed accounts' do
      it 'accepts ?, \, : and space as delimiter' do
        match = Fabricate(
          :account,
          display_name: 'A & l & i & c & e',
          username: 'username',
          domain: 'example.com'
        )
        account.follow!(match)

        results = Account.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
        expect(results).to eq [match]
      end

      it 'does not return non-followed accounts' do
        Fabricate(
          :account,
          display_name: 'A & l & i & c & e',
          username: 'username',
          domain: 'example.com'
        )

        results = Account.advanced_search_for('A?l\i:c e', account, limit: 10, following: true)
        expect(results).to eq []
      end

      it 'does not return suspended users' do
        Fabricate(
          :account,
          display_name: 'Display Name',
          username: 'username',
          domain: 'example.com',
          suspended: true
        )

        results = Account.advanced_search_for('username', account, limit: 10, following: true)
        expect(results).to eq []
      end

      it 'does not return unapproved users' do
        match = Fabricate(
          :account,
          display_name: 'Display Name',
          username: 'username'
        )

        match.user.update(approved: false)

        results = Account.advanced_search_for('username', account, limit: 10, following: true)
        expect(results).to eq []
      end

      it 'does not return unconfirmed users' do
        match = Fabricate(
          :account,
          display_name: 'Display Name',
          username: 'username'
        )

        match.user.update(confirmed_at: nil)

        results = Account.advanced_search_for('username', account, limit: 10, following: true)
        expect(results).to eq []
      end
    end

    it 'does not return suspended users' do
      Fabricate(
        :account,
        display_name: 'Display Name',
        username: 'username',
        domain: 'example.com',
        suspended: true
      )

      results = Account.advanced_search_for('username', account)
      expect(results).to eq []
    end

    it 'does not return unapproved users' do
      match = Fabricate(
        :account,
        display_name: 'Display Name',
        username: 'username'
      )

      match.user.update(approved: false)

      results = Account.advanced_search_for('username', account)
      expect(results).to eq []
    end

    it 'does not return unconfirmed users' do
      match = Fabricate(
        :account,
        display_name: 'Display Name',
        username: 'username'
      )

      match.user.update(confirmed_at: nil)

      results = Account.advanced_search_for('username', account)
      expect(results).to eq []
    end

    it 'accepts ?, \, : and space as delimiter' do
      match = Fabricate(
        :account,
        display_name: 'A & l & i & c & e',
        username: 'username',
        domain: 'example.com'
      )

      results = Account.advanced_search_for('A?l\i:c e', account)
      expect(results).to eq [match]
    end

    it 'limits result count by default value' do
      stub_const('Account::Search::DEFAULT_LIMIT', 1)
      2.times { Fabricate(:account, display_name: 'Display Name') }
      results = Account.advanced_search_for('display', account)
      expect(results.size).to eq 1
    end

    it 'accepts arbitrary limits' do
      2.times { Fabricate(:account, display_name: 'Display Name') }
      results = Account.advanced_search_for('display', account, limit: 1)
      expect(results.size).to eq 1
    end

    it 'ranks followed accounts higher' do
      match = Fabricate(:account, username: 'Matching')
      followed_match = Fabricate(:account, username: 'Matcher')
      Fabricate(:follow, account: account, target_account: followed_match)

      results = Account.advanced_search_for('match', account)
      expect(results).to eq [followed_match, match]
      expect(results.first.rank).to be > results.last.rank
    end
  end
end
