# A person represents a human being. A Person may be a Member in one or more
# Groups, and be a Participant in any number of events of those Groups.
# A Person may access the system by creating a User, and may have at most one
# User.
class Person < ApplicationRecord
  # @!attribute first_name
  #   @return [String]
  #     the person's first name. ('Vincent' in 'Vincent van Gogh'.)
  #
  # @!attribute infix
  #   @return [String]
  #     the part of a person's surname that is not taken into account when
  #     sorting by surname. ('van' in 'Vincent van Gogh'.)
  #
  # @!attribute last_name
  #   @return [String]
  #     the person's surname. ('Gogh' in 'Vincent van Gogh'.)
  #
  # @!attribute birth_date
  #   @return [Date]
  #     the person's birth date.
  #
  # @!attribute email
  #   @return [String]
  #     the person's email address.
  #
  # @!attribute calendar_token
  #   @return [String]
  #     the calendar token that can be used to open this Person's events as an
  #     ICAL file.
  #
  # @!attribute is_admin
  #   @return [Boolean]
  #     whether or not the person has administrative rights.

  has_one :user
  has_many :members,
    dependent: :destroy
  has_many :participants,
    dependent: :destroy
  has_many :groups, through: :members
  has_many :activities, through: :participants
  has_secure_token :calendar_token

  validates :email, uniqueness: true
  validates :first_name, presence: true
  validates :last_name, presence: true

  validate :birth_date_cannot_be_in_future

  before_validation :not_admin_if_nil
  before_save :update_user_email, if: :email_changed?

  # The person's full name.
  def full_name
    if self.infix
      [self.first_name, self.infix, self.last_name].join(' ')
    else
      [self.first_name, self.last_name].join(' ')
    end
  end

  # The person's reversed name, to sort by surname.
  def reversed_name
    if self.infix
      [self.last_name, self.infix, self.first_name].join(', ')
    else
      [self.last_name, self.first_name].join(', ')
    end
  end

  # All activities where this person is an organizer.
  def organized_activities
    self.participants.includes(:activity).where(is_organizer: true)
  end

  # Create multiple Persons from data found in a csv file, return those.
  def self.from_csv(content)
    reader = CSV.parse(content, {headers: true, skip_blanks: true})

    result = []
    reader.each do |row|
      p = Person.find_by(email: row['email'])
      if not p
        p = Person.new
        p.first_name  = row['first_name']
        p.infix       = row['infix']
        p.last_name   = row['last_name']
        p.email       = row['email']
        p.birth_date  = Date.strptime(row['birth_date']) unless row['birth_date'].blank?
        p.save!
      end
      result << p
    end

    return result
  end

  # @return [String]
  #   the URL to access this person's calendar.
  def calendar_url
    person_calendar_url self.calendar_token
  end

  private
  # Assert that the person's birth date, if any, lies in the past.
  def birth_date_cannot_be_in_future
    if self.birth_date && self.birth_date > Date.today
      errors.add(:birth_date, I18n.t('person.errors.cannot_future'))
    end
  end

  # Explicitly force nil to false in the admin field.
  def not_admin_if_nil
    self.is_admin ||= false
  end

  # Ensure the person's user email is updated at the same time as the person's
  # email.
  def update_user_email
    if not self.user.nil?
      self.user.update!(email: self.email)
    end
  end
end