module EnjuSeed
  module EnjuUser
    extend ActiveSupport::Concern

    included do
      scope :administrators, -> { joins(:role).where("roles.name = ?", "Administrator") }
      scope :librarians, -> { joins(:role).where("roles.name = ? OR roles.name = ?", "Administrator", "Librarian") }
      scope :suspended, -> { where.not(locked_at: nil) }
      belongs_to :profile
      if defined?(EnjuBiblio)
        has_many :import_requests, dependent: :restrict_with_exception
        has_many :picture_files, as: :picture_attachable, dependent: :destroy
      end
      has_one :user_has_role, dependent: :destroy
      has_one :role, through: :user_has_role
      accepts_nested_attributes_for :user_has_role

      validates :username, presence: true, uniqueness: true, format: {
        with: /\A[0-9A-Za-z][0-9A-Za-z_\-]*[0-9A-Za-z]\z/
      }
      validates :email, format: Devise::email_regexp, allow_blank: true, uniqueness: true
      validates :expired_at, date: true, allow_blank: true

      with_options if: :password_required? do |v|
        v.validates_presence_of     :password
        v.validates_confirmation_of :password
        v.validates_length_of       :password, allow_blank: true,
          within: Devise::password_length
      end

      before_destroy :check_role_before_destroy
      before_save :check_expiration
      after_create :set_confirmation
      after_save :set_lock_information

      extend FriendlyId
      friendly_id :username
      strip_attributes only: :username
      strip_attributes only: :email, allow_empty: true

      attr_accessor :password_not_verified,
        :update_own_account, :auto_generated_password,
        :locked, :current_password # , :agent_id

      paginates_per 10

      def send_devise_notification(notification, *args)
        devise_mailer.send(notification, self, *args).deliver_later
      end

      # 有効期限切れのユーザを一括で使用不可にします。
      def self.lock_expired_users
        User.find_each do |user|
          user.lock_access! if user.expired? && user.active_for_authentication?
        end
      end

      # ユーザの情報をエクスポートします。
      # @param [Hash] options
      def self.export(options = { format: :text })
        header = %w[
          username
          full_name
          full_name_transcription
          email
          user_number
          role
          user_group
          library
          locale
          locked
          required_role
          created_at
          updated_at
          expired_at
          keyword_list
          note
        ]
        header += %w[
          checkout_icalendar_token
          save_checkout_history
        ] if defined? EnjuCirculation
        header << "save_search_history" if defined? EnjuSearchLog
        header << "share_bookmarks" if defined? EnjuBookmark
        lines = []
        User.find_each.map { |u|
          line = []
          line << u.username
          line << u.try(:profile).try(:full_name)
          line << u.try(:profile).try(:full_name_transcription)
          line << u.email
          line << u.try(:profile).try(:user_number)
          line << u.role.try(:name)
          line << u.try(:profile).try(:user_group).try(:name)
          line << u.try(:profile).try(:library).try(:name)
          line << u.try(:profile).try(:locale)
          line << u.access_locked?
          line << u.try(:profile).try(:required_role).try(:name)
          line << u.created_at
          line << u.updated_at
          line << u.try(:profile).try(:expired_at)
          line << u.try(:profile).try(:keyword_list).try(:split).try(:join, "//")
          line << u.try(:profile).try(:note)
          if defined? EnjuCirculation
            line << u.try(:profile).try(:checkout_icalendar_token)
            line << u.try(:profile).try(:save_checkout_history)
          end
          if defined? EnjuSearchLog
            line << u.try(:profile).try(:save_search_history)
          end
          if defined? EnjuBookmark
            line << u.try(:profile).try(:share_bookmarks)
          end
          lines << line
        }
        if options[:format] == :text
          lines.map { |line| line.to_csv(col_sep: "\t") }.unshift(header.to_csv(col_sep: "\t")).join
        else
          lines
        end
      end
    end

    # ユーザにパスワードが必要かどうかをチェックします。
    # @return [Boolean]
    def password_required?
      if Devise.mappings[:user].modules.include?(:database_authenticatable)
        !persisted? || !password.nil? || !password_confirmation.nil?
      end
    end

    # ユーザが特定の権限を持っているかどうかをチェックします。
    # @param [String] role_in_question 権限名
    # @return [Boolean]
    def has_role?(role_in_question)
      return false unless role
      return true if role.name == role_in_question

      case role.name
      when "Administrator"
        true
      when "Librarian"
        true if role_in_question == "User"
      else
        false
      end
    end

    # ユーザに使用不可の設定を反映させます。
    def set_lock_information
      if locked == "1" && active_for_authentication?
        lock_access!
      elsif locked == "0" && !active_for_authentication?
        unlock_access!
      end
    end

    def set_confirmation
      if respond_to?(:confirm!)
        reload
        confirm!
      end
    end

    # ユーザが有効期限切れかどうかをチェックし、期限切れであれば使用不可に設定します。
    # @return [Object]
    def check_expiration
      return if has_role?("Administrator")

      if expired_at
        if expired_at.beginning_of_day < Time.zone.now.beginning_of_day
          lock_access! if active_for_authentication?
        end
      end
    end

    # ユーザの削除前に、管理者ユーザが不在にならないかどうかをチェックします。
    # @return [Object]
    def check_role_before_destroy
      if has_role?("Administrator")
        if Role.find_by(name: "Administrator").users.count == 0
          raise "#{username} is the last administrator in this system."
        end
      end
    end

    # ユーザに自動生成されたパスワードを設定します。
    # @return [String]
    def set_auto_generated_password
      password = Devise.friendly_token[0..7]
      self.password = password
      self.password_confirmation = password
    end

    # ユーザが有効期限切れかどうかをチェックします。
    # @return [Boolean]
    def expired?
      if expired_at
        true if expired_at.beginning_of_day < Time.zone.now.beginning_of_day
      end
    end

    # ユーザが管理者かどうかをチェックします。
    # @return [Boolean]
    def is_admin?
      return true if has_role?("Administrator")

      false
    end

    # ユーザがシステム上の最後のLibrarian権限ユーザかどうかをチェックします。
    # @return [Boolean]
    def last_librarian?
      if has_role?("Librarian")
        role = Role.find_by(name: "Librarian")
        return true if role.users.count == 1

        false
      end
    end

    def send_confirmation_instructions
      Devise::Mailer.confirmation_instructions(self).deliver if email.present?
    end

    # ユーザが削除可能かどうかをチェックします。
    # @param [User] current_user ユーザ
    # @return [Object]
    def deletable_by?(current_user)
      return nil unless current_user

      if defined?(EnjuCirculation)
        # 未返却の資料のあるユーザを削除しようとした
        if checkouts.count.positive?
          errors.add(:base, I18n.t("user.this_user_has_checked_out_item"))
        end
      end

      if has_role?("Librarian")
        # 管理者以外のユーザが図書館員を削除しようとした。図書館員の削除は管理者しかできない
        unless current_user.has_role?("Administrator")
          errors.add(:base, I18n.t("user.only_administrator_can_destroy"))
        end
        # 最後の図書館員を削除しようとした
        if last_librarian?
          errors.add(:base, I18n.t("user.last_librarian"))
        end
      end

      # 最後の管理者を削除しようとした
      if has_role?("Administrator")
        if Role.find_by(name: "Administrator").users.count == 1
          errors.add(:base, I18n.t("user.last_administrator"))
        end
      end

      if errors[:base] == []
        true
      else
        false
      end
    end
  end
