# frozen_string_literal: true

class Member < ApplicationRecord
  include Discard::Model

  acts_as_messageable # messages can be sent to this model
  include Geocodable
  include MemberFlickr
  include MemberNewsletter

  extend FriendlyId

  friendly_id :login_name, use: %i(slugged finders)

  #
  # Relationships
  has_many :posts, foreign_key: 'author_id', dependent: :destroy, inverse_of: :author
  has_many :comments, foreign_key: 'author_id', dependent: :destroy, inverse_of: :author
  has_many :forums, foreign_key: 'owner_id', dependent: :nullify, inverse_of: :owner
  has_many :gardens, foreign_key: 'owner_id', dependent: :destroy, inverse_of: :owner
  has_many :plantings, foreign_key: 'owner_id', dependent: :destroy, inverse_of: :owner
  has_many :seeds, foreign_key: 'owner_id', dependent: :destroy, inverse_of: :owner
  has_many :harvests, foreign_key: 'owner_id', dependent: :destroy, inverse_of: :owner
  has_many :activities, foreign_key: 'owner_id', dependent: :destroy, inverse_of: :owner
  has_and_belongs_to_many :roles
  has_many :notifications, foreign_key: 'recipient_id', inverse_of: :recipient
  has_many :sent_notifications, foreign_key: 'sender_id', inverse_of: :sender, class_name: "Notification"
  has_many :authentications, dependent: :destroy
  has_one :api_token, -> { where(provider: 'api') }, class_name: 'Authentication', dependent: :destroy

  def api_token?
    api_token.present?
  end

  def regenerate_api_token
    api_token.destroy if api_token?
    create_api_token(
      provider: 'api',
      uid:      id,
      token:    SecureRandom.hex(16)
    )
  end
  has_many :photos, inverse_of: :owner
  has_many :likes, dependent: :destroy

  #
  # Following other members
  has_many :follows, class_name: "Follow", foreign_key: "follower_id", dependent: :destroy,
                     inverse_of: :follower
  has_many :inverse_follows, class_name: "Follow", foreign_key: "followed_id",
                             dependent: :destroy, inverse_of: :followed
  has_many :followed, through: :follows
  has_many :followers, through: :inverse_follows, source: :follower

  #
  # Blocking other members
  has_many :blocks, class_name: "Block", foreign_key: "blocker_id", dependent: :destroy,
                    inverse_of: :blocker
  has_many :inverse_blocks, class_name: "Block", foreign_key: "blocked_id",
                            dependent: :destroy, inverse_of: :blocked
  has_many :blocked_members, through: :blocks, source: :blocked
  has_many :blockers, through: :inverse_blocks, source: :blocker

  #
  # Global data records this member created
  has_many :requested_crops, class_name: 'Crop', foreign_key: 'requester_id', dependent: :nullify,
                             inverse_of: :requester
  has_many :created_crops, class_name: 'Crop', foreign_key: 'creator_id', dependent: :nullify,
                           inverse_of: :creator
  has_many :created_alternate_names, class_name: 'AlternateName', foreign_key: 'creator_id', inverse_of: :creator
  has_many :created_scientific_names, class_name: 'ScientificName', foreign_key: 'creator_id', inverse_of: :creator

  #
  # Scopes
  scope :confirmed, -> { where.not(confirmed_at: nil) }
  scope :located, -> { where.not(location: '').where.not(latitude: nil).where.not(longitude: nil) }
  scope :recently_signed_in, -> { reorder(updated_at: :desc) }
  scope :recently_joined, -> { reorder(confirmed_at: :desc) }
  scope :interesting, -> { confirmed.located.recently_signed_in.has_plantings }
  scope :has_plantings, -> { joins(:plantings).group("members.id") }
  scope :wants_reminders, -> { where(send_planting_reminder: true) }

  # Include default devise modules. Others available are:
  # :token_authenticatable, :confirmable,
  # :lockable, :timeoutable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :confirmable, :lockable, :timeoutable, :omniauthable

  # discarded (deleted) member cannot log in
  def active_for_authentication?
    super && !discarded?
  end

  # set up geocoding
  geocoded_by :location

  # Virtual attribute for authenticating by either username or email
  # This is in addition to a real persisted field like 'username'
  attr_accessor :login

  #
  # Validations
  # Requires acceptance of the Terms of Service
  validates :tos_agreement, acceptance: { allow_nil: true, accept: true }
  validates :login_name,
            length:     {
              minimum: 2, maximum: 25, message: :login_name_length
            },
            exclusion:  {
              in: %w(growstuff admin moderator staff nearby), message: :login_name_reserved
            },
            format:     {
              with: /\A\w+\z/, message: :login_name_format
            },
            uniqueness: {
              case_sensitive: false
            }
  validates :website_url, format: { with: %r{\Ahttps?://}, message: :url_format }, allow_blank: true
  validates :other_url, format: { with: %r{\Ahttps?://}, message: :url_format }, allow_blank: true
  validates :instagram_handle, :facebook_handle, :bluesky_handle,
            format: { without: %r{\Ahttps?://|/}, message: :handle_format }, allow_blank: true

  #
  # Triggers
  after_validation :geocode
  after_validation :empty_unwanted_geocodes

  # Give each new member a default garden
  # we use find_or_create to avoid accidentally creating a second one,
  # which can happen sometimes especially with FactoryBot associations
  after_create { |member| Garden.create(name: "Garden", owner_id: member.id) }

  # allow login via either login_name or email address
  def self.find_first_by_auth_conditions(warden_conditions)
    conditions = warden_conditions.dup
    login = conditions.delete(:login)
    return  where(conditions).login_name_or_email(login).first if login

    find_by(conditions)
  end

  def to_s
    discarded? ? 'deleted' : login_name
  end

  def to_param
    slug
  end

  def mailboxer_email(_messageable)
    send_notification_email ? email : false
  end

  def role?(role_sym)
    roles.any? { |r| r.name.gsub(/\s+/, "_").underscore.to_sym == role_sym }
  end

  def auth(provider)
    authentications.find_by(provider:)
  end

  def unread_count
    receipts.where(is_read: false).count
  end

  def self.login_name_or_email(login)
    where(["lower(login_name) = :value OR lower(email) = :value", { value: login.downcase }])
  end

  def self.case_insensitive_login_name(login)
    where(["lower(login_name) = :value", { value: login.downcase }])
  end

  def self.nearest_to(place)
    return [] if place.blank?

    latitude, longitude = Geocoder.coordinates(place, params: { limit: 1 })
    return [] unless latitude && longitude

    Member.located.near([latitude, longitude], 1000)
  end

  def already_following?(member)
    follows.exists?(followed_id: member.id)
  end

  def get_follow(member)
    follows.find_by(followed_id: member.id) if already_following?(member)
  end

  def already_blocking?(member)
    blocks.exists?(blocked_id: member.id)
  end

  def get_block(member)
    blocks.find_by(blocked_id: member.id) if already_blocking?(member)
  end

  def has_activity?
    (gardens.exists? && gardens.count > 1) ||
      plantings.exists? ||
      harvests.exists? ||
      seeds.exists? ||
      photos.exists? ||
      forums.exists? ||
      activities.exists? ||
      posts.exists? ||
      comments.exists? ||
      requested_crops.exists? ||
      created_crops.exists? ||
      likes.exists? ||
      created_alternate_names.exists? ||
      created_scientific_names.exists? ||
      follows.exists? ||
      inverse_follows.exists? ||
      blocks.exists? ||
      inverse_blocks.exists?
  end
end
