# frozen_string_literal: true

RSpec.describe Jobs::SummariesBackfill do
  fab!(:topic) do
    Fabricate(:topic, word_count: 200, highest_post_number: 2, last_posted_at: 2.hours.ago)
  end
  let(:limit) { 24 } # guarantee two summaries per batch
  let(:intervals) { 12 } # budget is split into intervals. Job runs every five minutes.

  before { DiscourseAi::Completions::Endpoints::Fake.delays = [] }
  after { DiscourseAi::Completions::Endpoints::Fake.reset! }

  before do
    assign_fake_provider_to(:ai_summarization_model)
    SiteSetting.ai_summarization_enabled = true
    SiteSetting.ai_summary_backfill_maximum_topics_per_hour = limit
    SiteSetting.ai_summary_gists_enabled = true
  end

  describe "#current_budget" do
    let(:type) { AiSummary.summary_types[:complete] }

    context "when no summary has been backfilled yet" do
      it "returns the full budget" do
        expect(subject.current_budget(type)).to eq(limit / intervals)
      end

      it "ignores summaries generated by users" do
        Fabricate(:ai_summary, target: topic, origin: AiSummary.origins[:human])

        expect(subject.current_budget(type)).to eq(limit / intervals)
      end

      it "only accounts for summaries of the given type" do
        Fabricate(:topic_ai_gist, target: topic, origin: AiSummary.origins[:human])

        expect(subject.current_budget(type)).to eq(limit / intervals)
      end
    end
  end

  describe "#backfill_candidates" do
    let(:type) { AiSummary.summary_types[:complete] }

    it "only selects posts with enough words" do
      topic.update!(word_count: 100)

      expect(subject.backfill_candidates(type)).to be_empty
    end

    it "ignores up to date summaries" do
      Fabricate(:ai_summary, target: topic, highest_target_number: 2, updated_at: 10.minutes.ago)

      expect(subject.backfill_candidates(type)).to be_empty
    end

    it "ignores outdated summaries updated less than five minutes ago" do
      Fabricate(:ai_summary, target: topic, highest_target_number: 1, updated_at: 4.minutes.ago)

      expect(subject.backfill_candidates(type)).to be_empty
    end

    it "orders candidates by topic#last_posted_at" do
      topic.update!(last_posted_at: 1.minute.ago)
      topic_2 = Fabricate(:topic, word_count: 200, last_posted_at: 2.minutes.ago)

      expect(subject.backfill_candidates(type).map(&:id)).to contain_exactly(topic.id, topic_2.id)
    end

    it "prioritizes topics without summaries" do
      topic_2 =
        Fabricate(:topic, word_count: 200, last_posted_at: 2.minutes.ago, highest_post_number: 1)
      topic.update!(last_posted_at: 1.minute.ago)
      Fabricate(:ai_summary, target: topic, updated_at: 1.hour.ago, highest_target_number: 1)

      expect(subject.backfill_candidates(type).map(&:id)).to contain_exactly(topic_2.id, topic.id)
    end

    it "respects max age setting" do
      SiteSetting.ai_summary_backfill_topic_max_age_days = 1
      topic.update!(last_posted_at: 2.days.ago)

      expect(subject.backfill_candidates(type)).to be_empty
    end
  end

  describe "#execute" do
    it "backfills a batch" do
      topic_2 =
        Fabricate(:topic, word_count: 200, last_posted_at: 2.minutes.ago, highest_post_number: 1)
      topic.update!(last_posted_at: 1.minute.ago)
      Fabricate(:ai_summary, target: topic, updated_at: 3.hours.ago, highest_target_number: 1)
      Fabricate(:topic_ai_gist, target: topic, updated_at: 3.hours.ago, highest_target_number: 1)

      summary_1 = "Summary of topic_2"
      gist_1 = "Gist of topic_2"
      summary_2 = "Updated summary of topic"
      gist_2 = "Updated gist of topic"

      DiscourseAi::Completions::Llm.with_prepared_responses(
        [gist_1, gist_2, summary_1, summary_2],
      ) { subject.execute({}) }

      expect(AiSummary.complete.find_by(target: topic_2).summarized_text).to eq(summary_1)
      expect(AiSummary.gist.find_by(target: topic_2).summarized_text).to eq(gist_1)
      expect(AiSummary.complete.find_by(target: topic).summarized_text).to eq(summary_2)
      expect(AiSummary.gist.find_by(target: topic).summarized_text).to eq(gist_2)

      # Queue has to be empty if we just generated all summaries
      expect(subject.backfill_candidates(AiSummary.summary_types[:complete])).to be_empty
      expect(subject.backfill_candidates(AiSummary.summary_types[:gist])).to be_empty

      # Queue still empty when they are up to date and time passes.
      AiSummary.update_all(updated_at: 20.minutes.ago)
      expect(subject.backfill_candidates(AiSummary.summary_types[:complete])).to be_empty
      expect(subject.backfill_candidates(AiSummary.summary_types[:gist])).to be_empty
    end

    it "updates the highest_target_number if the summary turned to be up to date" do
      og_highest_post_number = topic.highest_post_number
      existing_summary =
        Fabricate(
          :ai_summary,
          target: topic,
          updated_at: 3.hours.ago,
          highest_target_number: og_highest_post_number,
        )
      topic.update!(highest_post_number: og_highest_post_number + 1)

      # No prepared responses here. We don't perform a completion call.
      subject.execute({})

      expect(existing_summary.reload.highest_target_number).to eq(og_highest_post_number + 1)
    end
  end
end
