# frozen_string_literal: true

# Provides helper methods for Availability resources and properties
class Availabilities::AvailabilitiesService

  def initialize(current_user, maximum_visibility = {})
    @current_user = current_user
    @maximum_visibility = maximum_visibility
    @service = Availabilities::StatusService.new(current_user.role)
  end

  # list all slots for the given machine, with reservations info, relatives to the given user
  def machines(machine_id, user)
    machine = Machine.friendly.find(machine_id)
    reservations = reservations(machine, user)
    availabilities = availabilities(machine, 'machines', user)

    slots = []
    availabilities.each do |a|
      slot_duration = a.slot_duration || Setting.get('slot_duration').to_i
      ((a.end_at - a.start_at) / slot_duration.minutes).to_i.times do |i|
        next unless (a.start_at + (i * slot_duration).minutes) > DateTime.current || user.admin?

        slot = Slot.new(
          start_at: a.start_at + (i * slot_duration).minutes,
          end_at: a.start_at + (i * slot_duration).minutes + slot_duration.minutes,
          availability_id: a.id,
          availability: a,
          machine: machine,
          title: ''
        )
        slot = @service.machine_reserved_status(slot, reservations, @current_user)
        slots << slot
      end
    end
    slots
  end

  # list all slots for the given space, with reservations info, relatives to the given user
  def spaces(space_id, user)
    space = Space.friendly.find(space_id)
    reservations = reservations(space, user)
    availabilities = availabilities(space, 'space', user)

    slots = []
    availabilities.each do |a|
      slot_duration = a.slot_duration || Setting.get('slot_duration').to_i
      ((a.end_at - a.start_at) / slot_duration.minutes).to_i.times do |i|
        next unless (a.start_at + (i * slot_duration).minutes) > DateTime.current || user.admin?

        slot = Slot.new(
          start_at: a.start_at + (i * slot_duration).minutes,
          end_at: a.start_at + (i * slot_duration).minutes + slot_duration.minutes,
          availability_id: a.id,
          availability: a,
          space: space,
          title: ''
        )
        slot = @service.space_reserved_status(slot, reservations, user)
        slots << slot
      end
    end
    slots.each do |s|
      s.title = I18n.t('availabilities.not_available') if s.complete? && !s.is_reserved
    end
    slots
  end

  # list all slots for the given training, with reservations info, relatives to the given user
  def trainings(training_id, user)
    # first, we get the already-made reservations
    reservations = user.reservations.where("reservable_type = 'Training'")
    reservations = reservations.where('reservable_id = :id', id: training_id.to_i) if training_id.is_number?
    reservations = reservations.joins(:slots).where('slots.start_at > ?', @current_user.admin? ? 1.month.ago : DateTime.current)

    # visible availabilities depends on multiple parameters
    availabilities = training_availabilities(training_id, user)

    # finally, we merge the availabilities with the reservations
    availabilities.each do |a|
      a = @service.training_event_reserved_status(a, reservations, user)
    end
  end

  private

  def subscription_year?(user)
    user.subscription && user.subscription.plan.interval == 'year' && user.subscription.expired_at >= DateTime.current
  end

  # member must have validated at least 1 training and must have a valid yearly subscription.
  def show_extended_slots?(user)
    user.trainings.size.positive? && subscription_year?(user)
  end

  def reservations(reservable, user)
    Reservation.where('reservable_type = ? and reservable_id = ?', reservable.class.name, reservable.id)
               .includes(:slots, statistic_profile: [user: [:profile]])
               .references(:slots, :user)
               .where('slots.start_at > ?', user.admin? ? 1.month.ago : DateTime.current)
  end

  def availabilities(reservable, type, user)
    if user.admin? || user.manager?
      reservable.availabilities
                .includes(:tags)
                .where('end_at > ? AND available_type = ?', 1.month.ago, type)
                .where(lock: false)
    else
      end_at = @maximum_visibility[:other]
      end_at = @maximum_visibility[:year] if subscription_year?(user)
      reservable.availabilities
                .includes(:tags)
                .where('end_at > ? AND end_at < ? AND available_type = ?', DateTime.current, end_at, type)
                .where('availability_tags.tag_id' => user.tag_ids.concat([nil]))
                .where(lock: false)
    end
  end

  def training_availabilities(training_id, user)
    availabilities = if training_id.is_number? || (training_id.length.positive? && training_id != 'all')
                       Training.friendly.find(training_id).availabilities
                     else
                       Availability.trainings
                     end

    # who made the request?
    # 1) an admin (he can see all availabilities of 1 month ago and future)
    if @current_user.admin?
      availabilities.includes(:tags, :slots, trainings: [:machines])
                    .where('availabilities.start_at > ?', 1.month.ago)
                    .where(lock: false)
    # 2) an user (he cannot see availabilities further than 1 (or 3) months)
    else
      end_at = @maximum_visibility[:other]
      end_at = @maximum_visibility[:year] if show_extended_slots?(user)
      availabilities.includes(:tags, :slots, :availability_tags, trainings: [:machines])
                    .where('availabilities.start_at > ? AND availabilities.start_at < ?', DateTime.current, end_at)
                    .where('availability_tags.tag_id' => user.tag_ids.concat([nil]))
                    .where(lock: false)
    end
  end
end
