class AbExperiment
  # Responsible for checking if a given :user has "accomplished" the state :goal for any of the
  # active :experiments.  We scope our tests to events that happened on or after the experiment's
  # start date.
  #
  # @note It is required that each experiment have a start date (in CCYY-MM-DD format).
  class GoalConversionHandler
    include FieldTest::Helpers

    # The constant says "publishes post"; we're not actually concerned with the state change (from
    # unpublished to published) but instead counting the number of published articles.
    USER_PUBLISHES_POST_GOAL = "user_publishes_post".freeze
    USER_CREATES_PAGEVIEW_GOAL = "user_creates_pageview".freeze
    USER_CREATES_COMMENT_GOAL = "user_creates_comment".freeze
    USER_CREATES_ARTICLE_REACTION_GOAL = "user_creates_article_reaction".freeze
    USER_CREATES_EMAIL_FEED_EVENT_GOAL = "user_creates_email_feed_event".freeze

    def self.call(...)
      new(...).call
    end

    def initialize(user:, goal:, experiments:, start_date: nil)
      @user = user
      @goal = goal
      @experiments = experiments
      @start_date = start_date&.beginning_of_day
    end

    attr_reader :experiments, :user, :goal

    def call
      # It's okay that there are no experiments.
      return if experiments.nil?

      experiments.each do |key, data|
        # We've already declared a winner, let's not do any of the processing
        next if data.key?("winner")

        experiment_start_date = @start_date || data.fetch("started_at").beginning_of_day
        experiment = key.to_sym
        convert(experiment: experiment, experiment_start_date: experiment_start_date)
      end
    end

    private

    def convert(experiment:, experiment_start_date:)
      case goal
        # We have special conditional goals for some where we look for past events for cummulative wins
        # Otherwise we convert the goal as given.
      when USER_CREATES_PAGEVIEW_GOAL
        convert_pageview_goal(experiment: experiment, experiment_start_date: experiment_start_date)
      when USER_CREATES_COMMENT_GOAL # comments goal. Only page views and comments are currently active.
        convert_comment_goal(experiment: experiment, experiment_start_date: experiment_start_date)
      when USER_PUBLISHES_POST_GOAL
        convert_post_goal(experiment: experiment, experiment_start_date: experiment_start_date)
      when USER_CREATES_ARTICLE_REACTION_GOAL
        convert_reaction_goal(experiment: experiment, experiment_start_date: experiment_start_date)
      else
        field_test_converted(experiment, participant: user, goal: goal) # When there is only a single goal.
      end
    end

    def convert_pageview_goal(experiment:, experiment_start_date:)
      # TODO: Remove once we know that this test is not over-heating the application.  That would be a
      # few days after the deploy to DEV of this change.
      if FeatureFlag.accessible?(:field_test_event_single_create_pageview)
        field_test_converted(experiment, participant: user, goal: goal) # base is someone viewed a page
      end
      pageview_goal(experiment,
                    [7.days.ago, experiment_start_date].max,
                    "DATE(created_at)",
                    2,
                    "user_views_pages_on_at_least_two_different_days_within_a_week")
      pageview_goal(experiment,
                    [7.days.ago, experiment_start_date].max,
                    "DATE(created_at)",
                    4,
                    "user_views_pages_on_at_least_four_different_days_within_a_week")
      pageview_goal(experiment,
                    [24.hours.ago, experiment_start_date].max,
                    "DATE_PART('hour', created_at)",
                    3,
                    "user_views_pages_on_at_least_three_different_hours_within_a_day")
      pageview_goal(experiment,
                    [24.hours.ago, experiment_start_date].max,
                    "DATE_PART('hour', created_at)",
                    4,
                    "user_views_pages_on_at_least_four_different_hours_within_a_day")
      pageview_goal(experiment,
                    [14.days.ago, experiment_start_date].max,
                    "DATE(created_at)",
                    9,
                    "user_views_pages_on_at_least_nine_different_days_within_two_weeks")
      pageview_goal(experiment,
                    [5.days.ago, experiment_start_date].max,
                    "DATE_PART('hour', created_at)",
                    12,
                    "user_views_pages_on_at_least_twelve_different_hours_within_five_days")
    end

    def convert_comment_goal(experiment:, experiment_start_date:)
      field_test_converted(experiment, participant: user, goal: goal) # base single comment goal.
      comment_goal(experiment,
                   [7.days.ago, experiment_start_date].max,
                   "DATE(created_at)",
                   4,
                   "user_creates_comment_on_at_least_four_different_days_within_a_week")
    end

    def convert_post_goal(experiment:, experiment_start_date:)
      field_test_converted(experiment, participant: user, goal: goal) # base is we created a post
      post_goal_with_group(experiment,
                           [7.days.ago, experiment_start_date].max,
                           "DATE(published_at)",
                           4,
                           "user_publishes_post_on_four_different_days_within_a_week")
      post_goal(experiment,
                [7.days.ago, experiment_start_date].max,
                2,
                "user_publishes_post_at_least_two_times_within_week")
      post_goal(experiment,
                [14.days.ago, experiment_start_date].max,
                2,
                "user_publishes_post_at_least_two_times_within_two_weeks")
    end

    def convert_reaction_goal(experiment:, experiment_start_date:)
      field_test_converted(experiment, participant: user, goal: goal) # base is we created a post
      reaction_goal(experiment,
                    [7.days.ago, experiment_start_date].max,
                    "DATE(created_at)",
                    4,
                    "user_creates_article_reaction_on_four_different_days_within_a_week")
    end

    def pageview_goal(experiment, time_start, group_value, min_count, goal)
      page_view_counts = user.page_views.where("created_at > ?", time_start)
        .group(group_value).count.values
      page_view_counts.delete(0)
      return unless page_view_counts.size >= min_count

      field_test_converted(experiment, participant: user, goal: goal)
    end

    def post_goal(experiment, time_start, min_count, goal)
      return unless user.articles.published.from_subforem.where("published_at > ?", time_start).count >= min_count

      field_test_converted(experiment, participant: user, goal: goal)
    end

    def post_goal_with_group(experiment, time_start, group_value, min_count, goal)
      post_publication_counts = user.articles.published.from_subforem.where("published_at > ?", time_start)
        .group(group_value).count.values
      return unless post_publication_counts.size >= min_count

      field_test_converted(experiment, participant: user, goal: goal)
    end

    def comment_goal(experiment, time_start, group_value, min_count, goal)
      comment_counts = user.comments.where("created_at > ?", time_start)
        .group(group_value).count.values
      comment_counts.delete(0)
      return unless comment_counts.size >= min_count

      field_test_converted(experiment, participant: user, goal: goal)
    end

    def reaction_goal(experiment, time_start, group_value, min_count, goal)
      reaction_counts = user.reactions
        .only_articles # as per `Reaction#record_field_test_event` we only record reactions to articles
        .public_category # as per `Reaction#record_field_test_event` we only record public category reactions
        .where("created_at > ?", time_start)
        .group(group_value).count.values
      return unless reaction_counts.size >= min_count

      field_test_converted(experiment, participant: user, goal: goal)
    end
  end
end
