# frozen_string_literal: true
class Course::Condition < ApplicationRecord
  actable

  validates :actable_type, length: { maximum: 255 }, allow_nil: true
  validates :conditional_type, length: { maximum: 255 }, presence: true
  validates :creator, presence: true
  validates :updater, presence: true
  validates :course, presence: true
  validates :conditional, presence: true
  validates :actable_type, uniqueness: { scope: [:actable_id], allow_nil: true,
                                         if: -> { actable_id? && actable_type_changed? } }
  validates :actable_id, uniqueness: { scope: [:actable_type], allow_nil: true,
                                       if: -> { actable_type? && actable_id_changed? } }
  validate :validate_conditional_in_the_same_course

  belongs_to :course, inverse_of: false
  belongs_to :conditional, polymorphic: true

  delegate :satisfied_by?, to: :actable

  ALL_CONDITIONS = [
    { name: Course::Condition::Achievement.name, active: true },
    { name: Course::Condition::Assessment.name, active: true },
    { name: Course::Condition::Level.name, active: true },
    { name: Course::Condition::Survey.name, active: true },
    { name: Course::Condition::Video.name, active: false },
    { name: Course::Condition::ScholaisticAssessment.name, active: true }
  ].freeze

  class << self
    # Finds all the conditionals for the given course.
    #
    # @param [Course] course The course with the conditionals to be retrieved.
    # @return [Object] acts_as_conditionals objects belonging to the given course
    def conditionals_for(course)
      dependent_class_to_condition_class_mapping.keys.map do |conditional_name|
        next unless conditional_name.constantize.include?(
          ActiveRecord::Base::ConditionalInstanceMethods
        )

        conditional_name.constantize.where(course_id: course)
      end.flatten.compact
    end

    # Finds all conditionals that depend on the given object.
    #
    # @param [Course::Assessment, Course::Achievement] dependent_object An assessment or
    #   achievement that conditionals depends on
    # @return [Object] acts_as_conditional Objects that depend on the condition_object
    def find_conditionals_of(dependent_object)
      condition_classes_of(dependent_object).map do |condition_name|
        Course::Condition.find_by_sql(<<-SQL)
          SELECT * FROM course_conditions cc
            INNER JOIN course_condition_#{condition_name.demodulize.downcase.pluralize} ccs
            ON cc.actable_type = '#{condition_name}'
              AND cc.actable_id = ccs.id
              AND ccs.#{dependent_object.class.name.demodulize.downcase}_id = #{dependent_object.id}
          WHERE course_id = #{dependent_object.course_id}
        SQL
      end.flatten.map(&:conditional)
    end

    private

    # Finds condition classes that depend on the dependent_object. For example, if the
    # dependent_object is a Course::Achievement object, this method should return
    # [Course::Condition::Achievement].
    def condition_classes_of(dependent_object)
      dependent_class_to_condition_class_mapping[dependent_object.class.name]
    end

    # Finds the mapping of dependent classes to arrays of condition classes. For example,
    # {
    #   'Course::Achievement' => ['Course::Condition::Achievement']
    #   'Course::Assessment' => ['Course::Condition::Assessment']
    # }
    def dependent_class_to_condition_class_mapping
      mappings = Hash.new { |h, k| h[k] = [] }

      Course::Condition::ALL_CONDITIONS.map do |condition|
        dependent_class = condition[:name].constantize.dependent_class
        mappings[dependent_class] << condition[:name] unless dependent_class.nil?
      end

      mappings
    end
  end

  private

  def validate_conditional_in_the_same_course
    return unless course_id && conditional

    return if conditional.course_id == course_id

    errors.add(:conditional, :not_in_same_course)
  end
end
