class Employee < User
  audited except: [
    :Address1,
    :Address2,
    :City,
    :State,
    :Zip,
    :HomeAddress1,
    :HomeAddress2,
    :HomeCity,
    :HomeState,
    :HomeZip,
    :HomePhone,
    :WorkPhone1,
    :WorkPhone2,
    :CellPhone,
    :FaxPhone,
    :Password,
    :EPassword,
    :EmployeeID,
    :StateID,
    :online
  ]

  has_associated_audits

  attr_accessor :employee_categories

  default_scope { where('Level > 0') }

  enum level: { volunteer: 1, employee: 2, supervisor: 3, administrator: 4 }
  enum pay_status: { no_status: 0, salary: 1, hourly: 2 }

  belongs_to :manager, foreign_key: :ManagerID, class_name: 'Employee', optional: true,
    inverse_of: :employees
  belongs_to :position, foreign_key: :PositionID, class_name: 'Position', optional: true,
    inverse_of: :employees
  belongs_to :building, foreign_key: :BuildingID, class_name: 'Facility::Building', optional: true,
    inverse_of: :employees
  belongs_to :location, foreign_key: :LocationID, class_name: 'Facility::Location', optional: true,
    inverse_of: :employees
  belongs_to :type, foreign_key: :UserTypeID, class_name: 'EmployeeType', optional: true,
    inverse_of: :employees
  belongs_to :race, foreign_key: :RaceID, optional: true, inverse_of: :employees

  has_many :phones, class_name: '::EmployeePhone', foreign_key: :associated_id,
    as: :associated, dependent: :destroy, inverse_of: :employee
  has_many :email_addresses, class_name: '::EmployeeEmail', foreign_key: :associated_id,
    as: :associated, dependent: :destroy, inverse_of: :employee
  has_many :addresses, class_name: '::EmployeeAddress', foreign_key: :associated_id,
    as: :associated, dependent: :destroy, inverse_of: :employee
  has_many :additional_ids, class_name: '::EmployeeAdditionalId', foreign_key: :associated_id,
    as: :associated, dependent: :destroy, inverse_of: :employee
  has_many :classrooms, through: :class_teachers
  has_many :documents, through: :classrooms
  has_many :document_folders, through: :classrooms
  has_many :employees, foreign_key: :ManagerID, class_name: 'Employee', dependent: :nullify,
    inverse_of: :manager
  has_many :notes, foreign_key: :UserID, class_name: 'EmployeeNote', inverse_of: :employee,
    dependent: :destroy
  has_many :authored_employee_notes, foreign_key: :AuthorID, class_name: 'EmployeeNote',
    inverse_of: :author, dependent: :nullify
  has_many :authored_employee_details, foreign_key: :AuthorID, class_name: 'EmployeeDetail',
    inverse_of: :author, dependent: :nullify
  has_many :employee_category_users, foreign_key: :UserID, dependent: :destroy,
    inverse_of: :employee
  has_many :categories, through: :employee_category_users, source: :category
  has_many :names, class_name: '::EmployeeName', foreign_key: :associated_id, as: :associated,
    dependent: :destroy, inverse_of: :employee
  has_many :positions, class_name: '::EmployeePosition', dependent: :destroy
  has_many :department_employees, foreign_key: :UserID, dependent: :destroy, inverse_of: :employee
  has_many :departments, through: :department_employees
  has_many :homeroom_students, class_name: 'Student', foreign_key: :TeacherID,
    inverse_of: :homeroom_teacher, dependent: :nullify
  has_many :employee_additional_values, dependent: :destroy
  has_many :contact_call_notes, class_name: '::ContactCallNote', foreign_key: :AuthorID,
    dependent: :destroy, inverse_of: :author
  has_many :verified_form_entries, class_name: 'FormEntry', foreign_key: :Verifier,
    inverse_of: :verifier, dependent: :nullify
  has_many :communication_batch_emails, class_name: 'Communication::BatchEmail',
    dependent: :destroy, foreign_key: :author_id, inverse_of: :author

  has_one :primary_email_address, -> { where(primary: true) }, class_name: '::EmployeeEmail',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :billing_address, -> { where(code: :billing) }, class_name: '::EmployeeAddress',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :employers_address, -> { where(code: :employers) }, class_name: '::EmployeeAddress',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :employment_address, -> { where(code: :employment) }, class_name: '::EmployeeAddress',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :mailing_address, -> { where(code: :mailing) }, class_name: '::EmployeeAddress',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :other_home_address, -> { where(code: :other_home) }, class_name: '::EmployeeAddress',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :physical_address, -> { where(code: :physical) }, class_name: '::EmployeeAddress',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :fax_phone, -> { where(code: :fax) }, class_name: '::EmployeePhone',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :home_phone, -> { where(code: :home) }, class_name: '::EmployeePhone',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :mobile_phone, -> { where(code: :mobile) }, class_name: '::EmployeePhone',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :other_phone, -> { where(code: :other) }, class_name: '::EmployeePhone',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :work_phone, -> { where(code: :work) }, class_name: '::EmployeePhone',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :other_name, -> { where(code: :other) }, class_name: '::EmployeeName',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :employee
  has_one :module_config, through: :school, source: :employee_module
  has_one :support_permission, -> { where(name: 'CoSupport') }, class_name: :PermissionUser,
    foreign_key: :UserID, inverse_of: :user
  has_one :state_user, class_name: 'EmployeeStateUser', foreign_key: :SSUID, inverse_of: :employee,
    dependent: :destroy
  has_one :detail, class_name: 'EmployeeDetail', foreign_key: :UserID, dependent: :destroy,
    inverse_of: :employee
  has_one :substitute, foreign_key: :UserID, dependent: :destroy, inverse_of: :employee
  has_one :state_id, -> { where(code: :state) }, as: :associated,
    class_name: '::EmployeeAdditionalId', foreign_key: :associated_id, inverse_of: :employee
  # Duplicate relationship to allow for unity beteween employee and student
  has_one :state_number, -> { where(code: :state) }, as: :associated,
    class_name: '::EmployeeAdditionalId', foreign_key: :associated_id, inverse_of: :employee
  has_one :school_number, -> { where(code: :school) }, as: :associated,
    class_name: '::EmployeeAdditionalId', foreign_key: :associated_id, inverse_of: :employee
  has_one :social_security_number, -> { where(code: :social_security_number) }, as: :associated,
    class_name: '::EmployeeAdditionalId', foreign_key: :associated_id, inverse_of: :employee
  has_one :recent_position, -> { order(start_date: :desc) }, class_name: 'EmployeePosition',
    foreign_key: :employee_id, inverse_of: :employee
  has_one :oldest_position, -> { order(start_date: :asc) }, class_name: 'EmployeePosition',
    foreign_key: :employee_id, inverse_of: :employee
  has_one :lunch_period, class_name: '::EmployeeLunchPeriod', foreign_key: :UserID,
    dependent: :destroy, inverse_of: :employee
  has_one :communication_user, -> { employee }, class_name: '::Communication::User',
    foreign_key: :associated_id, dependent: :destroy, inverse_of: :associated
  has_many :batch_email_recipients, class_name: '::Communication::BatchEmailRecipient',
    foreign_key: :associated_id, as: :associated, dependent: :destroy, inverse_of: :associated

  associations_for namespace: 'Covid' do |a|
    a.has_many :screenings, class_name: '::Covid::EmployeeScreening'
    a.has_many :temperatures, class_name: '::Covid::EmployeeTemperature'
  end

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

    a.has_one :identity, as: :associated, inverse_of: :associated, dependent: :destroy
  end

  associations_for namespace: 'TimeCard' do |a|
    a.has_many :entries, dependent: :destroy
    a.has_many :author_requests, dependent: :destroy, class_name: '::TimeCard::Request'
  end

  before_save :attach_categories

  accepts_nested_attributes_for :detail
  accepts_nested_attributes_for :substitute, :state_id, :school_number, :department_employees,
    :work_phone, :mobile_phone, allow_destroy: true

  validates :service_years, numericality: { greater_than_or_equal_to: 0 }
  validates :pin, confirmation: { case_sensitive: false }, numericality: true,
    length: { maximum: 4 }
  validates :pin_confirmation, presence: true, if: :pin_changed?

  scope :in_directory, -> { where(directory: 1) }
  scope :is_current, ->(flag) { where(current: flag) unless flag.nil? }
  scope :is_superuser, ->(flag) { where(superuser: flag) unless flag.nil? }
  scope :by_level, ->(level) { where(level: level) if level }
  scope :by_type, ->(type) { where(type_id: type) if type }
  scope :by_ids, ->(ids) { where(id: ids) if ids.present? }
  scope :by_main_district_site, -> { where(parent_user_id: 0) }

  scope :by_substitute, ->(flag) do
    return if flag.nil?

    if flag
      joins(:substitute)
    else
      left_joins(:substitute).where(SchoolSubstitutes: { SSID: nil })
    end
  end

  scope :by_legacy_permissions, ->(permissions) do
    includes(:permission_users).where(UserSecurity: { Permission: permissions })
  end

  scope :by_superuser_or_legacy_permissions, ->(flag, permissions=nil) do
    return is_superuser(flag) if permissions.nil?

    is_superuser(flag).or(by_legacy_permissions(permissions))
  end

  scope :by_categories, ->(ids) do
    joins(:categories).where(UserCategories: { CategoryID: ids }) if ids
  end

  scope :by_departments, ->(ids) do
    joins(:departments).where(DeptUsers: { DeptID: ids }) if ids
  end

  scope :teachers_and_superusers, -> do
    joins = left_joins(:class_teachers)
    joins.where(superuser: true).or(joins.where.not(ClassTeachers: { ClassTeacherID: nil }))
  end

  scope :position_within_date_range, ->(date_range) do
    return if date_range.blank?

    joins(:positions).merge(EmployeePosition.within_date_range(date_range))
  end

  scope :by_covid_temperature_submissions, ->(submitted, date) do
    return if submitted.nil?

    if submitted
      joins(:covid_temperatures).merge(Covid::EmployeeTemperature.by_date(date))
    else
      beginning_of_day = date.to_date.beginning_of_day
      end_of_day = date.to_date.end_of_day

      joins(<<~SQL).where(covid_employee_temperatures: { id: nil })
        LEFT JOIN covid_employee_temperatures
        ON covid_employee_temperatures.employee_id = Users.UserID
        AND Users.Level > 0
        AND covid_employee_temperatures.recorded_at
        BETWEEN '#{beginning_of_day}' AND '#{end_of_day}'
      SQL
    end
  end

  scope :by_covid_screening_submissions, ->(submitted, date) do
    return if submitted.nil?

    if submitted
      joins(:covid_screenings).merge(Covid::EmployeeScreening.by_date(date))
    else
      joins(<<~SQL).where(covid_employee_screenings: { id: nil })
        LEFT JOIN covid_employee_screenings
        ON covid_employee_screenings.employee_id = Users.UserID
        AND Users.Level > 0
        AND covid_employee_screenings.date = '#{date}'
      SQL
    end
  end

  def pin_confirmation=(val)
    @pin_confirmation = val.to_i
  end

  def own_and_associated_audits
    AuditOverrider.by_associate_type('User').by_associate_id(id)
      .or(AuditOverrider.by_auditable_type('User').by_auditable_id(id))
      .order(created_at: :desc)
  end

  def sync_legacy_ids
    return unless employee_id.present?

    additional_ids.find_or_initialize_by(code: :school).update(number: employee_id)
  end

  def sync_legacy_phones
    return unless phones.empty?

    country = ISO3166::Country.find_country_by_name(school.country)&.alpha2 || 'US'
    if legacy_home_phone.present? && phones.where(code: :home).empty?
      phones.create(
        code: :home,
        country_code: country,
        number: home_phone,
        skip_sync: true
      )
    end

    if work_phone.present? && phones.where(code: :work).empty?
      legacy_phones.create(
        code: :work,
        country_code: country,
        number: work_phone,
        skip_sync: true
      )
    end

    if legacy_cell_phone.present? && phones.where(code: :mobile).empty?
      phones.create(
        code: :mobile,
        country_code: country,
        number: cell_phone,
        skip_sync: true
      )
    end

    if legacy_fax_phone.present? && phones.where(code: :fax).empty?
      phones.create(
        code: :fax,
        country_code: country,
        number: fax_phone,
        skip_sync: true
      )
    end

    return if legacy_work_phone_2.blank? || phones.where(code: :other).present?

    phones.create(
      code: :other,
      country_code: country,
      number: work_phone_2,
      skip_sync: true
    )
  end

  def sync_legacy_addresses
    return unless addresses.empty?

    if address.present? && addresses.where(code: :employers).empty?
      addresses.create(
        code: :employers,
        street: address,
        street_ext: address_ext,
        city: city,
        state: state,
        zip: zip,
        skip_sync: true
      )
    end

    return if home_address.blank? || addresses.where(code: :physical).present?

    addresses.create(
      code: :physical,
      street: home_address,
      street_ext: home_address_ext,
      city: home_city,
      state: home_state,
      zip: home_zip,
      skip_sync: true
    )
  end

  def sync_legacy_names
    return unless nickname?
    return if names.where(code: :nickname).exists?

    names.create(code: :nickname, first_name: nickname, skip_sync: true)
  end

  def available_addresses(types=[])
    EmployeeAddress.codes.keys - (addresses.pluck(:code) - types)
  end

  def available_phones(types=[])
    EmployeePhone.codes.keys - (phones.pluck(:code) - types)
  end

  def available_names(types=[])
    EmployeeName.codes.keys - (names.pluck(:code) - types) - ['previous']
  end

  def available_ids(types=[])
    EmployeeAdditionalId.codes.keys - (additional_ids.pluck(:code) - types)
  end

  # Delete employee from legacy to retain backup data needed for restore
  def delete_from_legacy(current_user)
    Internal::HumanResources::EmployeeService.call(self, current_user.id)
  end

  private
    def attach_categories
      self.categories = employee_categories if employee_categories
    end
end
