# frozen_string_literal: true

# This will generate statistics indicators for ElasticSearch database
class StatisticService
  def generate_statistic(options = default_options)
    # remove data exists
    clean_stat(options)

    # subscription month/year list
    subscriptions_list(options).each do |s|
      Stats::Subscription.create({
        date: format_date(s.date),
        type: s.duration,
        subType: s.slug,
        stat: 1,
        ca: s.ca,
        planId: s.plan_id,
        subscriptionId: s.subscription_id,
        invoiceItemId: s.invoice_item_id,
        groupName: s.plan_group_name
      }.merge(user_info_stat(s)))
    end

    # machine list
    reservations_machine_list(options).each do |r|
      %w[booking hour].each do |type|
        stat = Stats::Machine.new({
          date: format_date(r.date),
          type: type,
          subType: r.machine_type,
          ca: r.ca,
          machineId: r.machine_id,
          name: r.machine_name,
          reservationId: r.reservation_id
        }.merge(user_info_stat(r)))
        stat.stat = (type == 'booking' ? 1 : r.nb_hours)
        stat.save
      end
    end

    # space list
    reservations_space_list(options).each do |r|
      %w[booking hour].each do |type|
        stat = Stats::Space.new({
          date: format_date(r.date),
          type: type,
          subType: r.space_type,
          ca: r.ca,
          spaceId: r.space_id,
          name: r.space_name,
          reservationId: r.reservation_id
        }.merge(user_info_stat(r)))
        stat.stat = (type == 'booking' ? 1 : r.nb_hours)
        stat.save
      end
    end

    # training list
    reservations_training_list(options).each do |r|
      %w[booking hour].each do |type|
        stat = Stats::Training.new({
          date: format_date(r.date),
          type: type,
          subType: r.training_type,
          ca: r.ca,
          trainingId: r.training_id,
          name: r.training_name,
          trainingDate: r.training_date,
          reservationId: r.reservation_id
        }.merge(user_info_stat(r)))
        stat.stat = (type == 'booking' ? 1 : r.nb_hours)
        stat.save
      end
    end

    # event list
    reservations_event_list(options).each do |r|
      %w[booking hour].each do |type|
        stat = Stats::Event.new({
          date: format_date(r.date),
          type: type,
          subType: r.event_type,
          ca: r.ca,
          eventId: r.event_id,
          name: r.event_name,
          eventDate: r.event_date,
          reservationId: r.reservation_id,
          eventTheme: r.event_theme,
          ageRange: r.age_range
        }.merge(user_info_stat(r)))
        stat.stat = (type == 'booking' ? r.nb_places : r.nb_hours)
        stat.save
      end
    end

    # account list
    members_list(options).each do |m|
      Stats::Account.create({
        date: format_date(m.date),
        type: 'member',
        subType: 'created',
        stat: 1
      }.merge(user_info_stat(m)))
    end

    # project list
    projects_list(options).each do |p|
      Stats::Project.create({
        date: format_date(p.date),
        type: 'project',
        subType: 'published',
        stat: 1
      }.merge(user_info_stat(p)).merge(project_info_stat(p)))
    end

    # member ca list
    members_ca_list(options).each do |m|
      Stats::User.create({
        date: format_date(m.date),
        type: 'revenue',
        subType: m.group,
        stat: m.ca
      }.merge(user_info_stat(m)))
    end
  end

  def subscriptions_list(options = default_options)
    result = []
    InvoiceItem.where('invoice_items.created_at >= :start_date AND invoice_items.created_at <= :end_date', options)
               .eager_load(invoice: [:coupon], subscription: [:plan, statistic_profile: [:group]]).each do |i|
      next if i.invoice.is_a?(Avoir)

      sub = i.subscription

      next unless sub

      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 OpenStruct.new({
        date: options[:start_date].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,
        ca: ca
      }.merge(user_info(profile)))
    end
    result
  end

  def reservations_machine_list(options = default_options)
    result = []
    Reservation
      .where("reservable_type = 'Machine' AND slots.canceled_at IS NULL AND " \
             'reservations.created_at >= :start_date AND reservations.created_at <= :end_date', options)
      .eager_load(:slots, statistic_profile: [:group], invoice: [:invoice_items])
      .each do |r|
      next unless r.reservable

      profile = r.statistic_profile
      result.push OpenStruct.new({
        date: options[:start_date].to_date,
        reservation_id: r.id,
        machine_id: r.reservable.id,
        machine_type: r.reservable.friendly_id,
        machine_name: r.reservable.name,
        nb_hours: r.slots.size,
        ca: calcul_ca(r.invoice)
      }.merge(user_info(profile)))
    end
    result
  end

  def reservations_space_list(options = default_options)
    result = []
    Reservation
      .where("reservable_type = 'Space' AND slots.canceled_at IS NULL AND " \
             'reservations.created_at >= :start_date AND reservations.created_at <= :end_date', options)
      .eager_load(:slots, statistic_profile: [:group], invoice: [:invoice_items])
      .each do |r|
      next unless r.reservable

      profile = r.statistic_profile
      result.push OpenStruct.new({
        date: options[:start_date].to_date,
        reservation_id: r.id,
        space_id: r.reservable.id,
        space_name: r.reservable.name,
        space_type: r.reservable.slug,
        nb_hours: r.slots.size,
        ca: calcul_ca(r.invoice)
      }.merge(user_info(profile)))
    end
    result
  end

  def reservations_training_list(options = default_options)
    result = []
    Reservation
      .where("reservable_type = 'Training' AND slots.canceled_at IS NULL AND " \
             'reservations.created_at >= :start_date AND reservations.created_at <= :end_date', options)
      .eager_load(:slots, statistic_profile: [:group], invoice: [:invoice_items])
      .each do |r|
      next unless r.reservable

      profile = r.statistic_profile
      slot = r.slots.first
      result.push OpenStruct.new({
        date: options[:start_date].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.invoice)
      }.merge(user_info(profile)))
    end
    result
  end

  def reservations_event_list(options = default_options)
    result = []
    Reservation
      .where("reservable_type = 'Event' AND slots.canceled_at IS NULL AND " \
             'reservations.created_at >= :start_date AND reservations.created_at <= :end_date', options)
      .eager_load(:slots, statistic_profile: [:group], invoice: [:invoice_items])
      .each do |r|
      next unless r.reservable

      profile = r.statistic_profile
      slot = r.slots.first
      result.push OpenStruct.new({
        date: options[:start_date].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),
        ca: calcul_ca(r.invoice)
      }.merge(user_info(profile)))
    end
    result
  end

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

      reservations_ca_list.push OpenStruct.new({
        date: options[:start_date].to_date,
        ca: calcul_ca(r.invoice)
      }.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])
         .each do |i|
      # the following line is a workaround for issue #196
      profile = i.statistic_profile || i.invoiced&.wallet&.user&.statistic_profile
      avoirs_ca_list.push OpenStruct.new({
        date: options[:start_date].to_date,
        ca: calcul_avoir_ca(i)
      }.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_info_list(profile, result)
      u.date = options[:start_date].to_date
      e.ca = 0 unless e.ca
      if u.ca
        u.ca = u.ca + e.ca
      else
        u.ca = 0
        u.ca = u.ca + e.ca
        result.push u
      end
    end
    result
  end

  def members_list(options = default_options)
    result = []
    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))
                    .each do |sp|
      next if sp.user&.need_completion?

      result.push OpenStruct.new({
        date: options[:start_date].to_date
      }.merge(user_info(sp)))
    end
    result
  end

  def projects_list(options = default_options)
    result = []
    Project.where('projects.published_at >= :start_date AND projects.published_at <= :end_date', options)
           .eager_load(:licence, :themes, :components, :machines, :project_users, author: [:group])
           .each do |p|
      result.push OpenStruct.new({
        date: options[:start_date].to_date
      }.merge(user_info(p.author)).merge(project_info(p)))
    end
    result
  end

  # return always yesterday's sum of comment of each project
  # def projects_comment_nb_list
  #   result = []
  #   Project.where(state: 'published')
  #          .eager_load(:licence, :themes, :components, :machines, :project_users, author: %i[profile group])
  #          .each do |p|
  #     result.push OpenStruct.new({
  #       date: 1.day.ago.to_date,
  #       project_comments: get_project_comment_nb(p)
  #     }.merge(user_info(p.author)).merge(project_info(p)))
  #   end
  #   result
  # end

  def clean_stat(options = default_options)
    client = Elasticsearch::Model.client
    %w[Account Event Machine Project Subscription Training User Space].each do |o|
      model = "Stats::#{o}".constantize
      client.delete_by_query(
        index: model.index_name,
        type: model.document_type,
        body: { query: { match: { date: format_date(options[:start_date]) } } }
      )
    end
  end

  private

  def default_options
    yesterday = 1.day.ago
    {
      start_date: yesterday.beginning_of_day,
      end_date: yesterday.end_of_day
    }
  end

  def format_date(date)
    if date.is_a?(String)
      Date.strptime(date, '%Y%m%d').strftime('%Y-%m-%d')
    else
      date.strftime('%Y-%m-%d')
    end
  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
    }
  end

  def user_info_stat(s)
    {
      userId: s.user_id,
      gender: s.gender,
      age: s.age,
      group: s.group
    }
  end

  def calcul_ca(invoice)
    return nil unless invoice

    ca = 0
    # sum each items in the invoice (+ for invoices/- for refunds)
    invoice.invoice_items.each do |ii|
      next if ii.subscription_id

      ca = if invoice.is_a?(Avoir)
             ca - ii.amount.to_i
           else
             ca + ii.amount.to_i
           end
    end
    # subtract coupon discount from invoices and refunds
    cs = CouponService.new
    ca = cs.ventilate(cs.invoice_total_no_coupon(invoice), ca, invoice.coupon) unless invoice.coupon_id.nil?
    # divide the result by 100 to convert from centimes to monetary unit
    ca.zero? ? ca : ca / 100.0
  end

  def calcul_avoir_ca(invoice)
    ca = 0
    invoice.invoice_items.each do |ii|
      ca -= ii.amount.to_i
    end
    # subtract coupon discount from the refund
    cs = CouponService.new
    ca = cs.ventilate(cs.invoice_total_no_coupon(invoice), ca, invoice.coupon) unless invoice.coupon_id.nil?
    ca.zero? ? ca : ca / 100.0
  end

  def difference_in_hours(start_at, end_at)
    if start_at.to_date == end_at.to_date
      ((end_at - start_at) / 60 / 60).to_i
    else
      end_at_to_start_date = end_at.change(year: start_at.year, month: start_at.month, day: start_at.day)
      hours = ((end_at_to_start_date - start_at) / 60 / 60).to_i
      hours = ((end_at.to_date - start_at.to_date).to_i + 1) * hours if end_at.to_date > start_at.to_date
      hours
    end
  end

  def get_project_themes(project)
    project.themes.map do |t|
      { id: t.id, name: t.name }
    end
  end

  def get_projects_components(project)
    project.components.map do |c|
      { id: c.id, name: c.name }
    end
  end

  def get_projects_machines(project)
    project.machines.map do |m|
      { id: m.id, name: m.name }
    end
  end

  def get_project_users(project)
    sum = 0
    project.project_users.each do |pu|
      sum += 1 if pu.is_valid
    end
    sum
  end

  # def get_project_comment_nb(project)
  #   project_comment_info = @projects_comment_info.select do |p|
  #     p['identifiers'].first == "project_#{project.id}"
  #   end.first
  #   project_comment_info ? project_comment_info['posts'] : 0
  # end

  def project_info(project)
    {
      project_id: project.id,
      project_name: project.name,
      project_created_at: project.created_at,
      project_published_at: project.published_at,
      project_licence: {},
      project_themes: get_project_themes(project),
      project_components: get_projects_components(project),
      project_machines: get_projects_machines(project),
      project_users: get_project_users(project)
    }
  end

  def project_info_stat(project)
    {
      projectId: project.project_id,
      name: project.project_name,
      licence: project.project_licence,
      themes: project.project_themes,
      components: project.project_components,
      machines: project.project_machines,
      users: project.project_users
    }
  end

  # def get_user_subscription_ca(user, subscriptions_ca_list)
  #   user_subscription_ca = subscriptions_ca_list.select do |ca|
  #     ca.user_id == user.id
  #   end
  #   user_subscription_ca.inject {|sum,x| sum.ca + x.ca } || 0
  # end

  def find_or_create_user_info_info_list(profile, list)
    found = list.select do |l|
      l.statistic_profile_id == profile.id
    end.first
    found || OpenStruct.new(user_info(profile))
  end
end
