require 'rails_helper'
require 'yaml'
require 'support/contain_link'

feature 'Searching Notices', type: :feature do
  include SearchHelpers
  include ContainLink

  scenario 'displays the results', search: true do
    create_list(:dmca, 5, title: 'Boy howdy')
    index_changed_instances

    submit_search 'Boy howdy'

    expect(page).to have_css('.result', count: 5)
  end

  scenario 'includes facets', js: true, search: true do
    create(:dmca, :with_facet_data, title: 'Facet this')

    index_changed_instances

    submit_search 'Facet this'

    # These counts match what's generated by :with_facet_data in
    # spec/factories.rb (plus one for an "All" link) and may fail if that code
    # is changed.
    find('a.language_facet').click
    expect(page).to have_css('ol.language_facet li', count: 2)
    find('a.tag_list_facet').click
    expect(page).to have_css('ol.tag_list_facet li', count: 3)
    # This fails because a spurious 'Copyright' topic is showing up and I can't
    # track down why. --ay 5 December 2018
    # expect(page).to have_css('ol.topic_facet li', count: 4)
    find('a.sender_name_facet').click
    expect(page).to have_css('ol.sender_name_facet li', count: 2)
    find('a.principal_name_facet').click
    expect(page).to have_css('ol.principal_name_facet li', count: 2)
    find('a.recipient_name_facet').click
    expect(page).to have_css('ol.recipient_name_facet li', count: 2)
    find('a.submitter_name_facet').click
    expect(page).to have_css('ol.submitter_name_facet li', count: 1)
    find('a.action_taken_facet').click
    expect(page).to have_css('ol.action_taken_facet li', count: 2)
  end

  scenario 'displays correct content when a notice only has a principal' do
    create(:dmca, role_names: %w[principal], title: 'A notice')
    index_changed_instances
    submit_search 'A notice'

    expect(page).not_to have_css('.result', text: 'on behalf of')
    expect(page).not_to have_css('.result', text: '/faceted_search')
  end

  scenario 'includes the relevant notice data', search: true do
    notice = create(
      :dmca,
      role_names: %w[sender principal recipient],
      title: 'A notice',
      date_received: Time.now,
      topics: create_list(:topic, 2)
    )
    on_behalf_of = "#{notice.sender_name} on behalf of #{notice.principal_name}"
    index_changed_instances

    submit_search 'A notice'
    expect(page).to have_link(notice.title, href: notice_path(notice))
    expect(page).to have_css(
      '.result .date-received', text: notice.date_received.to_fs(:simple)
    )
    expect(page).to have_words(on_behalf_of)
    notice.topics.each do |topic|
      expect(page).to have_css('.result .topic', text: topic.name)
    end
  end

  scenario 'includes excerpts', search: true do
    create(:dmca, title: 'foo bar baz')
    index_changed_instances

    submit_search 'foo'

    expect(page).to have_words('foo bar baz')
  end

  scenario 'sanitizes excerpts' do
    create(:dmca, title: '<strong>foo</strong> and <em>bar</em>')
    index_changed_instances

    submit_search 'foo'

    expect(page).not_to have_css(
      'li.excerpt',
      text: '<strong>foo</strong> and <em>bar</em>'
    )
  end

  scenario 'caching respects pagination', cache: true, search: true do
    # Create enough notices to force pagination of results. The concern here
    # is that caching a search result page might inadvertently cause all pages
    # of a search to match the first viewed page - we want to make sure that
    # doesn't happen.
    create_list(:dmca, 3, title: 'paginate me')
    index_changed_instances

    search_for(term: 'paginate me', page: 2, per_page: 1)

    first_page = page.body
    puts first_page

    find('.next a').click

    second_page = page.body
    expect(first_page).not_to eq second_page
  end

  scenario 'displays search terms', search: true do
    create(:dmca, title: 'The Lion King on Youtube')
    index_changed_instances

    submit_search 'awesome blossom'

    expect(page).to have_css("input#search[value='awesome blossom']")
  end

  scenario 'for full-text on a single model', search: true do
    notice = create(:dmca, title: 'The Lion King on Youtube')
    trademark = create(:trademark, title: "Coke - it's the King thing")
    index_changed_instances

    within_search_results_for('king') do
      expect(page).to have_n_results(2)
      expect(page).to have_words(notice.title)
      expect(page).to have_words(trademark.title)
      expect(page.html).to have_excerpt('King', 'The Lion', 'on Youtube')
    end
  end

  scenario 'exact search', search: true do
    notice = create(:dmca, title: 'The Lion King on Youtube')
    notice_2 = create(:dmca, title: 'The Lion King on Vimeo Youtube')
    trademark = create(:trademark, title: "Coke - Lion it's the King thing on Youtube")
    index_changed_instances

    within_search_results_for('"Lion King"') do
      expect(page).to have_n_results(2)
      expect(page).to have_words(notice.title)
      expect(page).to have_words(notice_2.title)
    end

    within_search_results_for('Lion King') do
      expect(page).to have_n_results(3)
      expect(page).to have_words(notice.title)
      expect(page).to have_words(notice_2.title)
      expect(page).to have_words(trademark.title)
    end

    within_search_results_for('"on Youtube"') do
      expect(page).to have_n_results(2)
      expect(page).to have_words(notice.title)
      expect(page).to have_words(trademark.title)
    end

    within_search_results_for('on Youtube') do
      expect(page).to have_n_results(3)
      expect(page).to have_words(notice.title)
      expect(page).to have_words(notice_2.title)
      expect(page).to have_words(trademark.title)
    end
  end

  scenario 'based on action taken', search: true do
    notices = [
      create(:dmca, action_taken: 'No'),
      create(:dmca, action_taken: 'Yes'),
      create(:dmca, action_taken: 'Partial')
    ]
    index_changed_instances

    notices.each do |notice|
      search_for(action_taken: notice.action_taken)

      expect(page).to have_n_results(1)
      expect(page).to have_words(notice.title)
    end
  end

  scenario 'paginates properly', search: true do
    3.times do
      create(:dmca, title: 'The Lion King on Youtube')
    end
    index_changed_instances

    search_for(term: 'lion', page: 2, per_page: 1)

    within('.pagination') do
      expect(page).to have_css('.current', text: 2)
      expect(page).to have_css('a[rel="next"]')
      expect(page).to have_css('a[rel="prev"]')
    end
  end

  context 'within associated models' do
    scenario 'for topic names', search: true do
      topic = create(:topic, name: 'Lion King')
      notice = create(:dmca, topics: [topic])
      index_changed_instances

      within_search_results_for('king') do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page).to have_words(topic.name)
        expect(page).to contain_link(topic_path(topic))
        expect(page.html).to have_excerpt('King', 'Lion')
      end
    end

    scenario 'for tags', search: true do
      notice = create(:dmca, tag_list: 'foo, bar')
      index_changed_instances

      within_search_results_for('bar') do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page.html).to have_excerpt('bar')
      end
    end

    scenario 'for entities', search: true do
      notice = create(:dmca, role_names: %w[sender principal recipient])
      index_changed_instances

      within_search_results_for(notice.recipient_name) do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page).to have_words(notice.recipient_name)
        expect(page.html).to have_excerpt('Entity')
      end

      within_search_results_for(notice.sender_name) do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page).to have_words(notice.sender_name)
        expect(page.html).to have_excerpt('Entity')
      end

      within_search_results_for(notice.principal_name) do
        # note: principal name not shown in results
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page.html).to have_excerpt('Entity')
      end
    end

    scenario 'for works', search: true do
      work = Work.new(
        description: 'An arbitrary description'
      )

      notice = create(:dmca, works: [work])
      index_changed_instances

      within_search_results_for('arbitrary') do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page).to have_words(work.description)
        expect(page.html).to have_excerpt('arbitrary', 'An', 'description')
      end
    end

    scenario 'for redacted works', search: true do
      # Sensitive content should neither display nor be searchable.
      work1 = Work.new(
        description: 'My SSN is not 123-45-6789'
      )
      work2 = Work.new(
        description: 'My phone number is not (123) 456-7890'
      )
      work3 = Work.new(
        description: 'My email address is not me@example.com'
      )
      notice = create(:dmca, works: [work1, work2, work3])
      index_changed_instances

      within_search_results_for('My SSN') do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page).to have_words('[REDACTED]')
        expect(page).not_to have_words('123-45-6789')
      end

      within_search_results_for('123-45-6789') do
        expect(page).to have_n_results(0)
      end

      within_search_results_for('My phone number') do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page).to have_words('[REDACTED]')
        expect(page).not_to have_words('(123) 456-7890')
      end

      within_search_results_for('(123) 456-7890') do
        expect(page).to have_n_results(0)
      end

      within_search_results_for('My email') do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page).to have_words('[REDACTED]')
        expect(page).not_to have_words('me@example.com')
      end

      within_search_results_for('me@example.com') do
        expect(page).to have_n_results(0)
      end
    end

    scenario 'for works with redacted URLs', search: true do
      i_url = InfringingUrl.new(
        url: 'https://example.com',
        url_original: 'https://totes.redacted'
      )

      i_url_2 = InfringingUrl.new(
        url: 'https://redacted.example.com/hey_i_need_to_be_redacted',
        url_original: 'https://redacted.example.com/hey_i_need_to_be_redacted'
      )

      i_url_3 = InfringingUrl.new(
        url: 'https://gogo-team.harvard.edu/something/something.pdf',
        url_original: 'https://gogo-team.harvard.edu/something/something.pdf',
      )

      c_url = CopyrightedUrl.new(
        url: 'https://foo.bar',
        url_original: 'https://sharklasers.com'
      )

      work1 = Work.new(infringing_urls: [i_url, i_url_2, i_url_3])
      work2 = Work.new(copyrighted_urls: [c_url])
      notice = create(:dmca, works: [work1, work2])
      index_changed_instances

      within_search_results_for('totes') do
        expect(page).to have_n_results(0)
      end

      within_search_results_for('example.com') do
        expect(page).to have_n_results(1)
        expect(page).not_to have_words('totes')
      end

      within_search_results_for('sharklasers') do
        expect(page).to have_n_results(0)
      end

      within_search_results_for('foo.bar') do
        expect(page).to have_n_results(1)
        expect(page).not_to have_words('sharklasers')
      end

      within_search_results_for('https') do
        expect(page).not_to have_words('hey_i_need_to_be_redacted')
        expect(page).to have_words('https://redacted.example.com/[REDACTED]')
      end

      within_search_results_for('harvard.edu') do
        expect(page).to have_n_results(1)
      end

      within_search_results_for('foo') do
        expect(page).to have_n_results(1)
        expect(page).not_to have_words('sharklasers')
      end
    end

    scenario 'for urls associated through works', search: true do
      work = Work.new(
        infringing_urls: [
          InfringingUrl.new(url: 'http://example1.com/infringing_url')
        ],
        copyrighted_urls: [
          CopyrightedUrl.new(url: 'http://example2.com/copyrighted_url')
        ]
      )

      notice = create(:dmca, works: [work])
      index_changed_instances

      # Redacted for users with no access
      within_search_results_for('infringing_url') do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page.html).to have_content('http://example1.com/[REDACTED]')
      end

      within_search_results_for('copyrighted_url') do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page.html).to have_content('http://example2.com/[REDACTED]')
      end

      user = create(:user, :admin)
      sign_in(user)

      # Not redacted for users with access
      within_search_results_for('infringing_url') do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page.html).to have_excerpt('infringing')
      end

      within_search_results_for('copyrighted_url') do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
        expect(page.html).to have_excerpt('copyrighted')
      end
    end
  end

  context 'changes to associated models' do
    scenario 'a topic is created', search: true do
      notice = create(:dmca)
      notice.topics.create!(name: 'arbitrary')
      index_changed_instances

      within_search_results_for('arbitrary') do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
      end
    end

    scenario 'a topic is destroyed', search: true do
      topic = create(:topic, name: 'arbitrary')
      notice = create(:dmca, topics: [topic])
      topic.destroy
      index_changed_instances

      expect_search_to_not_find('arbitrary', notice)
    end

    scenario 'a topic updates its name', search: true do
      topic = create(:topic, name: 'something')
      notice = create(:dmca, topics: [topic])
      topic.update!(name: 'arbitrary')
      index_changed_instances

      within_search_results_for('arbitrary') do
        expect(page).to have_n_results(1)
        expect(page).to have_words(notice.title)
      end
    end
  end

  scenario 'advanced search on multiple fields', search: true do
    create_notice_with_entities("Jim & Jon's", 'Jim', 'Jon')
    create_notice_with_entities("Jim & Dan's", 'Jim', 'Dan')
    create_notice_with_entities("Dan & Jon's", 'Dan', 'Jon')
    index_changed_instances

    search_for(sender_name: 'Jim', recipient_name: 'Jon')

    expect(page).to have_words("Jim & Jon's")
    expect(page).to have_no_content("Jim & Dan's")
    expect(page).to have_no_content("Dan & Jon's")
  end

  scenario 'searching with a blank parameter', search: true do
    expect { submit_search('') }.not_to raise_error
  end

  scenario 'cache does not break date filter', cache: true, js: true do
    last_year = Time.at(
      Time.now.beginning_of_day - 12.months
    ).to_datetime.to_fs(:simple)
    last_month = Time.at(
      Time.now.beginning_of_day - 1.month
    ).to_datetime.to_fs(:simple)

    create(:dmca, title: 'Ancient History', date_received: 100.days.ago)
    create(:dmca, title: 'Modern History', date_received: 1.day.ago)
    index_changed_instances

    search_for(term: 'history')
    find('a.date_received_facet').click
    expect(
      find('a', text: "Since #{last_year}").find('span').text
    ).to eq '2 Results'
    expect(
      find('a', text: "Since #{last_month}").find('span').text
    ).to eq '1 Results'

    search_for(term: 'ancient')
    find('a.date_received_facet').click
    expect(
      find('a', text: "Since #{last_year}").find('span').text
    ).to eq '1 Results'
    expect(
      find('a', text: "Since #{last_month}").find('span').text
    ).to eq '0 Results'

    search_for(term: 'modern')
    find('a.date_received_facet').click
    expect(
      find('a', text: "Since #{last_year}").find('span').text
    ).to eq '1 Results'
    expect(
      find('a', text: "Since #{last_month}").find('span').text
    ).to eq '1 Results'
  end

  scenario 'respects criteria which should suppress notices' do
    rescinded = create(:dmca, title: 'rescinded', rescinded: true)
    hidden = create(:dmca, title: 'hidden', hidden: true)
    spam = create(:dmca, title: 'spam', spam: true)
    unpublished = create(:dmca, title: 'unpublished', published: false)
    index_changed_instances

    expect_search_to_not_find('rescinded', rescinded)
    expect_search_to_not_find('hidden', hidden)
    expect_search_to_not_find('spam', spam)
    expect_search_to_not_find('unpublished', unpublished)
  end

  scenario 'updates results when notice changes', search: true do
    notice = create(:dmca, title: 'Old title')

    index_changed_instances

    submit_search 'title'
    expect(page).to have_n_results(1)
    expect(page).to have_words('Old title')

    notice.title = 'New title'
    notice.save!

    index_changed_instances

    submit_search 'title'
    expect(page).to have_n_results(1)
    expect(page).to have_words('New title')
    expect(page).not_to have_words('Old title')
  end

  private

  def expect_search_to_not_find(term, notice)
    submit_search(term)

    expect(page).to have_no_content(notice.title)

    yield if block_given?
  end

  # TODO split on punctuation, then rejoin on punctuation with highlights,
  # because analyzer. or make a different excerpter for URLs. Or fix the URL
  # analyzer.
  def have_excerpt(excerpt, prefix = nil, suffix = nil)
    include([prefix, "<em>#{excerpt}</em>", suffix].compact.join(' '))
  end

  def create_notice_with_entities(title, sender_name, recipient_name)
    sender = Entity.find_or_create_by(name: sender_name)

    dmca = create(:dmca, title: title).tap do |notice|
      create(
        :entity_notice_role,
        name: 'sender',
        notice: notice,
        entity: sender
      )
    end
    # A recipient is created by the factory as it's necessary to validate the
    # notice. We need to switch the notice to our preferred recipient.
    dmca.recipient.update(name: recipient_name)
  end
end
