# frozen_string_literal: true

# Fetch data from the PostgreSQL database and prepare them
# to be used in the statistics generation
class Statistics::FetcherService
  include Statistics::Concerns::HelpersConcern
  include Statistics::Concerns::ComputeConcern
  include Statistics::Concerns::ProjectsConcern
  include Statistics::Concerns::StoreOrdersConcern

  class << self
    def subscriptions_list(options = default_options)
      result = []
      InvoiceItem.where("object_type = '#{Subscription.name}' AND invoice_items.created_at >= :start_date " \
                        'AND invoice_items.created_at <= :end_date', options)
                 .eager_load(invoice: [:coupon]).find_each do |i|
        next if i.invoice.is_a?(Avoir)

        sub = i.object

        ca = i.amount.to_i
        cs = CouponService.new
        ca = cs.ventilate(cs.invoice_total_no_coupon(i.invoice), ca, i.invoice.coupon) unless i.invoice.coupon_id.nil?
        ca /= 100.00
        profile = sub.statistic_profile
        p = sub.plan
        result.push({ date: i.created_at.to_date,
                      plan: p.group.slug,
                      plan_id: p.id,
                      plan_interval: p.interval,
                      plan_interval_count: p.interval_count,
                      plan_group_name: p.group.name,
                      slug: p.slug,
                      duration: p.find_statistic_type.key,
                      subscription_id: sub.id,
                      invoice_item_id: i.id,
                      coupon: i.invoice.coupon&.code,
                      ca: ca }.merge(user_info(profile)))
      end
      result
    end

    # @param options [Hash]
    # @yield [reservation]
    # @yieldparam [Hash]
    def each_machine_reservation(options = default_options)
      Reservation
        .where("reservable_type = 'Machine' AND slots_reservations.canceled_at IS NULL AND " \
               'reservations.created_at >= :start_date AND reservations.created_at <= :end_date', options)
        .eager_load(:slots, :slots_reservations, :invoice_items, :reservation_context, statistic_profile: [:group])
        .find_each do |r|
        next unless r.reservable
        next if r.slots.empty?

        profile = r.statistic_profile
        result = { date: r.created_at.to_date,
                   reservation_id: r.id,
                   machine_id: r.reservable.id,
                   machine_type: r.reservable.friendly_id,
                   machine_name: r.reservable.name,
                   slot_dates: r.slots.map(&:start_at).map(&:to_date),
                   nb_hours: (r.slots.map(&:duration).map(&:to_i).reduce(:+) / 3600.0).to_f,
                   ca: calcul_ca(r.original_invoice),
                   reservation_context_id: r.reservation_context_id,
                   coupon: r&.original_invoice&.coupon&.code }.merge(user_info(profile))
        yield result
      end
    end

    # @param options [Hash]
    # @yield [reservation]
    # @yieldparam [Hash]
    def each_space_reservation(options = default_options)
      Reservation
        .where("reservable_type = 'Space' AND slots_reservations.canceled_at IS NULL AND " \
               'reservations.created_at >= :start_date AND reservations.created_at <= :end_date', options)
        .eager_load(:slots, :slots_reservations, :invoice_items, :reservation_context, statistic_profile: [:group])
        .find_each do |r|
        next unless r.reservable
        next if r.slots.empty?

        profile = r.statistic_profile
        result = { date: r.created_at.to_date,
                   reservation_id: r.id,
                   space_id: r.reservable.id,
                   space_name: r.reservable.name,
                   space_type: r.reservable.slug,
                   slot_dates: r.slots.map(&:start_at).map(&:to_date),
                   nb_hours: (r.slots.map(&:duration).map(&:to_i).reduce(:+) / 3600.0).to_f,
                   ca: calcul_ca(r.original_invoice),
                   reservation_context_id: r.reservation_context_id,
                   coupon: r&.original_invoice&.coupon&.code }.merge(user_info(profile))
        yield result
      end
    end

    # @param options [Hash]
    # @yield [reservation]
    # @yieldparam [Hash]
    def each_training_reservation(options = default_options)
      Reservation
        .where("reservable_type = 'Training' AND slots_reservations.canceled_at IS NULL AND " \
               'reservations.created_at >= :start_date AND reservations.created_at <= :end_date', options)
        .eager_load(:slots, :slots_reservations, :invoice_items, :reservation_context, statistic_profile: [:group])
        .find_each do |r|
        next unless r.reservable
        next if r.slots.empty?

        profile = r.statistic_profile
        slot = r.slots.first
        result = { date: r.created_at.to_date,
                   reservation_id: r.id,
                   training_id: r.reservable.id,
                   training_type: r.reservable.friendly_id,
                   training_name: r.reservable.name,
                   training_date: slot.start_at.to_date,
                   nb_hours: difference_in_hours(slot.start_at, slot.end_at),
                   ca: calcul_ca(r.original_invoice),
                   reservation_context_id: r.reservation_context_id,
                   coupon: r&.original_invoice&.coupon&.code }.merge(user_info(profile))
        yield result
      end
    end

    # @param options [Hash]
    # @yield [reservation]
    # @yieldparam [Hash]
    def each_event_reservation(options = default_options)
      Reservation
        .where("reservable_type = 'Event' AND slots_reservations.canceled_at IS NULL AND " \
               'reservations.created_at >= :start_date AND reservations.created_at <= :end_date', options)
        .eager_load(:slots, :slots_reservations, :invoice_items, statistic_profile: [:group])
        .find_each do |r|
        next unless r.reservable
        next if r.slots.empty?

        profile = r.statistic_profile
        slot = r.slots.first
        result = { date: r.created_at.to_date,
                   reservation_id: r.id,
                   event_id: r.reservable.id,
                   event_type: r.reservable.category.slug,
                   event_name: r.reservable.name,
                   event_date: slot.start_at.to_date,
                   event_theme: (r.reservable.event_themes.first ? r.reservable.event_themes.first.name : ''),
                   age_range: (r.reservable.age_range_id ? r.reservable.age_range.name : ''),
                   nb_places: r.total_booked_seats,
                   nb_hours: difference_in_hours(slot.start_at, slot.end_at),
                   coupon: r&.original_invoice&.coupon&.code,
                   ca: calcul_ca(r.original_invoice) }.merge(user_info(profile))
        yield result
      end
    end

    def members_ca_list(options = default_options)
      subscriptions_ca_list = subscriptions_list(options)
      reservations_ca_list = []
      avoirs_ca_list = []
      users_list = []
      Reservation.where('reservations.created_at >= :start_date AND reservations.created_at <= :end_date', options)
                 .eager_load(:slots, :invoice_items, statistic_profile: [:group])
                 .find_each do |r|
        next unless r.reservable
        next unless r.statistic_profile

        reservations_ca_list.push(
          { date: r.created_at.to_date, ca: calcul_ca(r.original_invoice) || 0 }.merge(user_info(r.statistic_profile))
        )
      end
      Avoir.where('invoices.created_at >= :start_date AND invoices.created_at <= :end_date', options)
           .eager_load(:invoice_items, statistic_profile: [:group])
           .find_each do |i|
        # the following line is a workaround for issue #196
        profile = i.statistic_profile || i.main_item.object&.wallet&.user&.statistic_profile
        next unless profile

        avoirs_ca_list.push({ date: i.created_at.to_date, ca: calcul_avoir_ca(i) || 0 }.merge(user_info(profile)))
      end
      reservations_ca_list.concat(subscriptions_ca_list).concat(avoirs_ca_list).each do |e|
        profile = StatisticProfile.find(e[:statistic_profile_id])
        u = find_or_create_user_info(profile, users_list)
        u[:date] = e[:date]
        add_ca(u, e[:ca], users_list)
      end
      users_list
    end

    # @param options [Hash]
    # @yield [reservation]
    # @yieldparam [Hash]
    def each_member(options = default_options)
      member = Role.find_by(name: 'member')
      StatisticProfile.where('role_id = :member AND created_at >= :start_date AND created_at <= :end_date',
                             options.merge(member: member.id))
                      .find_each do |sp|
        next if sp.user&.need_completion?

        result = { date: sp.created_at.to_date }.merge(user_info(sp))
        yield result
      end
    end

    # @param options [Hash]
    # @yield [reservation]
    # @yieldparam [Hash]
    def each_project(options = default_options)
      Project.where('projects.published_at >= :start_date AND projects.published_at <= :end_date', options)
             .eager_load(:licence, :themes, :components, :machines, :status, project_users: [{ user: :profile }], author: [:group])
             .find_each do |p|
        result = { date: p.created_at.to_date }.merge(user_info(p.author)).merge(project_info(p))
        yield result
      end
    end

    # @param states [Array<String>]
    # @param options [Hash]
    # @yield [reservation]
    # @yieldparam [Hash]
    def each_store_order(states, options = default_options)
      Order.includes(order_items: [:orderable])
           .joins(:order_items, :order_activities)
           .where(order_items: { orderable_type: 'Product' })
           .where(orders: { state: states })
           .where('order_activities.created_at >= :start_date AND order_activities.created_at <= :end_date', options)
           .group('orders.id')
           .find_each do |o|
        next unless o.invoice

        result = { date: o.created_at.to_date, ca: calcul_ca(o.invoice), coupon: o.invoice.coupon&.code }
                 .merge(user_info(o.statistic_profile))
                 .merge(store_order_info(o))
        yield result
      end
    end

    private

    def add_ca(profile, new_ca, users_list)
      if profile[:ca]
        profile[:ca] += new_ca || 0
      else
        profile[:ca] = new_ca || 0
        users_list.push profile
      end
    end

    def find_or_create_user_info(profile, list)
      found = list.find do |l|
        l[:statistic_profile_id] == profile.id
      end
      found || user_info(profile)
    end

    def user_info(statistic_profile)
      return {} unless statistic_profile

      {
        statistic_profile_id: statistic_profile.id,
        user_id: statistic_profile.user_id,
        gender: statistic_profile.str_gender,
        age: statistic_profile.age,
        group: statistic_profile.group ? statistic_profile.group.slug : nil,
        groupName: statistic_profile.group ? statistic_profile.group.name : nil,
      }
    end
  end
end
