# 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
  include Rails.application.routes.url_helpers
  # @!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&.present?
      [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'])
      unless 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

    result
  end

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

  # @return [Icalendar::Calendar]
  #   this Person's upcoming activities feed.
  def calendar_feed(skip_absent = false)
    cal = Icalendar::Calendar.new
    cal.x_wr_calname = 'Aardbei'

    tzid = 1.seconds.since.time_zone.tzinfo.name

    selection = self
                .participants
                .joins(:activity)
                .where('"end" > ?', 3.months.ago)

    if skip_absent
      selection = selection
                  .where.not(attending: false)
    end

    selection.each do |p|
      a = p.activity

      description_items = []
      # The description consists of the following parts:
      #  - The Participant's response and notes (if set),
      #  - The Activity's description (if not empty),
      #  - The names of the organizers,
      #  - Subgroup information, if applicable,
      #  - The URL.

      # Response
      yourresponse = "#{I18n.t 'activities.participant.yourresponse'}: #{p.human_attending}"

      yourresponse << " (#{p.notes})" if p.notes.present?

      description_items << yourresponse

      # Description
      description_items << a.description if a.description.present?

      # Organizers
      orgi = a.organizer_names
      orgi_names = orgi.join ', '
      orgi_line = case orgi.count
                  when 0 then I18n.t 'activities.organizers.no_organizers'
                  when 1 then "#{I18n.t 'activities.organizers.one'}: #{orgi_names}"
                  else "#{I18n.t 'activities.organizers.other'}: #{orgi_names}"
                  end

      description_items << orgi_line

      # Subgroups
      if a.subgroups.any?
        description_items << "#{I18n.t 'activities.participant.yoursubgroup'}: #{p.subgroup}" if p.subgroup

        subgroup_names = a.subgroups.map(&:name).join ', '
        description_items << "#{I18n.t 'activerecord.models.subgroup.other'}: #{subgroup_names}"

      end

      # URL
      a_url = group_activity_url a.group, a
      description_items << a_url

      cal.event do |e|
        e.uid = a_url
        e.dtstart = Icalendar::Values::DateTime.new a.start, tzid: tzid
        e.dtend =   Icalendar::Values::DateTime.new a.end,   tzid: tzid

        e.status = p.ical_attending

        e.summary = a.name
        e.location = a.location

        e.description = description_items.join "\n"

        e.url = a_url
      end
    end

    cal.publish
    cal
  end

  private

  # Assert that the person's birth date, if any, lies in the past.
  def birth_date_cannot_be_in_future
    errors.add(:birth_date, I18n.t('person.errors.cannot_future')) if self.birth_date && self.birth_date > Date.today
  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
    self.user&.update!(email: self.email)
  end
end