class Student < ApplicationRecord
  audited only: [
    :Gender,
    :DOB,
    :Ethnicity,
    :Graduated,
    :GradYear,
    :GradMonth,
    :GradDay,
    :StudentCode
  ]

  include Base::Student
  include Personable
  include Formattable
  include Edfiable

  attr_accessor :race, :origin, :origin_descriptors

  enum ethnicity: { unspecified: 0, non_hispanic: 1, hispanic: 2 }
  enum gender: { male: 'M', female: 'F', non_binary: 'N' }

  RESTORE_ASSOCIATIONS = [
    :user,
    :race_students,
    :class_students,
    :class_grade_postings,
    :class_grades,
    :communication_notification_users,
    :school_district_imports,
    :student_enrollments,
    :class_student_enrollments,
    :class_seatings,
    :category_students
  ]

  associations_for legacy: true do |a|
    a.belongs_to :school
    a.belongs_to :family, foreign_key: :FamilyID,
      inverse_of: :primary_students
    a.belongs_to :family2, foreign_key: :Family2ID, primary_key: :FamilyID,
      class_name: 'Family', inverse_of: :secondary_students, optional: true
    a.belongs_to :lunch_price_plan, keys: :LPPID, optional: true
    a.belongs_to :breakfast_price_plan, foreign_key: :BPPID, primary_key: :LPPID,
      class_name: :LunchPricePlan, optional: true

    a.has_many :attendances
    a.has_many :race_students, dependent: :destroy
    a.has_many :school_district_imports, dependent: :destroy
    a.has_many :school_year_students, autosave: true
    a.has_many :student_additional_values, autosave: true
    a.has_many :category_students, dependent: :destroy
    a.has_many :class_grades, dependent: :destroy
    a.has_many :class_grade_summaries, dependent: :destroy
    a.has_many :class_seatings, dependent: :destroy
    a.has_many :class_students, dependent: :destroy
    a.has_many :class_student_enrollments, dependent: :destroy
    a.has_many :student_enrollments, dependent: :destroy
    a.has_many :student_lunch_orders
    a.has_many :student_milk_orders
    a.has_many :student_documents, dependent: :destroy
    a.has_many :student_item_orders, dependent: :destroy
    a.has_many :parent_teacher_meetings, dependent: :destroy
    a.has_many :student_habit_values, dependent: :destroy

    a.has_one :student_medical, inverse_of: :student, autosave: true
    a.has_one :user, dependent: :destroy
  end

  associations_for namespace: 'Admission' do |a|
    a.has_many :applicants, inverse_of: :student
  end

  associations_for namespace: 'Accounting' do |a|
    a.has_many :transaction_details, dependent: :destroy
    a.has_many :student_matrices, dependent: :destroy
  end

  associations_for namespace: 'Billing' do |a|
    a.has_many :plans
    a.has_many :plan_transactions, through: :plans
    a.has_many :transaction_details, through: :plan_transactions, source: :detail
    a.has_many :transaction_days, through: :transaction_details, source: :days
  end

  associations_for namespace: 'Covid' do |a|
    a.has_many :temperatures
    a.has_many :screenings
  end

  associations_for namespace: 'Discipline' do |a|
    a.has_many :logs, class_name: '::Discipline::StudentLog', foreign_key: :StudentID,
      inverse_of: :student
    a.has_many :victim_logs, class_name: '::Discipline::StudentLog', foreign_key: :victim_id
    a.has_many :detentions, class_name: 'Discipline::StudentDetention', foreign_key: :StudentID,
      inverse_of: :student
  end

  associations_for namespace: 'EdFi' do |a|
    a.has_many :logs, as: :associated, inverse_of: :associated
    a.has_many :state_programs
    a.has_many :accommodations, dependent: :destroy
    a.has_many :ids, as: :associated, inverse_of: :associated, dependent: :destroy
    a.has_many :graduation_plans, dependent: :destroy

    a.has_one :identity, as: :associated, inverse_of: :associated, dependent: :destroy
    a.has_one :digital_equity, dependent: :destroy
  end

  associations_for namespace: 'Nursing' do |a|
    a.has_many :logs, class_name: 'Nursing::Log', foreign_key: :StudentID, inverse_of: :student
    a.has_many :medications, class_name: 'Nursing::StudentMedication', foreign_key: :StudentID,
      inverse_of: :student
    a.has_many :prescriptions, class_name: 'Nursing::Prescription', foreign_key: :StudentID,
      inverse_of: :student
    a.has_many :prescription_distributions, class_name: 'Nursing::PrescriptionDistribution',
      foreign_key: :StudentID, inverse_of: :student
    a.has_many :screenings, class_name: 'Nursing::Screening', foreign_key: :StudentID,
      inverse_of: :student
    a.has_many :vaccine_records, class_name: 'Nursing::VaccineRecord', foreign_key: :StudentID,
      inverse_of: :student
  end

  associations_for namespace: 'Service', legacy: true do |a|
    a.has_many :histories, class_name: '::Service::StudentHistory'
    a.has_many :logs, class_name: '::Service::StudentLog'
  end

  belongs_to :homeroom_teacher, class_name: 'Employee', foreign_key: :TeacherID,
    inverse_of: :homeroom_students, optional: true
  belongs_to :location, class_name: 'Facility::Location', foreign_key: :LocationID,
    inverse_of: :students, optional: true
  belongs_to :type, foreign_key: :STID, class_name: 'StudentType', optional: true,
    inverse_of: :students
  has_one :communication_user, -> { student }, class_name: '::Communication::User',
    foreign_key: :associated_id, dependent: :destroy, inverse_of: :associated
  has_one :current_school_year_student,
    ->(student) { where(current: true, school_year: student.school.current_year) },
    class_name: :SchoolYearStudent, inverse_of: :student
  has_one :detail, class_name: 'StudentDetail', inverse_of: :student, dependent: :destroy,
    foreign_key: :StudentID
  has_one :indiana_settlement_number, -> { where(code: :indiana_settlement) },
    class_name: '::StudentAdditionalId', foreign_key: :associated_id, as: :associated,
    dependent: :destroy, inverse_of: :student
  has_one :language, class_name: 'EdFi::StudentLanguage', foreign_key: :StudentID,
    inverse_of: :student, dependent: :destroy
  has_one :nickname, -> { nickname }, class_name: '::StudentName', autosave: true,
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :student
  has_one :payjunction_student, foreign_key: :person_id, class_name: 'Payjunction::Student'
  has_one :restore, as: :associated, inverse_of: :associated
  has_one :school_number, -> { where(code: :school) },
    class_name: '::StudentAdditionalId', foreign_key: :associated_id, as: :associated,
    dependent: :destroy, inverse_of: :student
  has_one :state_number, -> { where(code: :state) },
    class_name: '::StudentAdditionalId', foreign_key: :associated_id, as: :associated,
    dependent: :destroy, inverse_of: :student
  has_one :student_requirement, foreign_key: :StudentID, inverse_of: :student, dependent: :destroy
  has_one :family_medical, through: :family
  has_one :module_config, through: :school, source: :student_module
  has_one :requirement_label, through: :student_requirement
  has_one :primary_email_address, -> { where(primary: true) }, class_name: '::StudentEmail',
    foreign_key: :associated_id, dependent: :destroy, inverse_of: :student

  has_many :current_school_year_students,
    -> { joins(:school_year).where(SchoolYears: { Current: true }) },
    class_name: :SchoolYearStudent, inverse_of: :student
  has_many :additional_ids, class_name: '::StudentAdditionalId', foreign_key: :associated_id,
    as: :associated, dependent: :destroy, inverse_of: :student
  has_many :all_contact_families, ->(student) { unscope(:where).by_family_1_or_2(student) },
    class_name: 'ContactFamily'
  has_many :category_students, foreign_key: :StudentID, dependent: :destroy, inverse_of: :student
  has_many :class_grade_postings, dependent: :destroy
  has_many :communication_notification_users, -> { where(associated_type: :student) },
    class_name: 'Communication::NotificationUser', foreign_key: :recipient_id, dependent: :destroy,
    inverse_of: :student
  has_many :ethnic_origins, -> { where(origin: :ethnic) }, class_name: '::StudentOrigin',
    foreign_key: :student_id, dependent: :destroy, inverse_of: :student
  has_many :names, class_name: '::StudentName', foreign_key: :associated_id, as: :associated,
    dependent: :destroy, inverse_of: :student
  has_many :origins, class_name: '::StudentOrigin', foreign_key: :student_id, dependent: :destroy,
    inverse_of: :student
  has_many :race_origins, -> { where(origin: :race) }, class_name: '::StudentOrigin',
    foreign_key: :student_id, dependent: :destroy, inverse_of: :student
  has_many :student_activities, class_name: 'StudentActivityStudent', foreign_key: :StudentID,
    dependent: :destroy, inverse_of: :student
  has_many :student_transcripts, class_name: '::StudentTranscript', foreign_key: :StudentID,
    dependent: :destroy, inverse_of: :student
  has_many :indiana_question_values, -> { where(school_id: 101) },
    class_name: '::StudentAdditionalValue', foreign_key: :StudentID,
    inverse_of: :student
  has_many :email_addresses, class_name: '::StudentEmail', foreign_key: :associated_id,
    as: :associated, dependent: :destroy, inverse_of: :student

  has_many :accounting_transactions, through: :accounting_transaction_details
  has_many :admission_applications, through: :admission_applicants
  has_many :admission_applicant_documents, through: :admission_applicants,
    source: :applicant_documents, class_name: '::Admission::ApplicantDocument'
  has_many :admission_documents, through: :admission_applicant_documents,
    source: :file_attachment
  has_many :admission_submitted_applications, through: :admission_applicants,
    source: :submitted_application_attachment
  has_many :categories, through: :category_students, source: :category
  has_many :classrooms, through: :class_students
  has_many :class_assignments, through: :classrooms
  has_many :class_lesson_plans, through: :classrooms
  has_many :contact_families, through: :family
  has_many :documents, through: :class_students
  has_many :news_articles, through: :class_students
  has_many :primary_contacts, through: :family
  has_many :races, through: :race_students, dependent: :destroy
  has_many :school_years, through: :school_year_students
  has_many :sycamore_storage_attachments, through: :admission_applicants
  has_many :batch_email_recipients, class_name: '::Communication::BatchEmailRecipient',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :associated
  has_many :batch_email_recipients, class_name: '::Communication::BatchEmailRecipient',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :associated
  has_many :connection_logs, class_name: '::ConnectionLog', as: :associated, dependent: :destroy

  has_associated_audits

  before_validation do
    self.school = family.school
  end

  after_initialize do
    self.school = family.school if !school_id? && family_id?
  end

  before_create do
    self.code = "#{family.code}-#{family.primary_students.count + 1}" unless code?
  end

  before_create :destroy_restore

  after_create :push_family_to_tep, if: -> { school.school_config.tep_integration? }

  before_save :update_races
  before_save :update_student_code
  before_save :set_origins, if: :origin_descriptors

  before_destroy :add_to_restore

  after_commit :edfi_school_year_stash, on: [:create, :update]

  after_save :cleanup_races, if: :race
  after_save :tep_update, if: -> { school.school_config.tep_integration? }

  validates :first_name, :last_name, presence: true, length: { maximum: 32 }
  validates :middle_name, length: { maximum: 24 }

  validate :valid_grade?

  accepts_nested_attributes_for :user, reject_if: proc { |a| a[:active].to_s.blank? }
  accepts_nested_attributes_for :state_number, :school_number, :indiana_settlement_number,
    :student_requirement, :nickname, allow_destroy: true

  scope :by_id, ->(ids) { where(id: ids) if ids }
  scope :by_family_ids, ->(ids) { where(family_id: ids).or(where(family_id_2: ids)) if ids }

  scope :by_valid_graduation_date, -> do
    where.not(graduation_year: [nil, 0], graduation_month: [nil, 0], graduation_day: [nil, 0])
  end

  scope :by_applicant, ->(school, flag) do
    return if flag.nil?

    school_year_id = school.find_or_build_admission_config.school_year_id
    query = joins(admission_applicants: :application)
      .where(admission_applications: { school_year_id: school_year_id })

    flag ? query : where.not(id: query)
  end

  scope :join_school_year_student, ->(school_year_id, status) do
    return if school_year_id.nil?

    query = <<~SQL
      INNER JOIN SchoolYearStudents
      ON SchoolYearStudents.StudentID = Students.StudentID
      AND SchoolYearStudents.SchoolYearID = #{school_year_id}
    SQL
    return joins(query) if status == :all

    current = joins("#{query} AND SchoolYearStudents.Current = true")
    return current if status == :current

    joins(query).where.not(id: current)
  end

  scope :by_grade, ->(grade, not_grade=false) do
    return if grade.blank?

    not_grade ? where.not(grade: grade) : where(grade: grade)
  end

  scope :by_student_year_grade, ->(grade) do
    return if grade.nil?

    joins(:school_year_students).where(SchoolYearStudents: { SYGrade: grade })
  end

  scope :by_school_year, ->(id) do
    return if id.nil?

    joins(:school_year_students).where(SchoolYearStudents: { SchoolYearID: id }).distinct
  end

  scope :by_billing_template, ->(template, without=false) do
    return unless template

    template_join = joins(billing_transaction_details: :template_transaction)

    if without
      where.not(id: template_join.merge(Billing::TemplateTransaction.with_template(template)))
    else
      template_join.merge(Billing::TemplateTransaction.with_template(template))
    end
  end

  scope :by_covid_screening_submissions, ->(submitted, date) do
    return if submitted.nil?

    if submitted
      joins(:covid_screenings).merge(Covid::Screening.by_date(date))
    else
      joins(<<~SQL).where(covid_screenings: { id: nil })
        LEFT JOIN covid_screenings
        ON covid_screenings.student_id = Students.StudentID
        AND covid_screenings.date = '#{date}'
      SQL
    end
  end

  scope :by_covid_temperature_submissions, ->(submitted, date) do
    return if submitted.nil?

    if submitted
      joins(:covid_temperatures).merge(Covid::Temperature.by_date(date))
    else
      beginning_of_day = date.to_date.beginning_of_day
      end_of_day = date.to_date.end_of_day

      joins(<<~SQL).where(covid_temperatures: { id: nil })
        LEFT JOIN covid_temperatures
        ON covid_temperatures.student_id = Students.StudentID
        AND covid_temperatures.recorded_at BETWEEN '#{beginning_of_day}' AND '#{end_of_day}'
      SQL
    end
  end

  scope :current_status, ->(school, current) do
    if current.present?
      current_ids = school&.current_year&.students
        &.where('SchoolYearStudents.Current = 1')
        &.pluck(:id)

      case current
      when :current
        where(id: current_ids)
      when :non_current
        where.not(id: current_ids)
      end
    end
  end

  scope :with_outstanding_balance, ->(balance) do
    return if balance.blank?

    joins(:accounting_transaction_details).where(accounting_transaction_details: { closed: false })
  end

  scope :left_join_school_year_students, ->(school_year) do
    joins(<<~SQL)
      LEFT JOIN SchoolYearStudents
      ON SchoolYearStudents.StudentID = Students.StudentID
      AND SchoolYearStudents.Current = 1
      AND SchoolYearStudents.SchoolYearID = #{school_year}
    SQL
  end

  scope :order_by_current, ->(by_current, school_year) do
    return unless by_current

    left_join_school_year_students(school_year).order('SchoolYearStudents.Current DESC')
  end

  scope :by_classroom, ->(classroom) do
    joins(:class_students).where(ClassStudents: { ClassID: classroom }) if classroom
  end

  scope :has_medical_alerts, ->(flag=false) do
    return unless flag

    includes(:student_medical).where.not(StudentMed: { AlertComments: [nil, ''] })
  end

  scope :has_allergies, ->(flag=false) do
    return unless flag

    includes(:student_medical).where.not(StudentMed: { Allergies: [nil, ''] })
  end

  scope :has_vaccine_exemption, ->(flag=false) do
    return unless flag

    left_joins(:nursing_vaccine_records, :student_medical)
      .where.not(StudentVaccineRecords: { Exempt: 0 })
      .or(Student.left_joins(:nursing_vaccine_records, :student_medical)
      .where.not(StudentMed: { Exempt: 0 }))
  end

  scope :by_categories, ->(ids) do
    joins(:categories).where(StudentCategories: { SCID: ids }) if ids
  end

  scope :with_user, -> { where.not(user_id: 0) }

  def self.ordered
    order(:last_name, :first_name)
  end

  def self.search(term, or_first_name=false)
    query = "concat(LastName, ', ', FirstName) LIKE :term"
    query += ' OR FirstName LIKE :term' if or_first_name

    where(query, term: "#{term}%")
  end

  def self.restore_backup(id)
    return if find_by(id: id)

    student = new(id: id)
    audit = student.audits.last
    return restore_from_sql(id) unless audit && audit.action.to_sym == :destroy

    associations = audit.audited_changes.delete(:restore_ids)

    transaction do
      student = new(audit.audited_changes)
      student.nullify_dependent_associations
      student.save

      associations.each do |association, value|
        next if value.blank?

        model_name = Student.reflect_on_association(association).class_name
        Array(value).each do |associated_id|
          audit = model_name.constantize.new(id: associated_id).audits&.last
          next if audit.blank?

          record = audit.auditable_type.constantize.new(audit.audited_changes)
          record.nullify_dependent_associations
          record.save(validate: false)
        end
      end
    end
  end

  def self.restore_from_sql(id)
    restore = Restore.find_by(primary_id: id, crud_class: 'Student')
    return if restore.nil?

    transaction do
      student = new(restore.model_params)
      student.nullify_dependent_associations
      student.save

      backup = restore.school.dir_path.join('Backups', "Student_#{id}.sql")
      return unless File.exist?(backup)

      sql_statements = File.read(backup).strip.split(';')
      records = extract_inserts_into_active_record(sql_statements)
      records.each(&:save)
    end
  end

  def current?
    !!school_year_students.find_by(school_year: school.school_years.current, current: 1)
  end

  def school_year_grade
    school_year = school.school_years.current
    school_year_students = self.school_year_students.where(school_year: school_year)
    school_year_student = school_year_students.order(current: :desc, entry_date: :desc).first
    school_year_student.nil? ? nil : school_year_student.grade
  end

  def most_recent_grade
    grades = school_year_students.order(:exit_date)
    return if grades.empty?

    grades.first.grade_id
  end

  def path_to_photo
    url = Rails.application.secrets.snap_api_url
    return if photo.blank?

    Pathname.new(url).join('Schools', school_id.to_s, self.class.table_name, photo)
  end

  def family_two
    return if family_id_2.zero?

    school.families.find(family_id_2)
  end

  def families
    [family, family_two].compact
  end

  def accounting_transaction_matrix(transaction)
    matrix = accounting_student_matrices.find do |item|
      item.subcategory_id == transaction.subcategory&.id
    end

    return [transaction] if matrix.blank? || matrix.primary == 100 || family_two.nil?

    transaction_two = family_two.accounting_transactions.build(
      memo: transaction.memo,
      posted_on: transaction.posted_on,
      action: transaction.action.to_sym,
      type: transaction.type,
      subcategory: transaction.subcategory,
      tag_ids: transaction.tag_ids,
      student: self
    )
    amounts = matrix.process_matrix(transaction.amount)

    transaction.amount = amounts[:one]
    transaction_two.amount = amounts[:two]

    transaction_detail = transaction.transaction_details.first
    transaction_detail.amount = amounts[:one]

    transaction_two_detail = transaction_two.transaction_details.first
    transaction_two_detail.amount = amounts[:two]

    transactions = []

    transactions << transaction unless transaction.amount.zero?
    transactions << transaction_two unless transaction_two.amount.zero?

    transactions
  end

  def outstanding_balance
    balance(:increase)
  end

  def unallocated_balance
    balance(:decrease)
  end

  def balance_by_subcategory(type)
    school.accounting_subcategories.map do |subcategory|
      total = if type == :computed
        computed_balance(subcategory.id)
      else
        balance(type, subcategory.id, true)
      end
      [subcategory, total]
    end.to_h
  end

  def computed_balance(subcategory_id)
    balance(:increase, subcategory_id, true) -
      balance(:decrease, subcategory_id, true)
  end

  def service_hours
    logs = service_logs
    {
      approved: logs.select(&:approved?).map(&:hours).reduce(0, &:+),
      pending: logs.select(&:pending?).map(&:hours).reduce(0, &:+),
      denied: logs.select(&:denied?).map(&:hours).reduce(0, &:+)
    }
  end

  def ceds_grade(grade=self.grade)
    return unless grade

    case grade
    when -9, -8
      'IT'
    when -7, -6, -5, -1
      'PR'
    when -4, -3, -2
      'PK'
    when 0
      'KG'
    when 14, 15, 16
      'PS'
    when 99
      nil
    else
      sprintf('%02d', grade)
    end
  end

  def sync_legacy_ids
    if external_id.present?
      additional_ids.find_or_initialize_by(code: :school).update(number: external_id)
    end

    return if state_id.blank? || additional_ids.where(code: :state).present?

    additional_ids.create(code: :state, number: state_id)
  end

  def sync_legacy_names
    return unless nick_name?
    return if names.where(code: :nickname).exists?

    names.create(code: :nickname, first_name: nick_name)
  end

  def cache_balances
    path = "student/#{id}/balance"
    data = {
      outstanding: outstanding_balance,
      unallocated: unallocated_balance
    }.to_json

    $redis.del(path)
    $redis.set(path, data)
  end

  def available_codes
    array = Array(1..10)
    ids = family.primary_students.where.not(id: id).pluck(:code).map { |c| c.split('-').last }
    ids.each { |i| array.delete(i.to_i) }
    array.map(&:to_s)
  end

  def available_ids(types=[])
    codes = StudentAdditionalId.codes.keys - (additional_ids.pluck(:code) - types)
    codes -= ['indiana_settlement'] unless school.ed_fi_system_indiana?
    codes
  end

  def available_names(types=[])
    StudentName.codes.keys - (names.pluck(:code) - types) - ['previous']
  end

  def available_state_programs(show_all=false)
    programs = EdFi::StateProgram.programs.keys
    return programs if show_all

    student_programs = ed_fi_state_programs
      .where(end_date: nil, school_year_id: school.school_years.current.id)
      .pluck(:program)
    programs - student_programs
  end

  def missing_required_identity_info
    fields = [:first_name, :last_name, :date_of_birth, :race_ids, :gender]
    [].tap do |missing|
      fields.each { |f| missing << f if send(f).blank? }
      missing << :ethnicity if unspecified?
      return missing if (family_id? || family_id_2?) && all_contact_families.primary.present?

      missing << :primary_contact
    end
  end

  private
    def balance(type, subcategory_id=nil, always_query=false)
      accounting_transaction_details
        .joins(:accounting_transaction)
        .open_transactions
        .with_type(type)
        .without_void(true)
        .with_subcategory(subcategory_id, always_query)
        .map(&:balance)
        .reduce(0, :+)
    end

    def add_to_restore
      school.restores.create!(associated: self)
    end

    def destroy_restore
      return unless restore

      restore.destroy
    end

    def update_races
      self.races = school.races.where(id: race) if race
    end

    def cleanup_races
      orphaned = race_students.pluck(:race_id) - school.races.pluck(:id)
      return if orphaned.empty?

      race_students.where(race_id: orphaned).destroy_all
    end

    def edfi_school_year_stash
      return unless school.ed_fi_system_wisconsin?
      return unless current_school_year_student

      Internal::Edfi::StashService.call(
        school,
        current_school_year_student.id,
        :save,
        current_school_year_student.legacy_class_name
      )
    end

    def update_student_code
      return if user.blank?
      return unless code_changed?

      user.update(username: code)
    end

    def set_origins
      return unless origin && school.ed_fi_system_wisconsin?

      # remove any origins that don't have corresponding race
      wisconsin_descriptors('ancestryEthnicOriginDescriptors').each do |descriptor|
        type = descriptor['codeValue'].split('-').first
        if (type == 'bl' && !race_ids.include?(2)) || (type == 'as' && !race_ids.include?(5))
          origin_descriptors.delete(descriptor['id'])
        end
      end

      # remove any American Indian origins when race is not selected
      tribal_ids = wisconsin_descriptors('tribalAffiliationDescriptors').map { |d| d['id'] }
      descriptors = race_ids.include?(4) ? origin_descriptors : origin_descriptors - tribal_ids

      origins = descriptors.map do |descriptor|
        send("#{origin}_origins").build(descriptor: descriptor)
      end

      send("#{origin}_origins=", origins)
    end

    def wisconsin_descriptors(descriptor)
      EdFi::Wisconsin::DescriptorService.call(
        school.school_years.current.id,
        descriptor: descriptor
      )
    end

    def indiana_descriptors(descriptor)
      EdFi::Indiana::Sandbox::DescriptorService.call(
        school.school_years.current.id,
        descriptor: descriptor
      )
    end

    def valid_grade?
      range = (school.first_grade_offered..school.last_grade_offered).to_a
      if !range.include?(grade)
        errors.add(:grade, 'is not valid for this school')
      end
    end

    def tep_update
      latest_school_year_student = school_year_students.order(entry_date: :desc).first
      return unless latest_school_year_student.present? &&
        (latest_school_year_student.entry_date >= Time.zone.today ||
        latest_school_year_student.school_year.current?)

      Tep::StudentService.call(school, id)
    end

    def push_family_to_tep
      Tep::StudentService.call(school, id)
      primary_contacts.each(&:save) # triggering callbacks to make contacts
    end
end
