class Csv::Nursing::VaccineSummaryService < Csv::ApplicationService
  def initialize(school)
    @school = school
  end

  def call
    CSV.generate do |csv|
      csv << headers
      vaccines.each do |vaccine|
        vaccine_categories.each do |category|
          csv << content(vaccine, category)
        end
      end
      summary_row_headers.each do |key, summary|
        csv << summary(key, summary)
      end
    end
  end

  private
    def vaccines
      @vaccines ||= @school.nursing_vaccine_configs
        .includes(:vaccine)
        .ordered
        .decorate
        .to_a
    end

    def student_vaccines
      @student_vaccines ||= @school.nursing_vaccine_records
        .joins(:student)
        .group_by(&:student_id)
    end

    def students
      @students ||= @school.students
        .current_status(@school, :current)
        .includes(:student_medical)
        .ordered
        .group_by(&:grade)
    end

    def grades
      @grades ||= @school.grades_hash
    end

    def headers
      grades.values.unshift(nil)
    end

    def summary_row_headers
      {
        medical: 'Students with at least one medical waiver',
        religious: 'Students with at least one religious waiver',
        personal: 'Students with at least one personal waiver',
        exempt: 'Students with at least one other waiver',
        overall: 'Students who have an overall waiver',
        requirements_met: 'Students meeting all minimum requirements',
        requirements_missed: 'Students who did not meet the required by date',
        any_waiver: 'Students with any waiver'
      }
    end

    def vaccine_categories
      @vaccine_categories = [
        'received',
        'missing',
        'medical',
        'religious',
        'personal',
        'exempt',
        'none',
        'total'
      ]
    end

    def student_count
      summary_count = {}
      @student_count ||= vaccines.map do |vaccine|
        category_hash = {}
        grades.each_key do |grade|
          grade_categories = {
            received: 0,
            missing: 0,
            medical: 0,
            religious: 0,
            personal: 0,
            exempt: 0,
            none: 0
          }
          summary_count[grade] ||= {
            medical: [],
            religious: [],
            personal: [],
            exempt: [],
            overall: [],
            requirements_met: [],
            requirements_missed: [],
            any_waiver: []
          }
          students[grade]&.each do |student|
            if student.student_medical && !student.student_medical.not_exempt?
              grade_categories[student.student_medical.exempt.to_sym] += 1
              summary_count[grade][:any_waiver].push(student.id)
              next summary_count[grade][:overall].push(student.id)
            end

            student_vaccine = student_vaccines[student.id]
              &.find { |sv| sv.vaccine_id == vaccine.vaccine_id }

            if student_vaccine.nil? && student.grade >= vaccine.grade &&
                (student.student_medical.nil? || student.student_medical.not_exempt?)
              grade_categories[:missing] += 1
              summary_count[grade][:requirements_missed].push(student.id)
            elsif student_vaccine && student_vaccine.exempt != 'not_exempt'
              exemption = student_vaccine.exempt.to_sym
              grade_categories[exemption] += 1
              summary_count[grade][exemption].push(student.id)
              summary_count[grade][:any_waiver].push(student.id)
            elsif student_vaccine&.not_exempt?
              grade_categories[:received] += 1
            else
              grade_categories[:none] += 1
            end
          end

          grade_categories[:total] =
            grade_categories[:received] +
            grade_categories[:missing] +
            grade_categories[:medical] +
            grade_categories[:religious] +
            grade_categories[:personal] +
            grade_categories[:exempt] +
            grade_categories[:none]

          category_hash[grade] = grade_categories
          summary_count[grade][:requirements_met] = if students[grade].blank?
            []
          else
            students[grade].pluck(:id) -
              (summary_count[grade][:requirements_missed] + summary_count[grade][:any_waiver])
          end
        end
        [vaccine.vaccine_id, category_hash]
      end.to_h.merge(summary_count: summary_count)
    end

    def content(vaccine, category)
      data = ["#{vaccine.name} #{category.capitalize}"]
      grades.each_key do |grade|
        data.push(student_count.dig(vaccine.vaccine_id, grade, category.to_sym))
      end
      data
    end

    def summary(key, summary)
      data = [summary]
      grades.each_key do |grade|
        data.push(student_count[:summary_count][grade][key].uniq.length)
      end
      data
    end
end
