require "rails_helper"
RSpec.describe AbExperiment::GoalConversionHandler do
  include FieldTest::Helpers

  describe ".call" do
    subject(:handler) do
      described_class.new(user: user, goal: goal, experiments: experiments, start_date: start_date)
    end

    let(:user) { create(:user) }
    let(:start_date) { 16.days.ago }
    let(:goal) { "non_sense" }
    let(:experiments) { FieldTest.config["experiments"] }

    context "with no experiments" do
      let(:experiments) { nil }

      it "gracefully handles a case where there are no tests" do
        handler.call
        expect(FieldTest::Event.all.size).to be(0)
      end
    end

    context "with experiment that started to soon for some results" do
      let(:goal) { described_class::USER_CREATES_COMMENT_GOAL }
      let(:experiment_name) { AbExperiment::CURRENT_FEED_STRATEGY_EXPERIMENT }
      let(:start_date) { 8.days.from_now }

      before do
        field_test(experiment_name, participant: user)
      end

      it "registers some events but not others", :aggregate_failures do
        7.times do |n|
          create(:comment, user_id: user.id, created_at: n.days.ago)
        end
        handler.call
        # Only the comment registered; the other one would've registered had the events happened
        # after the experiment start date.
        expect(FieldTest::Event.where(name: goal).count).to eq(1)
        expect(FieldTest::Event
               .where(name: "user_creates_comment_on_at_least_four_different_days_within_a_week")
               .count).to eq(0)
      end
    end

    context "with user who is part of field test and user_publishes_post goal" do
      let(:goal) { described_class::USER_PUBLISHES_POST_GOAL }

      before do
        field_test(AbExperiment::CURRENT_FEED_STRATEGY_EXPERIMENT, participant: user)
      end

      it "records a conversion", :aggregate_failures do
        create(:article, :past, past_published_at: 2.days.ago, user_id: user.id)
        handler.call
        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.last.name).to eq(goal)
      end

      it "records weekly post publishing conversions", :aggregate_failures do
        create(:article, :past, past_published_at: 2.days.ago, user_id: user.id)
        create(:article, :past, past_published_at: 3.days.ago, user_id: user.id)
        create(:article, :past, past_published_at: 13.days.ago, user_id: user.id)

        handler.call

        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.pluck(:name).sort)
          .to match_array([
            goal,
            "user_publishes_post_at_least_two_times_within_two_weeks",
            "user_publishes_post_at_least_two_times_within_week",
          ].sort)
      end

      it "records a conversion when they post 4 within a week" do
        create(:article, :past, past_published_at: 25.hours.ago, user_id: user.id)
        create(:article, :past, past_published_at: 49.hours.ago, user_id: user.id)
        create(:article, :past, past_published_at: 73.hours.ago, user_id: user.id)
        create(:article, :past, past_published_at: 97.hours.ago, user_id: user.id)

        handler.call

        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.pluck(:name).sort)
          .to match_array([
            goal,
            "user_publishes_post_at_least_two_times_within_two_weeks",
            "user_publishes_post_at_least_two_times_within_week",
            "user_publishes_post_on_four_different_days_within_a_week",
          ].sort)
      end
    end

    context "with user who is part of field test and user_creates_comment goal" do
      let(:goal) { described_class::USER_CREATES_COMMENT_GOAL }

      before do
        field_test(AbExperiment::CURRENT_FEED_STRATEGY_EXPERIMENT, participant: user)
      end

      it "records a conversion", :aggregate_failures do
        handler.call
        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.last.name).to eq(goal)
      end

      it "records user_creates_comment_on_at_least_four_different_days_within_a_week field test conversion",
         :aggregate_failures do
        7.times do |n|
          create(:comment, user_id: user.id, created_at: n.days.ago)
        end
        handler.call
        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.last.name)
          .to eq("user_creates_comment_on_at_least_four_different_days_within_a_week")
      end
    end

    context "with user who is part of field test and user_creates_pageview goal" do
      let(:goal) { described_class::USER_CREATES_PAGEVIEW_GOAL }

      before do
        field_test(AbExperiment::CURRENT_FEED_STRATEGY_EXPERIMENT, participant: user)
        Timecop.freeze(Time.current.utc.at_noon)
      end

      after { Timecop.return }

      it "records a field test when user views a page", :aggregate_failures do
        create(:page_view, user_id: user.id)
        handler.call
        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.pluck(:name))
          .to include(goal)
      end

      it "records user_views_pages_on_at_least_two_different_days_within_a_week field test conversion",
         :aggregate_failures do
        3.times do |n|
          create(:page_view, user_id: user.id, created_at: n.days.ago)
        end
        handler.call
        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.pluck(:name))
          .to include("user_views_pages_on_at_least_two_different_days_within_a_week")
      end

      it "records user_views_pages_on_at_least_four_different_days_within_a_week field test conversion",
         :aggregate_failures do
        7.times do |n|
          create(:page_view, user_id: user.id, created_at: n.days.ago)
        end
        handler.call
        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.pluck(:name))
          .to include("user_views_pages_on_at_least_four_different_days_within_a_week")
      end

      it "records user_views_pages_on_at_least_nine_different_days_within_two_weeks field test conversionn",
         :aggregate_failures do
        10.times do |n|
          create(:page_view, user_id: user.id, created_at: n.days.ago)
        end
        handler.call
        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.pluck(:name))
          .to include("user_views_pages_on_at_least_nine_different_days_within_two_weeks")
      end

      it "records user_views_pages_on_at_least_twelve_different_hours_within_five_days field test conversion",
         :aggregate_failures do
        15.times do |n|
          create(:page_view, user_id: user.id, created_at: n.hours.ago)
        end

        handler.call
        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.pluck(:name))
          .to include("user_views_pages_on_at_least_twelve_different_hours_within_five_days")
      end

      it "does not record field test conversion if not qualifying" do
        2.times do |n|
          create(:page_view, user_id: user.id, created_at: n.days.ago)
        end
        handler.call
        expect(FieldTest::Event.pluck(:name))
          .not_to include("user_views_pages_on_at_least_twelve_different_hours_within_five_days")
      end

      it "records user_views_pages_on_at_least_three_different_hours_within_a_day field test conversionn",
         :aggregate_failures do
        3.times do |n|
          create(:page_view, user_id: user.id, created_at: n.hours.ago)
        end
        handler.call
        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.pluck(:name))
          .to eq([goal, "user_views_pages_on_at_least_three_different_hours_within_a_day"])
      end

      it "records user_views_pages_on_at_least_four_different_hours_within_a_day field test conversionn",
         :aggregate_failures do
        7.times do |n|
          create(:page_view, user_id: user.id, created_at: n.hours.ago)
        end
        handler.call
        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.pluck(:name))
          .to eq([goal, "user_views_pages_on_at_least_three_different_hours_within_a_day",
                  "user_views_pages_on_at_least_four_different_hours_within_a_day"])
      end

      it "does not record user_views_article_four_hours_in_day field test conversion for non-qualifying activity" do
        2.times do |n|
          create(:page_view, user_id: user.id, created_at: n.hours.ago)
        end
        handler.call
        expect(FieldTest::Event.pluck(:name))
          .not_to include("user_views_pages_on_at_least_four_different_hours_within_a_day")
      end
    end

    context "with user who is part of field test and user_creates_article_reaction goal" do
      let(:goal) { described_class::USER_CREATES_ARTICLE_REACTION_GOAL }
      let(:user) { create(:user, :trusted) } # Because we want to test handling of privileged reactions

      before do
        field_test(AbExperiment::CURRENT_FEED_STRATEGY_EXPERIMENT, participant: user)
      end

      it "records a conversion", :aggregate_failures do
        create(:reaction, user_id: user.id)
        handler.call
        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.last.name).to eq(goal)
      end

      it "records a conversion when they react 4 times within a week", :aggregate_failures do
        create(:reaction, user_id: user.id, created_at: 25.hours.ago)
        create(:reaction, user_id: user.id, created_at: 49.hours.ago)
        create(:reaction, user_id: user.id, created_at: 73.hours.ago)
        create(:reaction, user_id: user.id, created_at: 97.hours.ago)
        handler.call

        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.pluck(:name).sort)
          .to match_array([
            goal,
            "user_creates_article_reaction_on_four_different_days_within_a_week",
          ].sort)
      end

      it "does not record the conversion when they react to non-articles or non-public reactions" do
        create(:reaction, user_id: user.id, created_at: 25.hours.ago)
        create(:reaction, user_id: user.id, created_at: 49.hours.ago)
        create(:reaction, user_id: user.id, created_at: 73.hours.ago, category: "vomit")
        create(:reaction, user_id: user.id, created_at: 97.hours.ago, reactable: create(:comment))
        create(:reaction, user_id: user.id, created_at: 121.hours.ago)

        handler.call

        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.pluck(:name))
          .to contain_exactly(goal)
      end

      it "does not record the conversion when their 4 reactions are not within a week" do
        create(:reaction, user_id: user.id, created_at: 25.hours.ago)
        create(:reaction, user_id: user.id, created_at: 49.hours.ago)
        create(:reaction, user_id: user.id, created_at: 73.hours.ago)
        create(:reaction, user_id: user.id, created_at: 217.hours.ago) # 9 days ago

        handler.call

        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.pluck(:name))
          .to contain_exactly(goal)
      end
    end

    context "with user who is not part of field test" do
      let(:goal) { described_class::USER_CREATES_COMMENT_GOAL }

      it "does not register a conversion" do
        handler.call
        expect(FieldTest::Event.all.size).to be(0)
      end

      it "records user_views_article_four_days_in_week field test conversion" do
        7.times do |n|
          create(:page_view, user_id: user.id, created_at: n.days.ago)
        end
        handler.call
        expect(FieldTest::Event.all.size).to be(0)
      end
    end

    context "with user who is part of field test and user_creates_email_feed_event goal" do
      let(:goal) { described_class::USER_CREATES_EMAIL_FEED_EVENT_GOAL }
      let(:article) { create(:article, :past, past_published_at: 2.days.ago, user_id: user.id) }

      before do
        field_test(AbExperiment::CURRENT_FEED_STRATEGY_EXPERIMENT, participant: user)
      end

      it "records a conversio for click", :aggregate_failures do
        handler.call
        expect(FieldTest::Event.last.field_test_membership.participant_id).to eq(user.id.to_s)
        expect(FieldTest::Event.last.name).to eq(goal)
        expect(FieldTest::Event.all.size).to be(1)
      end

    end
  end
end
