class User < ApplicationRecord
  include Base::User
  include Personable
  include Formattable
  include Validatable

  enum gender: { no_gender: 0, male: 1, female: 2, non_binary: 3 }

  audited except: [:encrypted_password, :StateID, :EmployeeID]

  associations_for legacy: true do |a|
    a.belongs_to :school
    a.belongs_to :family, optional: true, inverse_of: :user
    a.belongs_to :student, optional: true, inverse_of: :user
    a.belongs_to :title, optional: true
    a.belongs_to :position, optional: true, primary_key: :UPID, class_name: 'UserPosition'
    a.belongs_to :lunch_price_plan, keys: :LPPID, optional: true

    a.has_many :sessions
    a.has_many :permission_users
    a.has_many :admission_applicant_checkboxes
    a.has_many :class_grades, foreign_key: :AuthorID, inverse_of: :author
    a.has_many :class_teachers, inverse_of: :employee
    a.has_many :pan_messages, foreign_key: :FromUID
    a.has_many :pan_recipients, foreign_key: :ToUID,
      class_name: 'PanRecipient', inverse_of: :recipient
    a.has_many :pan_authors, foreign_key: :FromUID,
      class_name: 'PanRecipient', inverse_of: :author
    a.has_many :school_district_imports, foreign_key: :AuthorID, inverse_of: :author
    a.has_many :parent_teacher_meetings
  end

  associations_for namespace: 'Discipline' do |a|
    a.has_many :logs, class_name: '::Discipline::StudentLog',
      foreign_key: :AuthorID
    a.has_many :authored_detentions, class_name: '::Discipline::StudentDetention',
      foreign_key: :author_id
    a.has_many :monitored_detentions, class_name: '::Discipline::StudentDetention',
      foreign_key: :MonitorID
  end

  associations_for namespace: 'Facility' do |a|
    a.has_many :buildings, class_name: 'Facility::Building', foreign_key: :owner_id,
      inverse_of: :owner
    a.has_many :rooms, class_name: 'Facility::Room', foreign_key: :owner_id, inverse_of: :owner
  end

  associations_for namespace: 'Nursing' do |a|
    a.has_many :authored_logs, class_name: 'Nursing::Log', foreign_key: :AuthorID,
      inverse_of: :author
  end

  associations_for namespace: 'Service', legacy: true do |a|
    a.has_many :student_logs, class_name: 'Service::StudentLog',
      foreign_key: :AuthorID, inverse_of: :author, dependent: :nullify
    a.has_many :family_logs, class_name: 'Service::FamilyLog',
      foreign_key: :AuthorID, inverse_of: :author, dependent: :nullify
  end

  associations_for namespace: 'TimeCard' do |a|
    a.has_many :requests, dependent: :destroy
  end

  has_one :admin_permission
  has_one :google_oauth_token, dependent: :destroy, inverse_of: :user
  has_one :contact, foreign_key: :UserID, dependent: :nullify, inverse_of: :user
  has_one :family_notification_config, inverse_of: :user, dependent: :destroy
  has_one :employee_notification_config, inverse_of: :user, dependent: :destroy
  has_one :student_notification_config, inverse_of: :user, dependent: :destroy
  has_one :email_verification_token, inverse_of: :user, dependent: :destroy

  has_many :blip_recipients, foreign_key: :UserID, inverse_of: :user, dependent: :destroy
  has_many :tokens, dependent: :destroy
  has_many :news_readers
  has_many :news_articles, through: :class_teachers
  has_many :classrooms, foreign_key: :PrimaryStaffID
  has_many :form_entries, foreign_key: :UserID, dependent: :destroy, inverse_of: :user
  has_many :forms, foreign_key: :PANID, dependent: :destroy, inverse_of: :notifier
  has_many :push_notification_subscribers
  has_many :notification_messages, dependent: :destroy, inverse_of: :user
  has_many :dismissed_help_notifications, class_name: 'Help::DismissedNotification',
    dependent: :destroy
  has_many :user_contacts, foreign_key: :UserID, inverse_of: :user, dependent: :destroy
  has_many :contact_categories, class_name: 'PersonalContactCategory', dependent: :destroy
  has_many :company_notes, foreign_key: :AuthorID, inverse_of: :author, dependent: :nullify
  has_many :contacts, foreign_key: :AuthorID, inverse_of: :author, dependent: :nullify

  has_many_attached :reports

  before_validation :set_username, if: :new_record?
  before_validation -> { self.username = username.gsub(/\s/, '') }, if: :username_changed?

  before_save :encrypt_password, if: :encrypted_password_changed?

  after_create :set_user_id_on_association, if: -> { [:student, :family].include?(role) }

  validates :username, presence: true, uniqueness: { scope: :school }
  validates :first_name, :last_name, presence: true, if: -> { role == :employee }
  validates :first_name, :last_name, :middle_name, :username, length: { maximum: 32 }
  validates :email, length: { maximum: 64 }
  validates :suffix, length: { maximum: 5 }

  validate :encrypted_password_format, if: :encrypted_password_changed?

  scope :ordered, -> { order(:last_name, :first_name) }
  scope :by_ids, ->(ids) { where(id: ids) if ids.present? }
  scope :by_status, ->(status) { where(current: status) }
  scope :online, -> { where(online: true) }

  scope :by_role_current_status, ->(role, school_year_id) do
    case role
    when :employee
      joins('LEFT JOIN SchoolYearFamilies ON Users.FamilyID = SchoolYearFamilies.FamilyID ' \
          "AND SchoolYearFamilies.SchoolYearID = #{school_year_id} " \
          'AND StudentID = 0')
        .joins('LEFT JOIN SchoolYearStudents ON Users.StudentID = SchoolYearStudents.StudentID ' \
            'AND SchoolYearStudents.Current = true ' \
            "AND SchoolYearStudents.SchoolYearID = #{school_year_id}")
        .where('SchoolYearFamilies.FamilyID IS NOT NULL ' \
          'OR SchoolYearStudents.StudentID IS NOT NULL ' \
          'OR (Users.Level > 0 AND Users.Current = true)')
        .distinct
    when :student
      where('Users.Level > 0 AND Users.Current = true')
    when :family
      joins('LEFT JOIN SchoolYearFamilies ON Users.FamilyID = SchoolYearFamilies.FamilyID ' \
          "AND SchoolYearFamilies.SchoolYearID = #{school_year_id} " \
          'AND StudentID = 0')
        .where('(Users.StudentID = 0 ' \
          'AND SchoolYearFamilies.FamilyID IS NOT NULL) ' \
          'OR (Users.Level > 0 AND Users.Current = true)')
        .distinct
    end
  end

  scope :by_role, ->(role) do
    case role
    when :family
      where('(FamilyID > 0 AND StudentID = 0) OR Level > 0')
    when :student
      where('Level > 0')
    end
  end

  def self.search(term)
    where('FirstName like :term OR LastName like :term', term: "%#{term}%")
  end

  def news_articles_by_scope(scope)
    query =
      case scope
      when :class
        user_model_association.news_articles
      when :school
        school.news_articles.school_level
      else
        school.news_articles.all_articles(role, user_model_association.id)
      end

    query.include_internal(superuser? || has_permission('CoNews'))
  end

  def valid_password?(password)
    BCrypt::Password.new(encrypted_password.sub(/\A\$2y/, '$2a')).is_password?(password)
  end

  def has_permission(permission)
    return true if superuser?

    @permissions = permission_users.pluck(:name) if @permissions.nil?
    @permissions.include? permission
  end

  def has_permission?(area, permissions)
    permissions.include?(admin_permission[area])
  end

  def role
    if !student && !family
      :employee
    elsif family && !student
      :family
    elsif student
      :student
    end
  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 send_unread_pan_count
    unread_count = pan_recipients.where(closed: false).count
    ActionCable.server.broadcast("notifications_channel_#{id}", pan_count: unread_count)
  end

  def accepted_terms_of_service?
    terms_of_service_date? && '2018-05-21'.to_date < terms_of_service_date.to_date
  end

  def is_active?
    return false unless active?

    case role
    when :family
      family.current? || school.find_or_build_family_module.admissions
    when :student
      student.current?
    else
      true
    end
  end

  def user_model_association
    case role
    when :employee
      self
    when :student
      student
    when :family
      family
    end
  end

  private
    def encrypt_password
      self.password = ''
      self.password_changed = Time.zone.now
      self.encrypted_password = BCrypt::Password.create(encrypted_password)
    end

    def encrypted_password_format
      requirements = {
        'must be 8-16 characters long' => /^.{8,16}$/,
        'must contain a uppercase/lowercase character' => /[A-Za-z]/,
        'must contain a digit' => /[0-9]/,
        'must contain a symbol' => /\W/,
        'must not contain these symbols (\'/,*)' => /^[^',*\/]+$/,
        'must not contain whitespaces' => /^\S*$/
      }

      requirements.each do |message, regex|
        errors.add(:password, message) unless encrypted_password.match(regex)
      end
    end

    def set_username
      case role
      when :student
        self.username = student.code unless username?
        self.school = student.school
        self.first_name = student.first_name
        self.last_name = student.last_name
        self.family = student.family
        self.title_label = 'Student'
        self.active = true
      when :employee
        unless username?
          self.username = (first_name.gsub(/\s/, '').first + last_name.gsub(/\s/, '')).downcase
        end
      when :family
        self.username = family.code
      end
    end

    def set_user_id_on_association
      user_model_association.update(UserID: id)
    end
end
