# frozen_string_literal: true

require "spec_helper"

describe BookmarkSearchForm, bookmark_search: true do
  describe "options" do
    it "includes flags set to false" do
      bsf = BookmarkSearchForm.new(show_restricted: false, show_private: false)
      expect(bsf.options).to include(show_restricted: false)
      expect(bsf.options).to include(show_private: false)
    end
  end

  describe "bookmarkable_search_results" do
    describe "sorting" do
      context "when everything is created at a different time" do
        let(:tag) { create(:canonical_fandom) }

        let!(:work1) do
          travel_to(40.minutes.ago) do
            create(:work, title: "One", fandom_string: tag.name)
          end
        end

        let!(:work2) do
          travel_to(60.minutes.ago) do
            create(:work, title: "Two", fandom_string: tag.name)
          end
        end

        let!(:work3) do
          travel_to(50.minutes.ago) do
            create(:work, title: "Three", fandom_string: tag.name)
          end
        end

        let!(:bookmark1) do
          travel_to(30.minutes.ago) do
            create(:bookmark, bookmarkable: work1)
          end
        end

        let!(:bookmark2) do
          travel_to(10.minutes.ago) do
            create(:bookmark, bookmarkable: work2)
          end
        end

        let!(:bookmark3) do
          travel_to(20.minutes.ago) do
            create(:bookmark, bookmarkable: work3)
          end
        end

        before { run_all_indexing_jobs }

        context "by Date Updated" do
          it "returns bookmarkables in the correct order" do
            results = BookmarkSearchForm.new(
              parent: tag, sort_column: "bookmarkable_date"
            ).bookmarkable_search_results
            expect(results.map(&:title)).to eq %w[One Three Two]
          end

          it "changes when the work is updated" do
            work2.update_attribute(:revised_at, Time.current)
            run_all_indexing_jobs
            results = BookmarkSearchForm.new(
              parent: tag, sort_column: "bookmarkable_date"
            ).bookmarkable_search_results
            expect(results.map(&:title)).to eq %w[Two One Three]
          end
        end

        context "by Date Bookmarked" do
          it "returns bookmarkables in the correct order" do
            results = BookmarkSearchForm.new(
              parent: tag, sort_column: "created_at"
            ).bookmarkable_search_results
            expect(results.map(&:title)).to eq %w[Two Three One]
          end

          it "changes when a new bookmark is created" do
            create(:bookmark, bookmarkable: work1)
            run_all_indexing_jobs
            results = BookmarkSearchForm.new(
              parent: tag, sort_column: "created_at"
            ).bookmarkable_search_results
            expect(results.map(&:title)).to eq %w[One Two Three]
          end
        end
      end

      context "when everything is created and updated at the same time" do
        before { freeze_time }

        let(:tag) { create(:canonical_fandom) }
        let!(:work1) { create(:work, fandom_string: tag.name) }
        let!(:work2) { create(:work, fandom_string: tag.name) }
        let!(:bookmark1) { create(:bookmark, bookmarkable: work1) }
        let!(:bookmark2) { create(:bookmark, bookmarkable: work2) }

        context "doesn't change tied bookmarkables order on work update" do
          it "when sorted by Date Updated" do
            search = BookmarkSearchForm.new(
              parent: tag, sort_column: "bookmarkable_date"
            )
            run_all_indexing_jobs
            res = search.bookmarkable_search_results.map(&:id)

            [work1, work2].each do |work|
              work.update!(summary: "Updated")
              run_all_indexing_jobs
              expect(search.bookmarkable_search_results.map(&:id)).to eq(res)
            end
          end

          it "when sorted by Date Bookmarked" do
            run_all_indexing_jobs
            search = BookmarkSearchForm.new(
              parent: tag, sort_column: "created_at"
            )
            run_all_indexing_jobs
            res = search.bookmarkable_search_results.map(&:id)

            [work1, work2].each do |work|
              work.update!(summary: "Updated")
              run_all_indexing_jobs
              expect(search.bookmarkable_search_results.map(&:id)).to eq(res)
            end
          end
        end
      end
    end

    describe "searching" do
      context "by work language" do
        let(:language) { create(:language, short: "ptBR") }

        let(:work1) { create(:work) }
        let(:work2) { create(:work, language_id: language.id) }

        let!(:bookmark1) { create(:bookmark, bookmarkable: work1) }
        let!(:bookmark2) { create(:bookmark, bookmarkable: work2) }

        let(:unused_language) { create(:language, name: "unused", short: "tlh") }

        before { run_all_indexing_jobs }

        it "returns work bookmarkables with specified language" do
          # "Work language" dropdown, with short names
          results = BookmarkSearchForm.new(language_id: "ptBR").bookmarkable_search_results
          expect(results).not_to include work1
          expect(results).to include work2

          # "Work language" dropdown, with IDs (backward compatibility)
          bsf = BookmarkSearchForm.new(language_id: language.id)
          expect(bsf.language_id).to eq("ptBR")
          results = bsf.bookmarkable_search_results
          expect(results).not_to include work1
          expect(results).to include work2

          # "Any field on work" or "Search within results", with short names
          results = BookmarkSearchForm.new(bookmarkable_query: "language_id: ptBR").bookmarkable_search_results
          expect(results).not_to include work1
          expect(results).to include work2

          # "Any field on work" or "Search within results", with IDs (backward compatibility)
          bsf = BookmarkSearchForm.new(bookmarkable_query: "language_id: #{language.id} OR language_id: #{unused_language.id}")
          expect(bsf.bookmarkable_query).to eq("language_id: ptBR OR language_id: tlh")
          results = bsf.bookmarkable_search_results
          expect(results).not_to include work1
          expect(results).to include work2
        end
      end

      context "using pseud_ids in the bookmarkable query" do
        let(:pseud) { create(:pseud) }
        let(:collection) { create(:collection) }

        let(:work) { create(:work, authors: [pseud], collections: [collection]) }
        let(:series) { create(:series, authors: [pseud], works: [work]) }

        let!(:bookmark1) { create(:bookmark, bookmarkable: work) }
        let!(:bookmark2) { create(:bookmark, bookmarkable: series) }

        before { run_all_indexing_jobs }

        context "when a work & series are anonymous" do
          let(:collection) { create(:anonymous_collection) }

          it "doesn't include the work or the series" do
            results = BookmarkSearchForm.new(bookmarkable_query: "pseud_ids: #{pseud.id}").bookmarkable_search_results
            expect(results).not_to include work
            expect(results).not_to include series
          end
        end

        context "when a work & series are unrevealed" do
          let(:collection) { create(:unrevealed_collection) }

          it "doesn't include the work or the series" do
            results = BookmarkSearchForm.new(bookmarkable_query: "pseud_ids: #{pseud.id}").bookmarkable_search_results
            expect(results).not_to include work
            expect(results).not_to include series
          end
        end

        context "when a work & series are neither unrevealed nor anonymous" do
          it "includes the work and the series" do
            results = BookmarkSearchForm.new(bookmarkable_query: "pseud_ids: #{pseud.id}").bookmarkable_search_results
            expect(results).to include work
            expect(results).to include series
          end
        end
      end

      context "using user_ids in the bookmarkable query" do
        let(:user) { create(:user) }
        let(:collection) { create(:collection) }

        let(:work) { create(:work, authors: [user.default_pseud], collections: [collection]) }
        let(:series) { create(:series, authors: [user.default_pseud], works: [work]) }

        let!(:bookmark1) { create(:bookmark, bookmarkable: work) }
        let!(:bookmark2) { create(:bookmark, bookmarkable: series) }

        before { run_all_indexing_jobs }

        context "when a work & series are anonymous" do
          let(:collection) { create(:anonymous_collection) }

          it "doesn't include the work or the series" do
            results = BookmarkSearchForm.new(bookmarkable_query: "user_ids: #{user.id}").bookmarkable_search_results
            expect(results).not_to include work
            expect(results).not_to include series
          end
        end

        context "when a work & series are unrevealed" do
          let(:collection) { create(:unrevealed_collection) }

          it "doesn't include the work or the series" do
            results = BookmarkSearchForm.new(bookmarkable_query: "user_ids: #{user.id}").bookmarkable_search_results
            expect(results).not_to include work
            expect(results).not_to include series
          end
        end

        context "when a work & series are neither unrevealed nor anonymous" do
          it "includes the work and the series" do
            results = BookmarkSearchForm.new(bookmarkable_query: "user_ids: #{user.id}").bookmarkable_search_results
            expect(results).to include work
            expect(results).to include series
          end
        end
      end
    end
  end

  describe "search_results" do
    describe "sorting" do
      before { freeze_time }

      let!(:work1) { create(:work) }
      let!(:work2) { create(:work) }
      let(:bookmarker) { create(:user) }
      let!(:bookmark1) { create(:bookmark, bookmarkable: work1, pseud: bookmarker.default_pseud) }
      let!(:bookmark2) { create(:bookmark, bookmarkable: work2, pseud: bookmarker.default_pseud) }

      context "doesn't change tied bookmark order on work/bookmark update" do
        %w[created_at bookmarkable_date].each do |sort_column|
          it "when sorted by #{sort_column}" do
            search = BookmarkSearchForm.new(
              bookmarker: bookmarker.default_pseud.name, sort_column: sort_column
            )
            run_all_indexing_jobs
            res = search.search_results.map(&:id)

            [work1, work2].each do |work|
              work.update!(summary: "Updated")
              run_all_indexing_jobs
              expect(search.search_results.map(&:id)).to eq(res)
            end

            [bookmark1, bookmark2].each do |bookmark|
              bookmark.update!(bookmarker_notes: "Updated")
              run_all_indexing_jobs
              expect(search.search_results.map(&:id)).to eq(res)
            end
          end
        end
      end
    end
  end

  describe "when searching by bookmarker" do
    let(:bookmarker) { create(:user, login: "yabalchoath") }

    {
      Work: :work,
      Series: :series,
      ExternalWork: :external_work
    }.each_pair do |type, factory|
      it "returns the correct bookmarked #{type.to_s.pluralize} when bookmarker changes username" do
        bookmarkable = create(factory)
        bookmark = create(:bookmark,
                          bookmarkable_id: bookmarkable.id,
                          bookmarkable_type: type,
                          pseud: bookmarker.default_pseud)
        run_all_indexing_jobs

        result = BookmarkSearchForm.new(bookmarker: "yabalchoath").search_results.first
        expect(result).to eq bookmark

        bookmarker.login = "cioelle"
        bookmarker.save!
        run_all_indexing_jobs

        result = BookmarkSearchForm.new(bookmarker: "yabalchoath").search_results.first
        expect(result).to be_nil
        result = BookmarkSearchForm.new(bookmarker: "cioelle").search_results.first
        expect(result).to eq bookmark
      end
    end
  end

  describe "when searching any bookmarkable field for author of bookmarkable" do
    let(:author) { create(:user, login: "yabalchoath") }

    {
      Work: :work,
      Series: :series
    }.each_pair do |type, factory|
      it "returns the correct bookmarked #{type.to_s.pluralize} when author changes username" do
        bookmarkable = create(factory, authors: [author.default_pseud])
        bookmark = create(:bookmark, bookmarkable_id: bookmarkable.id, bookmarkable_type: type)
        run_all_indexing_jobs

        result = BookmarkSearchForm.new(bookmarkable_query: "yabalchoath").search_results.first
        expect(result).to eq bookmark

        author.login = "cioelle"
        author.save!
        run_all_indexing_jobs

        result = BookmarkSearchForm.new(bookmarkable_query: "yabalchoath").search_results.first
        expect(result).to be_nil
        result = BookmarkSearchForm.new(bookmarkable_query: "cioelle").search_results.first
        expect(result).to eq bookmark
      end
    end
  end

  describe "#processed_options" do
    it "removes blank options" do
      options = { foo: nil, bar: false }
      searcher = BookmarkSearchForm.new(options)
      expect(searcher.options).to have_key(:bar)
      expect(searcher.options).not_to have_key(:foo)
    end

    it "renames the notes field" do
      options = { bookmark_notes: "Mordor" }
      searcher = BookmarkSearchForm.new(options)
      expect(searcher.options[:notes]).to eq("Mordor")
    end

    it "unescapes angle brackets for date fields" do
      options = {
        date: "&lt;1 week ago",
        bookmarkable_date: "&gt;1 year ago",
        title: "escaped &gt;.&lt; field"
      }
      searcher = BookmarkSearchForm.new(options)
      expect(searcher.options[:date]).to eq("<1 week ago")
      expect(searcher.options[:bookmarkable_date]).to eq(">1 year ago")
      expect(searcher.options[:title]).to eq("escaped &gt;.&lt; field")
    end

    it "renames old warning_ids fields" do
      options = { warning_ids: [13] }
      searcher = BookmarkSearchForm.new(options)
      expect(searcher.options[:archive_warning_ids]).to eq([13])
    end
  end

  describe "facets" do
    let(:author) { create(:user).default_pseud }
    let(:bookmarker) { create(:user).default_pseud }

    let(:fandom1) { create(:canonical_fandom) }
    let(:fandom2) { create(:canonical_fandom) }
    let(:fandom3) { create(:fandom, merger: fandom1) }

    let(:character1) { create(:canonical_character) }
    let(:character2) { create(:canonical_character) }
    let(:character3) { create(:canonical_character) }

    let(:freeform1) { create(:canonical_freeform) }
    let(:freeform2) { create(:freeform, merger: freeform1) }

    let(:work1) do
      create(:work,
             fandom_string: fandom1.name,
             character_string: [character1.name, character2.name].join(","),
             authors: [author])
    end

    let(:work2) do
      create(:work,
             fandom_string: fandom2.name,
             character_string: [character1.name, character2.name].join(","),
             authors: [author])
    end

    let(:work3) do
      create(:work,
             fandom_string: fandom3.name,
             character_string: [character1.name, character3.name].join(","),
             authors: [author])
    end

    let!(:bookmark1) { create(:bookmark, pseud: bookmarker, bookmarkable: work1, tag_string: freeform1.name) }
    let!(:bookmark2) { create(:bookmark, pseud: bookmarker, bookmarkable: work2, tag_string: freeform1.name) }
    let!(:bookmark3) { create(:bookmark, pseud: bookmarker, bookmarkable: work3, tag_string: freeform2.name) }

    before { run_all_indexing_jobs }

    let(:form) { BookmarkSearchForm.new(faceted: true) }
    let(:facets) { results.facets }

    def facet_hash(facets)
      facets.to_h { |facet| [facet.name, facet.count] }
    end

    shared_examples "it calculates the correct counts" do
      it "lists the canonical fandoms with their counts" do
        expect(facet_hash(facets["fandom"])).to eq(
          {
            fandom1.name => 2,
            fandom2.name => 1
          }
        )
      end

      it "lists the canonical characters with their counts" do
        expect(facet_hash(facets["character"])).to eq(
          {
            character1.name => 3,
            character2.name => 2,
            character3.name => 1
          }
        )
      end

      it "lists all bookmark tags with their counts" do
        expect(facet_hash(facets["tag"])).to eq(
          {
            freeform1.name => 2,
            freeform2.name => 1
          }
        )
      end

      context "when excluding tags" do
        let(:form) { BookmarkSearchForm.new(faceted: true, excluded_tag_ids: [fandom2.id]) }

        it "lists the canonical fandoms with their counts" do
          expect(facet_hash(facets["fandom"])).to eq(
            {
              fandom1.name => 2
            }
          )
        end

        it "lists the canonical characters with their counts" do
          expect(facet_hash(facets["character"])).to eq(
            {
              character1.name => 2,
              character2.name => 1,
              character3.name => 1
            }
          )
        end

        it "lists all bookmark tags with their counts" do
          expect(facet_hash(facets["tag"])).to eq(
            {
              freeform1.name => 1,
              freeform2.name => 1
            }
          )
        end
      end
    end

    context "for search_results" do
      let(:results) { form.search_results }

      it_behaves_like "it calculates the correct counts"
    end

    context "for bookmarkable_search_results" do
      let(:results) { form.bookmarkable_search_results }

      it_behaves_like "it calculates the correct counts"
    end
  end
end
