class GekanatorQuestionExample < ApplicationRecord
  ANSWERS = GekanatorQuestionSuggestion::ANSWERS
  NON_UNKNOWN_ANSWERS = ANSWERS - ['unknown']
  SOURCES = ['initial_suggestion', 'post_game_answer', 'post_game_extra'].freeze

  belongs_to :gekanator_question
  belongs_to :post
  belongs_to :user
  belongs_to :gekanator_game, optional: true

  validates :answer, presence: true, inclusion: { in: ANSWERS }
  validates :answer_counts, presence: true
  validates :sample_count,
            presence: true,
            numericality: {
              only_integer: true,
              greater_than: 0
            }
  validates :source, presence: true, inclusion: { in: SOURCES }
  validates :weight,
            presence: true,
            numericality: {
              greater_than: 0
            }

  before_validation :normalize_learning_state

  def record_answer!(answer:, source:, gekanator_game: nil)
    answer = answer.to_s
    raise ArgumentError, 'invalid answer' unless ANSWERS.include?(answer)

    counts = normalized_answer_counts
    counts[answer] += 1

    self.answer_counts = counts
    self.sample_count = counts.values.sum
    self.gekanator_game = gekanator_game if gekanator_game.present?
    self.source = source

    apply_aggregated_answer!(preferred_answer: answer)
    self
  end

  private

  def normalize_learning_state
    counts = normalized_answer_counts

    if counts.values.sum.zero? && answer.present?
      counts[answer] = 1
    end

    self.answer_counts = counts
    self.sample_count = counts.values.sum

    apply_aggregated_answer!
  end

  def apply_aggregated_answer!(preferred_answer: nil)
    counts = normalized_answer_counts
    known_counts = counts.slice(*NON_UNKNOWN_ANSWERS)
    known_total = known_counts.values.sum

    if known_total.zero?
      self.answer = 'unknown'
      self.weight = 0.1
      return
    else
      max_count = known_counts.values.max
      candidates = known_counts.select { |_answer, count| count == max_count }.keys
      self.answer =
          if preferred_answer.present? && candidates.include?(preferred_answer)
            preferred_answer
          elsif answer.present? && candidates.include?(answer)
            answer
          else
            candidates.first
          end
    end

    consensus = max_count.to_f / known_total
    self.weight = Math.sqrt(known_total) * consensus
  end

  def normalized_answer_counts
    base = ANSWERS.index_with(0)

    answer_counts.to_h.each do |key, value|
      answer_key = key.to_s
      next unless ANSWERS.include?(answer_key)

      base[answer_key] = value.to_i
    end

    base
  end
end