end


# == Schema Information
#
# Table name: users
#
#  id                       :integer         not null, primary key
#  email                    :string(255)     default(""), not null
#  encrypted_password       :string(255)     default(""), not null
#  reset_password_token     :string(255)
#  reset_password_sent_at   :datetime
#  remember_created_at      :datetime
#  sign_in_count            :integer         default(0)
#  current_sign_in_at       :datetime
#  last_sign_in_at          :datetime
#  current_sign_in_ip       :string(255)
#  last_sign_in_ip          :string(255)
#  password_salt            :string(255)
#  confirmation_token       :string(255)
#  confirmed_at             :datetime
#  confirmation_sent_at     :datetime
#  unconfirmed_email        :string(255)
#  failed_attempts          :integer         default(0)
#  unlock_token             :string(255)
#  locked_at                :datetime
#  authentication_token     :string(255)
#  created_at               :datetime        not null
#  updated_at               :datetime        not null
#  username                 :string(255)     not null
#  library_id               :integer         default(1), not null
#  user_group_id            :integer         default(1), not null
#  expired_at               :datetime
#  required_role_id         :integer         default(1), not null
#  note                     :text
#  keyword_list             :text
#  user_number              :string(255)
#  state                    :string(255)
#  locale                   :string(255)
#  enju_access_key          :string(255)
#  save_checkout_history    :boolean
#  checkout_icalendar_token :string(255)
#  share_bookmarks          :boolean
#  save_search_history      :boolean
#  answer_feed_token        :string(255)
#
