class Family < Base::Family
  include Formattable
  include Validatable

  associations_for legacy: true do |a|
    a.belongs_to :school

    a.has_many :primary_students, foreign_key: :FamilyID, class_name: 'Student'
    a.has_many :secondary_students, foreign_key: :Family2ID, class_name: 'Student'
    a.has_many :contact_families, autosave: true
    a.has_many :class_student_enrollments
    a.has_many :student_enrollments
    a.has_many :school_year_families
    a.has_many :family_additional_values, autosave: true
    a.has_many :student_lunch_orders
    a.has_many :student_cafeteria_transactions
    a.has_many :family_transactions
    a.has_many :daycare_transactions
    a.has_many :student_item_orders
    a.has_many :parent_teacher_meetings
    a.has_many :parent_teacher_families

    a.has_one :family_medical, autosave: true
  end

  belongs_to :country, class_name: 'IsoCountry', optional: true
  belongs_to :country_2, class_name: 'IsoCountry', optional: true

  has_many :financial_aid_answers, foreign_key: :family_id, inverse_of: :family
  has_many :financial_aid_applications, foreign_key: :family_id, inverse_of: :family, dependent: :destroy
  has_many :financial_aid_waivers, foreign_key: :family_id, inverse_of: :family, dependent: :destroy
  has_many :all_students, ->(family) {
    unscope(:where).where('Students.FamilyID = :id OR Students.Family2ID = :id', id: family.id)
  }, class_name: 'Student', inverse_of: :family
  has_many :class_students, through: :all_students
  has_many :classrooms, through: :class_students
  has_many :class_lesson_plans, through: :all_students
  has_many :documents, through: :class_students
  has_many :news_articles, through: :class_students

  has_many :contacts, through: :contact_families
  has_many :primary_contacts, -> { primary }, through: :contact_families, source: :contact
  has_many :emergency_contacts, -> { emergency }, through: :contact_families, source: :contact
  has_many :primary_users, through: :primary_contacts, source: :user
  has_many :school_years, through: :school_year_families
  has_many :student_service_logs, class_name: 'Service::StudentLog',
    through: :all_students, source: :service_logs
  has_many :covid_screenings, through: :all_students
  has_many :class_grades, through: :all_students
  has_many :discipline_detentions, through: :all_students
  has_many :discipline_logs, through: :all_students
  has_many :nursing_logs, through: :all_students
  has_many :account_holds, dependent: :destroy
  has_many :connection_logs, class_name: '::ConnectionLog', as: :associated, dependent: :destroy

  has_one :user, -> { where(student_id: 0, contact_id: 0) }, inverse_of: :family,
    dependent: :destroy, autosave: true, foreign_key: :FamilyID
  has_one :module_config, through: :school, source: :family_module

  associations_for namespace: 'Admission' do |a|
    a.has_many :applicants
    a.has_many :families
    a.has_many :family_revisions
    a.has_many :medical_revisions
    a.has_many :contact_revisions, autosave: true
    a.has_many :family_additional_values

    a.has_many :applications, through: :applicants
  end

  associations_for namespace: 'Accounting' do |a|
    a.has_many :transactions
    a.has_many :increases
    a.has_many :decreases
    a.has_many :invoices
  end

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

  has_many :accounting_transaction_details, through: :accounting_transactions,
    source: :transaction_details, class_name: 'Accounting::TransactionDetail'

  has_associated_audits

  after_initialize :set_family_directory_defaults, if: :new_record?
  after_initialize :set_country_id

  before_validation :set_family_code, if: :new_record?

  before_save :update_legacy_country, if: -> { country_id_changed? || country_2_id_changed? }

  after_create :iterate_school_family_code

  after_save :edfi_sync

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

  scope :with_tags, ->(ids) {
    joins(admission_families: :tags).where(tags: { id: ids }) if ids.present?
  }

  scope :by_admissions, ->(flag, year) {
    return unless flag

    joins(admission_applicants: :application)
      .where(admission_applications: { school_year_id: year })
      .distinct
  }

  scope :current_status, ->(current) do
    if current.present?
      current_id = joins(:school_years)
        .where('SchoolYears.Current = 1')
        .pluck(:id)

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

  scope :with_outstanding_balance, ->(balance) do
    if balance.present?
      joins(:accounting_transactions)
        .where(accounting_transactions: { closed: false })
    end
  end

  scope :by_transaction_type, ->(type) do
    return unless type

    joins(:accounting_transactions)
      .merge(::Accounting::Transaction.send("only_#{type.to_s.pluralize}"))
  end

  scope :left_join_admission_payment, ->(status, school_year_id) do
    joins(<<~SQL)
      LEFT OUTER JOIN admission_families
      ON admission_families.family_id = Families.FamilyID
      AND admission_families.school_year_id = #{school_year_id}
    SQL
      .where(admission_families: { payment_status: status })
  end

  scope :by_application_school_year, ->(school_year_id) do
    joins(admission_applicants: :application)
      .where(admission_applications: { school_year_id: school_year_id })
      .distinct
  end

  def self.ordered
    order(:name)
  end

  def self.search(term)
    where('Families.Name like :term', term: "#{term}%")
  end

  def students(query=false)
    return primary_students + secondary_students unless query

    primary_students.or(secondary_students)
  end

  def current?
    !!school_year_families.find_by(school_year: school.school_years.current)
  end

  def activate_user_login
    if user.blank?
      last, first = name.split(', ')
      build_user(school: school, last_name: last, first_name: first)
    end

    user.active = true
  end

  def generate_student_code
    "#{code}-#{primary_students.count + 1}"
  end

  def primary_contact_emails
    contacts.where('FamilyParents.PrimaryParent is TRUE')
      .pluck(:email)
      .compact
      .reject(&:empty?)
  end

  def admission_editable?
    admission_year = school.admission_config&.school_year
    return false unless admission_year

    revision = admission_family_revisions.find_by(school_year: admission_year)
    medical_revision = admission_medical_revisions.find_by(school_year: admission_year)
    contact_revisions = admission_contact_revisions.where(school_year: admission_year)

    revisions = [revision, medical_revision].compact + contact_revisions
    return true if revisions.empty?

    !revisions.all?(&:applied)
  end

  def destroy_admission_data
    admission_family_revisions.each(&:destroy!)
    admission_medical_revisions.each(&:destroy!)
    admission_contact_revisions.each(&:destroy!)
    admission_family_additional_values.each(&:destroy!)
  end

  def outstanding_balance
    balance(:increase)
  end

  def unallocated_balance
    balance(:decrease)
  end

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

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

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

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

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

  private
    def set_family_directory_defaults
      school.admission_registration_fields.by_family_directory.each do |registration_field|
        field = Admission::RegistrationField::FAMILY_DIRECTORY_FIELDS[registration_field.field]
        self[field] = registration_field.display
      end
    end

    def edfi_sync
      return unless school.ed_fi_system_wisconsin? && school.student_address_opt?

      Internal::Edfi::FamilyService.call(school, self)
    end

    def set_family_code
      number = school.family_code.to_s
      self.code = name[/[^, ]*/].gsub(/[^A-Za-z]/, '')[0..2].upcase.ljust(3, '-') + number
    end

    def iterate_school_family_code
      school.update(family_code: school.family_code + 1)
    end

    def update_legacy_country
      self.Country1 = country&.name || ''
      self.Country2 = country_2&.name || ''
    end

    def set_country_id
      self.country_id = school.country_id unless country_id
      self.country_2_id = school.country_id unless country_2_id
    end
end
