class School < ApplicationRecord
  include Base::School

  enum site_type: { customer: 0, demo: 1, reporting: 2, internal: 3 }
  enum site_environment: { production: 0, staging: 1 }
  enum attendance_type: { hourly: 1, daily: 2 }
  enum ed_fi_system: { indiana: 'IN', wisconsin: 'Wis' }, _prefix: true

  belongs_to :country, class_name: 'IsoCountry'
  belongs_to :admin_user, class_name: 'Employee', foreign_key: :PrincipalUID, optional: true,
    inverse_of: :school

  associations_for legacy: true do |a|
    a.has_many :school_years
    a.has_many :school_days
    a.has_many :classrooms
    a.has_many :class_lesson_plans, keys: :ClassLessonID
    a.has_many :users
    a.has_many :employees
    a.has_many :employee_category_groups
    a.has_many :employee_categories
    a.has_many :positions
    a.has_many :employee_types
    a.has_many :employee_additional_groups
    a.has_many :employee_additional_fields
    a.has_many :families
    a.has_many :students
    a.has_many :category_students
    a.has_many :contacts
    a.has_many :school_district_imports
    a.has_many :student_lunch_orders
    a.has_many :student_milk_orders
    a.has_many :transcript_templates
    a.has_many :student_medicals
    a.has_many :class_grades
    a.has_many :class_grade_categories
    a.has_many :class_seatings
    a.has_many :class_student_enrollments
    a.has_many :class_teachers
    a.has_many :student_enrollments
    a.has_many :school_year_sessions
    a.has_many :pan_messages
    a.has_many :pan_recipients
    a.has_many :social_media, class_name: 'SocialMedia'
    a.has_many :events
    a.has_many :lunch_meals
    a.has_many :news_articles
    a.has_many :news_readers
    a.has_many :attendances
    a.has_many :attendance_types, class_name: '::Attendance::Type'
    a.has_many :documents
    a.has_many :document_folders
    a.has_many :family_additional_groups
    a.has_many :family_additional_fields
    a.has_many :family_additional_choices
    a.has_many :sessions
    a.has_many :student_cafeteria_transactions
    a.has_many :periods
    a.has_many :user_oauths
    a.has_many :requirement_labels
    a.has_many :departments
    a.has_many :school_gpa_scales
    a.has_many :school_state_classrooms
    a.has_many :student_transcripts
    a.has_many :colleges
    a.has_many :blip_messages
    a.has_many :class_enrollments
    a.has_many :lunch_cycles
    a.has_many :lunch_items
    a.has_many :student_item_orders
    a.has_many :parent_teacher_meetings
    a.has_many :parent_teacher_dates
    a.has_many :parent_teacher_families
    a.has_many :student_habits
    a.has_many :student_habit_groups
    a.has_many :student_habit_values
    a.has_many :parent_teacher_users
    # Legacy accounting accounts
    a.has_many :family_accounts

    a.has_one :customer_wizard, autosave: true
    a.has_one :school_config
    a.has_one :disabled_family_login
    a.has_one :enrollment_config
    a.has_one :owned_school_district, inverse_of: :office, class_name: 'SchoolDistrict'
    a.has_one :school_district_school
    a.has_one :school_state, foreign_key: 'SchoolID'
    a.has_one :family_profile_config
    a.has_one :ed_fi_credential, class_name: 'EdFi::Credential'
    a.has_one :classroom_config
    a.has_one :transcript_config
    a.has_one :clever_config, class_name: 'Clever::Config'
    a.has_one :class_grade_config
  end

  has_many :financial_aid_school_questions, dependent: :destroy
  has_many :financial_aid_questions, through: :financial_aid_school_questions, source: :question

  has_many :snapshots, inverse_of: :school, dependent: :destroy, class_name: '::SchoolSnapshot'
  has_many :courses, inverse_of: :school, dependent: :destroy
  has_many :companies, class_name: 'Company', inverse_of: :school, foreign_key: :SchoolID,
    dependent: :destroy
  has_many :school_tokens
  has_many :school_grades, autosave: true, dependent: :destroy
  has_many :tags
  has_many :module_configs
  has_many :restores, dependent: :destroy
  has_many :contact_families, through: :families
  has_many :class_students, through: :students
  has_many :additional_ids, class_name: '::SchoolAdditionalId', foreign_key: :associated_id,
    as: :associated, dependent: :destroy, inverse_of: :school
  has_many :teachers, -> { distinct }, through: :class_teachers, source: :employee
  has_many :oauth_applications, class_name: 'Oauth::Application', as: :owner,
    dependent: :destroy
  has_many :ed_fi_logs, through: :school_years
  has_many :student_category_groups, -> { order(:name) }, foreign_key: :SchoolID,
    dependent: :destroy, inverse_of: :school
  has_many :student_categories, -> { order(:name) }, foreign_key: :SchoolID, dependent: :destroy,
    inverse_of: :school
  has_many :employee_positions, through: :employees, source: :positions
  has_many :student_additional_groups, dependent: :destroy,
    inverse_of: :school
  has_many :student_additional_fields, dependent: :destroy, inverse_of: :school
  has_many :student_additional_values, dependent: :destroy, inverse_of: :school
  has_many :student_additional_ids, through: :students, source: :additional_ids
  has_many :permission_users, through: :users
  has_many :student_types, dependent: :destroy
  has_many :class_subjects, through: :classrooms
  has_many :school_year_students, through: :school_years
  has_many :school_year_families, through: :school_years
  has_many :forms, dependent: :destroy, inverse_of: :school
  has_many :contact_category_groups, foreign_key: :SchoolID, dependent: :destroy,
    inverse_of: :school
  has_many :contact_categories, foreign_key: :SchoolID, dependent: :destroy, inverse_of: :school
  has_many :contact_additional_fields, dependent: :destroy, inverse_of: :school
  has_many :contact_additional_groups, dependent: :destroy, inverse_of: :school
  has_many :company_additional_fields, dependent: :destroy, inverse_of: :school
  has_many :company_additional_groups, dependent: :destroy, inverse_of: :school
  has_many :company_category_groups, foreign_key: :SchoolID, dependent: :destroy,
    inverse_of: :school
  has_many :company_categories, foreign_key: :SchoolID, dependent: :destroy, inverse_of: :school
  has_many :student_activities, foreign_key: :SchoolID, dependent: :destroy, inverse_of: :school
  has_many :student_activity_students, foreign_key: :SchoolID, dependent: :destroy,
    inverse_of: :school
  has_many :class_assignments, through: :classrooms
  has_many :email_addresses, dependent: :destroy
  has_many :primary_contacts, -> { primary }, through: :contact_families, source: :contact
  has_many :connection_logs, class_name: '::ConnectionLog', as: :associated, dependent: :destroy
  has_many :financial_aid_applications, through: :school_years, dependent: :destroy

  has_one :financial_aid_config, dependent: :destroy
  has_one :ed_fi_indiana_environment, inverse_of: :school, class_name: '::EdFi::IndianaEnvironment',
    dependent: :destroy
  has_one :apple_manager_config, inverse_of: :school, dependent: :destroy
  has_one :school_district, through: :school_district_school
  has_one :school_branding
  has_one :employee_module
  has_one :family_module
  has_one :student_module
  has_one :current_year, -> { where(current: true) }, class_name: :SchoolYear, inverse_of: :school
  has_one :state_id, -> { where(code: :school) }, class_name: '::SchoolAdditionalId',
    foreign_key: :associated_id, inverse_of: :school
  has_one :payjunction_config, -> { payjunction }, class_name: :AccountPaymentConfig,
    inverse_of: :school, foreign_key: :SchoolID
  has_one :parent_teacher_config, inverse_of: :school, dependent: :destroy

  associations_for namespace: 'Admission' do |a|
    a.has_many :agreements
    a.has_many :applications
    a.has_many :attachments
    a.has_many :checkboxes
    a.has_many :contact_revision_fields
    a.has_many :documents
    a.has_many :essays
    a.has_many :family_additional_fields
    a.has_many :family_revision_fields
    a.has_many :medical_revision_fields
    a.has_many :registrations
    a.has_many :registration_fields
    a.has_many :statuses
    a.has_many :tags

    a.has_many :applicants, through: :applications
    a.has_many :additional_fields, through: :applications
    a.has_many :application_grades, through: :applications

    a.has_one :config
  end

  has_many :admission_application_grades, through: :admission_applications,
    source: :application_grades, class_name: '::Admission::ApplicationGrade'
  has_many :admission_applicant_documents, through: :admission_applicants,
    source: :applicant_documents, class_name: '::Admission::ApplicantDocument'
  has_many :admission_additional_values, through: :student_additional_fields
  has_many :admission_family_additional_values, through: :family_additional_fields

  has_many :accounting_transactions, through: :families
  has_many :accounting_increases, through: :families
  has_many :accounting_decreases, through: :families
  has_many :student_languages, through: :students, source: :language
  has_many :account_holds, through: :families

  associations_for namespace: 'Accounting' do |a|
    a.has_many :categories
    a.has_many :tags
    a.has_many :invoices

    a.has_many :subcategories, through: :categories
    a.has_many :transaction_details, through: :transactions

    a.has_one :cafeteria_config
    a.has_one :invoice_config
  end

  associations_for namespace: 'Billing' do |a|
    a.has_many :templates
  end

  has_many :billing_plans, through: :students
  has_many :billing_transaction_days, through: :students

  associations_for namespace: 'Communication' do |a|
    a.has_many :batch_emails
    a.has_many :notification_users
    a.has_many :sparkpost_configs, inverse_of: :school

    a.has_one :smtp_config
    a.has_one :config
  end

  has_one :communication_sparkpost_sending_config, -> { sending },
    class_name: '::Communication::SparkpostConfig', inverse_of: :school
  has_one :communication_sparkpost_bounce_config, -> { bounce },
    class_name: '::Communication::SparkpostConfig', inverse_of: :school
  has_one :communication_sparkpost_tracking_config, -> { tracking },
    class_name: '::Communication::SparkpostConfig', inverse_of: :school

  associations_for namespace: 'Covid' do |a|
    a.has_many :form_sections

    a.has_one :config
  end

  has_many :covid_screenings, through: :students
  has_many :covid_employee_screenings, through: :employees, source: :covid_screenings
  has_many :covid_temperatures, through: :students
  has_many :covid_employee_temperatures, through: :employees, source: :covid_temperatures
  has_many :covid_form_questions, through: :covid_form_sections, source: :questions
  has_many :covid_form_displays, through: :covid_form_sections, source: :displays
  has_many :covid_form_options, through: :covid_form_questions, source: :options

  associations_for namespace: 'Discipline', legacy: true do |a|
    a.has_many :groups
    a.has_many :violations
    a.has_many :student_logs
    a.has_many :student_detentions

    a.has_one :config
  end

  associations_for namespace: 'EdFi' do |a|
    a.has_many :course_error_logs, as: :associated, inverse_of: :associated,
      class_name: '::EdFi::Log'
  end

  associations_for namespace: 'Facility', legacy: true do |a|
    a.has_many :buildings
    a.has_many :locations
    a.has_many :rooms
  end

  associations_for namespace: 'Google' do |a|
    a.has_many :domains

    a.has_one :config
  end

  associations_for namespace: 'Library' do |a|
    a.has_many :bibs
    a.has_many :contributors
    a.has_many :holdings
    a.has_many :item_conditions
    a.has_many :item_types
    a.has_many :locations
    a.has_many :publishers
  end

  associations_for namespace: 'Nursing' do |a|
    a.has_many :complaints
    a.has_many :logs
    a.has_many :medications
    a.has_many :prescriptions
    a.has_many :prescription_distributions
    a.has_many :student_medications
    a.has_many :vaccine_configs, foreign_key: :SchoolID
    a.has_many :vaccine_records
  end

  has_many :nursing_screenings, through: :students

  associations_for namespace: 'Paya' do |a|
    a.has_one :config
  end

  associations_for namespace: 'Payjunction' do |a|
    a.has_one :credential

    a.has_many :accounts
    a.has_many :terminals
  end

  associations_for namespace: 'Service' do |a|
    a.has_one :requirement, foreign_key: :SchoolID, inverse_of: :school
    a.has_one :setting, dependent: :destroy

    a.has_many :groups, foreign_key: :SchoolID, inverse_of: :school
    a.has_many :student_histories
  end

  has_many :service_opportunities, through: :service_groups, source: :opportunities
  has_many :state_reporting_configs, through: :school_years
  has_many :service_family_logs, through: :families, source: :service_logs
  has_many :service_student_logs, through: :students, source: :service_logs
  has_many :audits, through: :users
  has_many :sycamore_storage_attachments
  has_many :contact_call_notes, through: :contacts, source: :call_notes

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

    a.has_many :cycles, dependent: :destroy
    a.has_many :entries, dependent: :destroy
    a.has_many :requests, dependent: :destroy
    a.has_many :tasks, dependent: :destroy
  end

  has_many :school_district_students, dependent: :destroy
  has_many :school_district_users, dependent: :destroy

  has_one :stripe_connect_account, -> { where(model: 'App\\Models\\School') }, class_name: 'StripeConnectMapping', foreign_key: :model_id,
    inverse_of: :school, dependent: :destroy

  after_initialize -> { self.country_id = 236 }, if: -> { country_id.nil? }

  before_save :mark_grades_active
  before_save :update_legacy_country, if: -> { country_id_changed? }
  before_save :reset_paya_environment, if: -> { internal? && paya_config }
  before_save :reset_payjunction_environment, if: -> { internal? && payjunction_config }

  after_create :create_directories

  after_save :sync_legacy_grades
  after_save :update_users

  after_update :create_school_district, if: -> { school_district_office? }
  after_update :delete_ed_fi_credential, unless: :active?

  accepts_nested_attributes_for :school_state, allow_destroy: true
  accepts_nested_attributes_for :school_grades, update_only: true
  accepts_nested_attributes_for :school_config

  validates :motto, length: { maximum: 255 }
  validates :time_zone, :website, length: { maximum: 64 }
  validates :name, presence: true, length: { maximum: 48 }
  validates :address, length: { maximum: 40 }
  validates :address_ext, :city, :state, :country, length: { maximum: 32 }
  validates :zip, length: { maximum: 10 }
  validates :short_name, length: { maximum: 8 }
  validates :classroom_hours, numericality: { greater_than_or_equal_to: 0 }

  scope :ordered, -> { order(:name) }
  scope :by_active, ->(flag) { where(active: flag) unless flag.nil? }
  scope :is_district, ->(flag) { where(school_district_office: flag) unless flag.nil? }
  scope :by_site_types, ->(types) { where(site_type: types) if types }

  def set_transaction_base(action)
    if [:charge, :refund].include?(action)
      accounting_increases
    elsif [:payment, :credit].include?(action)
      accounting_decreases
    else
      accounting_transactions
    end
  end

  def admission_school_year
    id = find_or_build_admission_config.school_year_id
    id.nil? ? school_years.current : admission_config.school_year
  end

  def generate_family_code_and_save(name)
    num = family_code.to_s
    self.family_code += 1
    save
    name[/[^, ]*/].gsub(/[^A-Za-z]/, '')[0..2].upcase.ljust(3, '-') + num
  end

  def dir_path
    Pathname.new(ENV['PHP_ROOT']).join('Schools', id.to_s)
  end

  def tmp_path
    Pathname.new(ENV['PHP_ROOT']).join('Downloads', id.to_s, 'tmp')
  end

  def grades
    return school_grades.active if school_grades.present?

    file = dir_path.join('grades.inc')
    legacy_grades = school_grades.legacy_defaults

    if File.exist?(file)
      saved_grades = File.read(file).scan(/'([^']*)'/).flatten.in_groups_of(2).to_h
      legacy_grades.merge!(saved_grades)
    end

    edfi_file = dir_path.join('edfigrades.json')
    edfi_map = {}
    if File.exist?(edfi_file)
      edfi_map = JSON.parse(File.read(edfi_file)) || {}
      # Convert array into hash for consistency
      edfi_map = edfi_map.map.with_index { |a, i| [i.to_s, a] }.to_h if edfi_map.is_a?(Array)
    end

    descriptors = ::EdFi::Descriptor.by_grade_levels(self).map { |d| [d.code, d.key] }.to_h

    school_grades.legacy_mapping.each do |grade, legacy_id|
      school_grades.build(
        grade: grade,
        name: legacy_grades["GL_#{legacy_id}"],
        external: legacy_grades["EGL_#{legacy_id}"],
        edfi_grade: descriptors[edfi_map[grade.to_s]],
        active: (first_grade_offered..last_grade_offered).cover?(grade)
      )
    end

    save
    school_grades.active
  end

  def create_directories
    return if File.exists?("#{ENV['PHP_ROOT']}/Schools/#{id}")

    FileUtils.mkdir("#{ENV['PHP_ROOT']}/Schools/#{id}")
    FileUtils.mkdir("#{ENV['PHP_ROOT']}/Schools/#{id}/Email")
    FileUtils.mkdir("#{ENV['PHP_ROOT']}/Schools/#{id}/Users")
    FileUtils.mkdir("#{ENV['PHP_ROOT']}/Downloads/#{id}")
  end

  def destroy_directories
    FileUtils.rm_rf("#{ENV['PHP_ROOT']}/Schools/#{id}")
    FileUtils.rm_rf("#{ENV['PHP_ROOT']}/Downloads/#{id}")
  end

  def races
    races = self.school_state&.state&.races
    races = Race.for_usa(us_department_of_education) if races.nil?
    races
  end

  def languages
    languages = school_state&.state&.languages
    languages = Language.for_default if languages.nil?
    languages
  end

  def weather
    Accuweather::LocationService.call(self) unless weather_id?

    Accuweather::WeatherService.call(weather_id)
  end

  def sync_legacy_ids
    return unless additional_ids.empty?

    if legacy_state_id.present? && additional_ids.where(code: :school).empty?
      additional_ids.create(code: :school, number: legacy_state_id)
    end

    if district_code.present? && additional_ids.where(code: :district).empty?
      additional_ids.create(code: :district, number: district_code)
    end

    return if federal_code.blank? || additional_ids.where(code: :federal).present?

    additional_ids.create(code: :federal, number: federal_code)
  end

  def grades_hash
    school_grades.where(active: true).pluck(:grade, :name).to_h
  end

  def available_ids(types=[])
    codes = SchoolAdditionalId.codes.keys - (additional_ids.pluck(:code) - types)
    codes -= ['nces', 'act', 'ceeb']
    codes -= ['lea'] unless ed_fi_system_wisconsin?
    codes
  end

  def indiana_environment_service
    self.find_or_build_ed_fi_indiana_environment.environment_service
  end

  private
    def mark_grades_active
      school_grades.each do |grade|
        grade.active = (first_grade_offered..last_grade_offered).cover?(grade.grade)
      end
    end

    def sync_legacy_grades
      return unless school_grades.any? { |g| g.saved_changes.present? }

      legacy_mapping = SchoolGrade.legacy_mapping
      File.open(dir_path.join('grades.inc'), 'w') do |file|
        file << "<? \n"
        school_grades.active.each do |grade|
          file << "define('GL_#{legacy_mapping[grade.grade]}', '#{grade.name}');\n"
          file << "define('EGL_#{legacy_mapping[grade.grade]}', '#{grade.external}');\n"
        end
        file << '?>'
      end

      return unless ed_fi_system?

      File.open(dir_path.join('edfigrades.json'), 'w') do |file|
        descriptors = ::EdFi::Descriptor.by_grade_levels(self).map { |d| [d.key, d.code] }.to_h
        edfi_map = school_grades.active.map { |g| [g.grade.to_s, descriptors[g.edfi_grade]] }.to_h
        file << edfi_map.to_json
      end
    end

    def update_users
      Notification::ModuleConfigJob.perform_async(id)
    end

    def update_legacy_country
      self.Country = decorate.country_name
    end

    def reset_paya_environment
      paya_config.sandbox!
    end

    def reset_payjunction_environment
      payjunction_config.sandbox!
    end

    def delete_ed_fi_credential
      ed_fi_credential&.destroy
    end

    def create_school_district
      return if school_district.present?

      self.school_district = SchoolDistrict.create(name: name, school_id: id)
    end
end
