class Ecs::QueryCourseEvaluationService < ApplicationService
  attr_reader :ec_course, :evaluation_status

  def initialize(ec_course)
    @ec_course = ec_course
    @_course_achievement = 0
  end

  def course_targets
    @_course_targets ||= calculate_course_targets
  end

  def course_achievement
    course_targets

    @_course_achievement.round(2)
  end

  def graduation_subitem_evaluations
    student_scores = ec_course.ec_course_student_scores.joins(:ec_course_target).group(:ec_course_target_id)
    student_scores = student_scores.select('AVG(score) as average_score, ec_course_target_id')
    student_score_map = student_scores.group_by { |item| item.ec_course_target_id }

    subitem_targets = ec_course.ec_graduation_subitem_course_targets
                        .includes(ec_graduation_subitem: :ec_graduation_requirement)

    subitem_targets.group_by(&:ec_graduation_subitem_id).map do |_id, arr|
      subitem = arr.first.ec_graduation_subitem

      support = subitem.ec_course_supports.find_by(ec_course_id: ec_course.id)

      weight = support.weights.to_f
      objective_achievement = (weight * ec_course.ec_year.calculation_value.to_f).round(3)

      target_total_rates = 0
      reach_real_target = 0

      arr.map(&:ec_course_target).uniq.each do |target|
        target_total_rates += target.weight.to_f
        student_score = student_score_map[target.id]

        reach_real_target += student_score.average_score.to_f * target.weight.to_f if student_score
      end

      actually_achievement = target_total_rates.zero? ? 0 : (reach_real_target * weight) / (target_total_rates.round(3) * 100)

      {
        graduation_subitem_id: subitem.id,
        content: subitem.content,
        position: subitem.position,
        graduation_requirement_position: subitem.ec_graduation_requirement.position,
        weights: format('%.02f', weight),
        support_course_target_ids: arr.map(&:ec_course_target_id),
        objective_achievement: format('%.03f', objective_achievement),
        actually_achievement: format('%.03f', actually_achievement),
        status: achieve_status(objective_achievement, actually_achievement)
      }
    end
  end

  def score_levels_map
    @_score_levels_map ||= begin
      ec_course.ec_score_levels.each_with_object({}) do |level, obj|
        obj[level.id.to_s] = level.as_json(only: %i[id position score level])
      end
    end
  end

  private

  def calculate_course_targets
    score_levels = ec_course.ec_score_levels.to_a

    ec_course.ec_course_targets.includes(:ec_student_score_targets).map do |course_target|
      data = course_target.as_json(only: %i[id position content standard_grade])
      data[:weight] = course_target.weight

      score_targets = course_target.ec_student_score_targets.to_a
      data[:average_score] = score_targets.size.zero? ? 0 : score_targets.sum(&:score).fdiv(score_targets.size).round(2)
      data[:maximum_score] = score_targets.max_by(&:score)&.score
      data[:minimum_score] = score_targets.min_by(&:score)&.score
      data[:actually_grade] = data[:average_score]

      data[:status] = achieve_status(course_target.standard_grade, data[:average_score])

      # 计算总评成绩
      @_course_achievement += data[:average_score].to_f * course_target.weight.to_f

      # 计算学生成绩分布区间
      data[:score_levels] = score_levels.map do |score_level|
        level_condition_proc =
          if (score_level.position - 1).zero? # 第一区间
            -> (score_target){ score_target.score >= score_level.score ? 1 : 0 }
          elsif score_levels.position == score_levels.size # 末尾区间
            -> (score_target){ score_target.score < score_level.score ? 1 : 0 }
          else
            # 中间区间
            -> (score_target){ score_target.score >= score_level.score && score_target.score < score_targets[score_level.position - 1] ? 1 : 0 }
          end

        # 计算该成绩区间人数
        count = score_targets.sum(&level_condition_proc)

        { id: score_level.id, count: count }
      end

      data
    end
  end

  def achieve_status(standard_value, real_value)
    standard_value && real_value && real_value >= standard_value ? 'achieved' : 'not_achieved'
  end
end