class Csv::EdFi::Indiana::TranscriptService < Csv::ApplicationService
  def initialize(school_id, grade)
    @school_id = school_id
    @grade_range = grade_range(grade)
  end

  def call
    body = []
    CSV.generate do |csv|
      csv << headers
      preschool_transcripts.each { |c| body += props(c, c.class_subject.code) }
      day_long_transcripts.each { |c| body += props(c, c.class_subject.code) }
      period_long_transcripts.each { |c| body += props(c, c.classroom.course_number) }
      body.each { |b| csv << b }
    end
  end

  private
    def school
      @school ||= School.find(@school_id)
    end

    def school_config
      @school_config ||= school.school_config
    end

    def gpa_scales
      @gpa_scales ||= school.school_gpa_scales
    end

    def transcript_config
      @transcript_config ||= school.transcript_config
    end

    def grade_range(grade)
      {
        'preschool' => -10..-1,
        'primary' => 0..5,
        'middle' => 6..8,
        'secondary' => 9..12
      }[grade]
    end

    def class_ids
      school.classrooms
        .preload(:class_periods)
        .joins(:school_state_classroom)
        .period_long
        .where.not(course_number: ['', ' '])
        .pluck(:id)
    end

    def subject_ids(type)
      school.class_subjects
        .joins(classroom: :school_state_classroom)
        .where.not(code: ['', ' '])
        .merge(Classroom.by_type(type))
        .pluck(:id)
    end

    def grades_mapped_ungraded
      school.grades.where(edfi_grade: -4).pluck(:grade)
    end

    def transcripts
      school.student_transcripts
        .where(grade: @grade_range)
        .where.not(grade: grades_mapped_ungraded)
        .includes(:student, :college, :classroom)
        .by_school_year(school.current_year.id)
    end

    def period_long_transcripts
      transcripts
        .where(grade: 0..8)
        .or(transcripts.where(grade: 9..Float::INFINITY))
        .joins(:student)
        .by_classes(class_ids)
        .order('StudentTranscripts.Grade ASC, Students.LastName, Students.FirstName')
    end

    def day_long_transcripts
      transcripts
        .joins(:student)
        .most_recent(:quarter, school.id, school.current_year.id)
        .by_subjects(subject_ids(:day_long))
        .where(grade: 0..8, high_school_credit: false)
        .order('StudentTranscripts.Grade ASC, Students.LastName, Students.FirstName')
    end

    def preschool_transcripts
      transcripts
        .joins(:student)
        .most_recent(:quarter, school.id, school.current_year.id)
        .by_subjects(subject_ids(:preschool))
        .where(grade: -10..0)
        .order('StudentTranscripts.Grade ASC, Students.LastName, Students.FirstName')
    end

    def descriptor_service(descriptor)
      EdFi::Indiana::Sandbox::DescriptorService.call(school.current_year.id, descriptor: descriptor)
    end

    def method_credit_earned_descriptors
      @method_credit_earned_descriptors ||= descriptor_service(:methodcreditearneddescriptors)
        .index_by { |d| d['id'] }
    end
    
    def method_credit_descriptor(transcript)
      descriptor = method_credit_earned_descriptors[transcript&.credit_descriptor_id]
      return '<Classroom credit>' if descriptor.nil?

      "#{descriptor['codeValue']}"
    end

    def grades
      @grades ||= school.grades_hash
    end

    def terms
      @terms ||= if school_config.semesters?
        { 1 => 'Fall Semester', 2 => 'Spring Semester' }
      else
        { 1 => 'First Trimester', 2 => 'Second Trimester', 3 => 'Third Trimester' }
      end
    end

    def headers
      arr = [
        'Grade',
        'Student Name',
        'Student State ID',
        'Course Name',
        'State Course Code',
        'Term',
        'Result',
        'Earned Credits',
        'Numeric Grade',
        'Credit Type',
        'College',
        'Method Credit Earned'
      ]
      arr.delete_at(8) if [-10..-1, 0..5].include?(@grade_range)
      arr
    end

    def props(transcript, code)
      classroom = transcript.classroom
      semesters = if classroom.period_long? && transcript.semester.zero?
        credits = transcript.credits_earned / school_config.semesters.keys.count
        school_config.semesters.keys
      elsif classroom.preschool? || classroom.day_long?
        credits = nil
        if school_config.trimesters?
          [transcript.quarter]
        elsif [1, 2].include?(transcript.quarter)
          [1]
        else
          [2]
        end
      else
        credits = transcript.credits_earned
        [transcript.semester]
      end

      grade = if transcript.letter_grade == 'I'
        'Incomplete'
      elsif transcript.letter_grade == 'F'
        'Fail'
      elsif transcript.letter_grade.blank?
        'No grade awarded'
      else
        'Pass'
      end

      semesters.map do |semester|
        points = transcript.calculate_points(gpa_scales, transcript_config)
        points = 4.0 if points > 4
        term_descriptor = classroom.period_long? ? terms[semester] : 'Year Round'
        grade_ranges = [-10..-1, 0..5]

        arr = [
          grades[transcript.grade],
          transcript.student.full_name(:reverse),
          transcript.student.state_number&.number,
          transcript.course_title,
          code,
          term_descriptor,
          grade,
          credits,
          points.round(2),
          transcript.college ? 'Dual Credit' : 'Regular Credit',
          transcript.college&.code,
          method_credit_descriptor(transcript)
        ]

        arr.delete_at(8) if grade_ranges.include?(@grade_range)
        arr
      end
    end
end
