# frozen_string_literal: true

class User < ApplicationRecord
  include User::Relationship
  include User::Relationship::Follow
  include User::Relationship::Block
  include User::Relationship::Mute
  include User::AnswerMethods
  include User::BanMethods
  include User::InboxMethods
  include User::NotificationMethods
  include User::QuestionMethods
  include User::PushNotificationMethods
  include User::ReactionMethods
  include User::RelationshipMethods
  include User::SharingMethods
  include User::TimelineMethods
  include ActiveModel::OneTimePassword

  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :async, :registerable,
         :recoverable, :rememberable, :trackable,
         :validatable, :confirmable, authentication_keys: [:login]

  has_one_time_password
  enum otp_module: { disabled: 0, enabled: 1 }, _prefix: true
  attr_accessor :otp_attempt, :otp_validation
  attr_writer :login

  rolify

  has_many :questions, dependent: :destroy_async
  has_many :answers, dependent: :destroy_async
  has_many :comments, dependent: :destroy_async
  has_many :inbox_entries, dependent: :destroy_async
  has_many :smiles, class_name: "Reaction", dependent: :destroy_async
  has_many :notifications, foreign_key: :recipient_id, dependent: :destroy_async
  has_many :reports, dependent: :destroy_async
  has_many :lists, dependent: :destroy_async
  has_many :list_memberships, class_name: "ListMember", dependent: :destroy_async
  has_many :mute_rules, dependent: :destroy_async

  has_many :subscriptions, dependent: :destroy_async
  has_many :totp_recovery_codes, dependent: :destroy_async
  has_many :web_push_subscriptions, dependent: :destroy_async

  has_one :profile, dependent: :destroy
  has_one :theme, dependent: :destroy

  has_many :bans, class_name: "UserBan", dependent: :destroy_async
  has_many :banned_users, class_name:  "UserBan",
                          foreign_key: "banned_by_id",
                          dependent:   :nullify

  has_many :anonymous_blocks, dependent: :destroy_async
  has_many :passive_anonymous_blocks, class_name:  "AnonymousBlock",
                                      foreign_key: "target_user_id",
                                      dependent:   :nullify

  SCREEN_NAME_REGEX = /\A[a-zA-Z0-9_]+\z/
  WEBSITE_REGEX = /https?:\/\/([A-Za-z.-]+)\/?(?:.*)/i

  before_validation do
    screen_name.strip!
  end

  validates :email, fake_email: true, typoed_email: true
  validates :sharing_custom_url, allow_blank: true, valid_url: true
  validates :screen_name,
            presence:    true,
            format:      { with: SCREEN_NAME_REGEX, message: I18n.t("activerecord.validation.user.screen_name.format") },
            length:      { minimum: 1, maximum: 16 },
            uniqueness:  { case_sensitive: false },
            screen_name: true

  mount_uploader :profile_picture, ProfilePictureUploader, mount_on: :profile_picture_file_name
  process_in_background :profile_picture
  mount_uploader :profile_header, ProfileHeaderUploader, mount_on: :profile_header_file_name
  process_in_background :profile_header

  # when a user has been deleted, all reports relating to the user become invalid
  before_destroy do
    Report.where(target_id: id, type: "Reports::User").find_each do |r|
      unless r.nil?
        r.deleted = true
        r.save
      end
    end
  end

  after_destroy do
    Retrospring::Metrics::USERS_DESTROYED.increment
  end

  after_create do
    Profile.create(user_id: id) if Profile.where(user_id: id).count.zero?

    Retrospring::Metrics::USERS_CREATED.increment
  end

  # use the screen name as parameter for url helpers
  def to_param = screen_name

  def login = @login || screen_name || email

  def self.find_first_by_auth_conditions(warden_conditions)
    conditions = warden_conditions.dup
    if (login = conditions.delete(:login))
      where(conditions).where(["lower(screen_name) = :value OR lower(email) = :value", { value: login.downcase }]).first
    else
      where(conditions).first
    end
  end

  # @param list [List]
  # @return [Boolean] true if +self+ is a member of +list+
  def member_of?(list) = list_memberships.pluck(:list_id).include? list.id

  # answers a question
  # @param question [Question] the question to answer
  # @param content [String] the answer content
  def answer(question, content)
    raise Errors::AnsweringOtherBlockedSelf if question.user&.blocking?(self)
    raise Errors::AnsweringSelfBlockedOther if self.blocking?(question.user)

    Retrospring::Metrics::QUESTIONS_ANSWERED.increment

    Answer.create!(content:, user: self, question:, has_reacted: false, is_subscribed: true)
  end

  # has the user answered +question+ yet?
  # @param question [Question]
  def answered?(question) = question.answers.pluck(:user_id).include? id

  def comment(answer, content)
    raise Errors::CommentingSelfBlockedOther if self.blocking?(answer.user)
    raise Errors::CommentingOtherBlockedSelf if answer.user.blocking?(self)

    Retrospring::Metrics::COMMENTS_CREATED.increment

    Comment.create!(user: self, answer:, content:)
  end

  # @return [Boolean] is the user a moderator?
  def mod? = has_cached_role?(:moderator) || has_cached_role?(:administrator)

  def admin? = has_cached_role?(:administrator)

  # region stuff used for reporting/moderation
  def report(object, reason = nil)
    target_user = if object.instance_of?(::User)
                    object
                  elsif object.respond_to? :user
                    object.user
                  end

    existing = Report.find_by(type: "Reports::#{object.class}", target_id: object.id, user_id: id, target_user_id: target_user&.id, deleted: false)
    if existing.nil?
      Report.create(type: "Reports::#{object.class}", target_id: object.id, user_id: id, target_user_id: target_user&.id, reason:)
    elsif !reason.nil? && reason.length.positive?
      existing.append_reason(reason)
    end
  end
  # endregion

  def can_export?
    return (Time.zone.now > export_created_at.in(1.week)) && !export_processing unless export_created_at.nil?

    !export_processing
  end

  def inbox_locked? = privacy_lock_inbox
end