class SchoolYear < Base::SchoolYear
  associations_for legacy: true do |a|
    a.belongs_to :school

    a.has_many :attendances, dependent: :destroy
    a.has_many :class_student_enrollments, dependent: :destroy
    a.has_many :student_enrollments, dependent: :destroy
    a.has_many :school_days, dependent: :destroy
    a.has_many :school_district_imports, dependent: :destroy
    a.has_many :school_year_families, dependent: :destroy
    a.has_many :school_year_students, dependent: :destroy
    a.has_many :school_year_sessions, dependent: :destroy
    a.has_many :student_lunch_orders, dependent: :destroy
    a.has_many :student_documents, dependent: :nullify
    a.has_many :lunch_days, dependent: :destroy
    a.has_many :student_transcripts, dependent: :destroy
    a.has_many :student_item_orders, dependent: :destroy
  end

  has_many :additional_years, ->(record) { where.not(id: record.id) }, class_name: '::SchoolYear',
    foreign_key: :SchoolID, primary_key: :SchoolID
  has_many :families, through: :school_year_families
  has_many :students, through: :school_year_students
  has_many :student_activity_students, foreign_key: :SchoolYearID, inverse_of: :school_year,
    dependent: :destroy
  has_many :service_student_logs, inverse_of: :school_year, dependent: :destroy,
    class_name: 'Service::StudentLog'
  has_many :service_family_logs, inverse_of: :school_year, dependent: :destroy,
    class_name: 'Service::FamilyLog'
    has_many :financial_aid_applications, inverse_of: :school_year, dependent: :destroy
    has_many :financial_aid_waivers, inverse_of: :school_year, dependent: :destroy

  associations_for namespace: 'Admission' do |a|
    a.has_many :applications, dependent: :destroy
    a.has_many :families, dependent: :destroy
    a.has_many :family_revisions, dependent: :destroy
    a.has_many :medical_revisions, dependent: :destroy
    a.has_many :contact_revisions, dependent: :destroy
    a.has_many :family_additional_values, dependent: :destroy

    a.has_many :applicants, through: :applications

    a.has_one :config, dependent: :destroy
  end

  associations_for namespace: 'Billing' do |a|
    a.has_many :plans, dependent: :destroy
    a.has_many :templates, dependent: :destroy
  end

  associations_for namespace: 'EdFi' do |a|
    a.has_many :ids, dependent: :destroy
    a.has_many :logs, dependent: :destroy
    a.has_many :state_programs, dependent: :destroy
    a.has_many :accommodations, dependent: :destroy
  end

  associations_for namespace: 'Service' do |a|
    a.has_many :student_histories, dependent: :destroy
  end

  associations_for namespace: 'StateReporting' do |a|
    a.has_one :config, dependent: :destroy
  end

  delegate :school_config, to: :school

  before_create :create_school_days, unless: -> { school_config.sessions? }

  before_update :set_cycle_days, if: :cycle_days_changed?

  validates :academic_year, uniqueness: { scope: :school }
  validates :name, :academic_year, presence: true
  validates :q1_start, :q2_start, :end, presence: true, unless: -> { school_config.sessions? }
  validates :q3_start, presence: true,
    if: -> { school_config.semesters? || school_config.trimesters? }
  validates :q4_start, presence: true, if: -> { school_config.semesters? }

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

  scope :ordered, -> { order(academic_year: :desc) }

  def self.current
    find_by(current: 1)
  end

  def date_in_range(date)
    return false if date.nil?

    date >= q1_start && date <= self.end
  end

  def closest_term_date(term=:quarter)
    return q1_start if term == :full_year

    # Find session start date in SchoolYearSessions
    if school_config.sessions?
      return school_config.current_year_sessions.find_by(sequence: closest_quarter).start
    end

    # semester and trimester schools can always use quarter
    if term == :quarter || ['trimester', 'semester'].include?(school_config.school_year_type)
      return self["q#{closest_quarter}_start"]
    end

    # Logic for semesters schools
    current_semester == 1 ? q1_start : q3_start
  end

  def current_semester
    quarter = closest_school_day.quarter
    return quarter if school_config.sessions? || school_config.trimesters?

    [1, 2].include?(quarter) ? 1 : 2
  end

  def closest_quarter(date=Date.today)
    closest_school_day(date).quarter
  end

  def semester_date_range(semester=current_semester)
    quarter = semester == 2 && school_config.semesters? ? 3 : semester
    first_day = school_days.by_quarter(quarter).ordered.limit(1).first

    next_quarter = school_config.trimesters? ? quarter : quarter + 1
    last_day = school_days.by_quarter(next_quarter).order(date: :desc).limit(1).first

    first_day.date..last_day.date
  end

  def begin_and_end_days_by_semester
    dates = {}
    school_config.semesters.each_key do |semester|
      dates[semester] = {}
      quarter = semester
      quarter += 1 if (school_config.semester? || school_config.semesters?) && quarter != 1
      dates[semester][:begin] = school_days.ordered.find_by(quarter: quarter)
      quarter += 1 if school_config.semester? || school_config.semesters?
      dates[semester][:end] = school_days.ordered(:desc).find_by(quarter: quarter)
    end
    dates
  end

  def begin_and_end_days_by_quarter
    dates = {}
    school_config.quarters.each_key do |quarter|
      dates[quarter] = {}
      dates[quarter][:begin] = school_days.ordered.find_by(quarter: quarter)
      dates[quarter][:end] = school_days.ordered(:desc).find_by(quarter: quarter)
    end
    dates
  end

  private
    def closest_school_day(date=Date.today)
      past = school.school_days.where('Date <= ?', Time.zone.now.to_date).order(date: :desc).first
      return past if past&.date == date

      future = school.school_days.where('Date > ?', Time.zone.now.to_date).order(:date).first
      return past if future.blank?

      [past, future].compact.min_by { |s| (s.date.to_time - date.to_time).abs }
    end

    def create_school_days
      school_config.quarters.each_key do |quarter|
        quarter_date = send("q#{quarter}_start")
        is_last_quarter = school_config.quarters.size == quarter
        next_date = is_last_quarter ? self.end : send("q#{quarter + 1}_start")
        days_in_between = (next_date - quarter_date).to_i

        days_in_between.times do |index|
          day = quarter_date + index.days
          next if day.saturday? || day.sunday?

          school_days.build(
            date: day,
            quarter: quarter,
            hours: school.classroom_hours,
            half_day: false
          )
        end
      end
    end

    def dates_in_range
      valid_years = [academic_year, academic_year - 1]

      school_config.quarters.each_key do |quarter|
        prop = "q#{quarter}_start"
        next if send(prop).nil? || valid_years.include?(send(prop).year)

        errors.add(prop, 'enter a date within the school year range')
      end
      return if self.end.nil? || valid_years.include?(self.end.year)

      errors.add(:end, 'enter a date within the school year range')
    end

    def overlapping_quarters
      quarters = school_config.quarters
      last_quarter = "q#{quarters.keys.last}_start"
      last_quarter_label = quarters.delete(quarters.keys.last)

      quarters.each do |quarter, label|
        prop = "q#{quarter}_start"
        next_prop = "q#{quarter + 1}_start"
        next if send(prop).nil? || send(next_prop).nil? || send(prop) < send(next_prop)

        errors.add(next_prop, "cannot overlap with #{label}")
      end
      return if send(last_quarter).nil? || self.end.nil? || send(last_quarter) < self.end

      errors.add(:end, "cannot overlap with #{last_quarter_label}")
    end

    def overlapping_records
      return if q1_start.nil? || self.end.nil? || q1_start >= self.end

      additional_years.each do |year|
        if between_dates?(q1_start, year)
          errors.add(:q1_start, 'cannot overlap an existing school year record')
        end

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

    def between_dates?(date, sibling)
      date >= sibling.q1_start && date < sibling.end
    end

    def set_cycle_days
      counter = cycle_days? ? 1 : nil
      school_days.ordered.each do |day|
        day.update(cycle_day: counter)
        next if counter.nil?

        counter = counter == cycle_days ? 1 : counter + 1
      end
    end
end
