class SchoolYearStudent < ApplicationRecord
  include Base::SchoolYearStudent
  include Edfiable

  attr_accessor :skip_after_update

  associations_for legacy: true do |a|
    a.belongs_to :school_year
    a.belongs_to :student
  end

  belongs_to :grade, ->(school) { where(school: school) }, class_name: 'SchoolGrade',
    primary_key: :grade, foreign_key: :SYGrade, optional: true

  has_many :additional_years,
    ->(record) { where(student: record.student.id).where.not(id: record.id) },
    class_name: 'SchoolYearStudent', foreign_key: 'SchoolYearID', primary_key: 'SchoolYearID'

  has_one :state_id, -> { where(code: :state) }, class_name: 'StudentAdditionalId',
    foreign_key: :associated_id, primary_key: :StudentID

  associations_for namespace: 'EdFi' do |a|
    a.has_many :logs, as: :associated, inverse_of: :associated
  end

  has_one :ed_fi_choice, -> { where(extension_id: 48) }, foreign_key: :TargetID,
    class_name: '::EdFi::ExtensionValue', inverse_of: :school_year_student

  delegate :school, to: :school_year
  delegate :date_in_range, to: :school_year

  accepts_nested_attributes_for :ed_fi_choice

  scope :by_grades, ->(grade_ids) { where(grade_id: grade_ids) if grade_ids.present? }

  after_initialize :set_entry_date, if: :new_record?, unless: -> { school.school_config.sessions? }

  before_create :set_school_id
  before_create :set_grade
  before_create :set_exit_date, unless: -> { school.school_config.sessions? }

  before_save :sync_grade, if: [:grade_id_changed?, :current?]
  before_save :unenroll_from_classes, if: :current_changed?

  before_destroy -> { unenroll_from_classes(true) }

  after_update :update_edfi

  after_save :mark_active_in_tep, if: [:saved_change_to_current?]

  after_commit :edfi_student_stash

  validates :entry_date, presence: true, unless: -> { school.school_config.sessions? }

  validate :dates_in_range, unless: -> { school.school_config.sessions? }
  validate :overlapping_records, unless: -> { school.school_config.sessions? }
  validate :valid_grade?

  scope :by_ids, ->(ids) { where(id: ids) if ids }

  def delete_edfi_record
    EdFi::Indiana::Sandbox::StudentSchoolAssociationService
      .new(school_year_id, { id: edfi_id }).delete
  end

  def set_exit_date
    self.exit_date = Time.current unless exit_date || current?

    if exit_date && (exit_date <= entry_date || !school_year.date_in_range(exit_date))
      self.exit_date = school_year.end
    end
  end

  private
    def set_school_id
      self.school_id = school_year.school_id
    end

    def set_entry_date
      self.entry_date = Time.current unless entry_date
      self.entry_date = school_year.q1_start unless school_year.date_in_range(entry_date)
    end

    def set_grade
      self.grade_id = school.first_grade_offered unless grade_id
    end

    def edfi_student_stash
      return unless school.ed_fi_system?
      return unless school_year.academic_year == school.current_year.academic_year - 1

      data = { year: school_year_id }
      Internal::Edfi::StashService.call(school, student.id, :save, student.legacy_class_name, data)
      Internal::Edfi::StashService.call(school, id, :save, legacy_class_name, data)
    end

    def dates_in_range
      unless date_in_range(entry_date)
        errors.add(:entry_date, 'enter a date within the school year range.')
      end

      if !date_in_range(exit_date) && exit_date
        errors.add(:exit_date, 'enter a date within the school year range.')
      end
    end

    def overlapping_records
      additional_years.each do |year|
        if between_dates?(entry_date, year)
          errors.add(:entry_date, 'cannot overlap an existing enrollment record.')
        end

        next if exit_date.nil? && entry_date > year.entry_date

        if between_dates?(exit_date, year)
          errors.add(:exit_date, 'cannot overlap an existing enrollment record.')
        end
      end
    end

    def between_dates?(date, sibling)
      date >= sibling.entry_date && (sibling.exit_date.nil? || date < sibling.exit_date)
    end

    def update_edfi
      return if skip_after_update
      return unless school.ed_fi_system == 'IN'
      return unless school_year.current?
      return unless school_year.state_reporting_config&.enabled

      EdFi::Indiana::Sandbox::StudentSchoolAssociationService.call(school_year_id, { id: id })
    end

    def sync_grade
      return unless school.current_year.id == school_year_id

      student.update(grade: grade_id)
    end

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

    def unenroll_from_classes(destroyed=false)
      return unless destroyed || !current?
      return unless school_year.current?

      student.class_students.destroy_all
    end

    def mark_active_in_tep
      return unless current?
      return unless school.school_config.tep_integration?
      return unless school_year.current?

      # Send students as active when enrolled in current school year
      Tep::StudentService.call(school, student_id, status: :active)
    end
end
