# frozen_string_literal: true

# Provides methods for working with cart items
class CartService
  def initialize(operator)
    @operator = operator
  end

  # For details about the expected hash format
  # @see app/frontend/src/javascript/models/payment.ts > interface ShoppingCart
  # @return [ShoppingCart]
  def from_hash(cart_items)
    cart_items.permit! if cart_items.is_a? ActionController::Parameters

    @customer = customer(cart_items)
    plan_info = plan(cart_items)

    items = []
    cart_items[:items].each do |item|
      if ['subscription', :subscription].include?(item.keys.first) && plan_info[:new_subscription]
        items.push(CartItem::Subscription.new(
                     plan: plan_info[:plan],
                     customer_profile: @customer.invoicing_profile,
                     start_at: item[:subscription][:start_at]
                   ))
      elsif ['reservation', :reservation].include?(item.keys.first)
        items.push(reservable_from_hash(item[:reservation], plan_info))
      elsif ['prepaid_pack', :prepaid_pack].include?(item.keys.first)
        items.push(CartItem::PrepaidPack.new(
                     prepaid_pack: PrepaidPack.find(item[:prepaid_pack][:id]),
                     customer_profile: @customer.invoicing_profile
                   ))
      elsif ['free_extension', :free_extension].include?(item.keys.first)
        items.push(CartItem::FreeExtension.new(
                     customer_profile: @customer.invoicing_profile,
                     subscription: plan_info[:subscription],
                     new_expiration_date: item[:free_extension][:end_at]
                   ))
      end
    end

    coupon = CartItem::Coupon.new(
      customer_profile: @customer.invoicing_profile,
      operator_profile: @operator.invoicing_profile,
      coupon: Coupon.find_by(code: cart_items[:coupon_code])
    )
    schedule = CartItem::PaymentSchedule.new(
      plan: plan_info[:plan],
      coupon: coupon,
      requested: cart_items[:payment_schedule],
      customer_profile: @customer.invoicing_profile,
      start_at: plan_info[:subscription]&.start_at
    )

    ShoppingCart.new(
      @customer,
      @operator,
      coupon,
      schedule,
      cart_items[:payment_method],
      items: items
    )
  end

  def from_payment_schedule(payment_schedule)
    @customer = payment_schedule.user
    subscription = payment_schedule.payment_schedule_objects.find { |pso| pso.object_type == Subscription.name }&.subscription
    plan = subscription&.plan

    coupon = CartItem::Coupon.new(
      customer_profile: @customer.invoicing_profile,
      operator_profile: @operator.invoicing_profile,
      coupon: payment_schedule.coupon
    )
    schedule = CartItem::PaymentSchedule.new(
      plan: plan,
      coupon: coupon,
      requested: true,
      customer_profile: @customer.invoicing_profile,
      start_at: subscription.start_at
    )

    items = []
    payment_schedule.payment_schedule_objects.each do |object|
      if object.object_type == Subscription.name
        items.push(CartItem::Subscription.new(
                     plan: object.subscription.plan,
                     customer_profile: @customer.invoicing_profile,
                     start_at: object.subscription.start_at
                   ))
      elsif object.object_type == Reservation.name
        items.push(reservable_from_payment_schedule_object(object, plan))
      elsif object.object_type == PrepaidPack.name
        items.push(CartItem::PrepaidPack.new(
                     prepaid_pack_id: object.statistic_profile_prepaid_pack.prepaid_pack_id,
                     customer_profile: @customer.invoicing_profile
                   ))
      elsif object.object_type == OfferDay.name
        items.push(CartItem::FreeExtension.new(
                     customer_profile: @customer.invoicing_profile,
                     subscription: object.offer_day.subscription,
                     new_expiration_date: object.offer_day.end_date
                   ))
      end
    end

    ShoppingCart.new(
      @customer,
      @operator,
      coupon,
      schedule,
      payment_schedule.payment_method,
      items: items
    )
  end

  private

  def plan(cart_items)
    new_plan_being_bought = false
    subscription = nil
    plan = if cart_items[:items].any? { |item| ['subscription', :subscription].include?(item.keys.first) }
             index = cart_items[:items].index { |item| ['subscription', :subscription].include?(item.keys.first) }
             if cart_items[:items][index][:subscription][:plan_id]
               new_plan_being_bought = true
               plan = Plan.find(cart_items[:items][index][:subscription][:plan_id])
               subscription = CartItem::Subscription.new(
                 plan: plan,
                 customer_profile: @customer.invoicing_profile,
                 start_at: cart_items[:items][index][:subscription][:start_at]
               ).to_object
               plan
             end
           elsif @customer.subscribed_plan
             subscription = @customer.subscription unless @customer.subscription.expired_at < Time.current
             @customer.subscribed_plan
           else
             nil
           end
    { plan: plan, subscription: subscription, new_subscription: new_plan_being_bought }
  end

  def customer(cart_items)
    if @operator.admin? || (@operator.manager? && @operator.id != cart_items[:customer_id])
      User.find(cart_items[:customer_id])
    else
      @operator
    end
  end

  def reservable_from_hash(cart_item, plan_info)
    reservable = cart_item[:reservable_type]&.constantize&.find(cart_item[:reservable_id])
    case reservable
    when Machine
      CartItem::MachineReservation.new(customer_profile: @customer.invoicing_profile,
                                       operator_profile: @operator.invoicing_profile,
                                       reservable: reservable,
                                       cart_item_reservation_slots_attributes: cart_item[:slots_reservations_attributes],
                                       plan: plan_info[:plan],
                                       new_subscription: plan_info[:new_subscription],
                                       reservation_context_id: cart_item[:reservation_context_id])
    when Training
      CartItem::TrainingReservation.new(customer_profile: @customer.invoicing_profile,
                                        operator_profile: @operator.invoicing_profile,
                                        reservable: reservable,
                                        cart_item_reservation_slots_attributes: cart_item[:slots_reservations_attributes],
                                        plan: plan_info[:plan],
                                        new_subscription: plan_info[:new_subscription],
                                        reservation_context_id: cart_item[:reservation_context_id])
    when Event
      CartItem::EventReservation.new(customer_profile: @customer.invoicing_profile,
                                     operator_profile: @operator.invoicing_profile,
                                     event: reservable,
                                     cart_item_reservation_slots_attributes: cart_item[:slots_reservations_attributes],
                                     normal_tickets: cart_item[:nb_reserve_places],
                                     cart_item_event_reservation_tickets_attributes: cart_item[:tickets_attributes] || {},
                                     cart_item_event_reservation_booking_users_attributes: cart_item[:booking_users_attributes] || {})
    when Space
      CartItem::SpaceReservation.new(customer_profile: @customer.invoicing_profile,
                                     operator_profile: @operator.invoicing_profile,
                                     reservable: reservable,
                                     cart_item_reservation_slots_attributes: cart_item[:slots_reservations_attributes],
                                     plan: plan_info[:plan],
                                     new_subscription: plan_info[:new_subscription],
                                     reservation_context_id: cart_item[:reservation_context_id])
    else
      Rails.logger.warn "the reservable #{reservable} is not implemented"
      raise NotImplementedError
    end
  end

  def reservable_from_payment_schedule_object(object, plan)
    reservable = object.reservation.reservable
    cart_item_reservation_slots = object.reservation.slots_reservations.map do |s|
      { slot_id: s.slot_id, slots_reservation_id: s.id, offered: s.offered }
    end
    case reservable
    when Machine
      CartItem::MachineReservation.new(customer_profile: @customer.invoicing_profile,
                                       operator_profile: @operator.invoicing_profile,
                                       reservable: reservable,
                                       cart_item_reservation_slots_attributes: cart_item_reservation_slots,
                                       plan: plan,
                                       new_subscription: true)
    when Training
      CartItem::TrainingReservation.new(customer_profile: @customer.invoicing_profile,
                                        operator_profile: @operator.invoicing_profile,
                                        reservable: reservable,
                                        cart_item_reservation_slots_attributes: cart_item_reservation_slots,
                                        plan: plan,
                                        new_subscription: true)
    when Event
      CartItem::EventReservation.new(customer_profile: @customer.invoicing_profile,
                                     operator_profile: @operator.invoicing_profile,
                                     event: reservable,
                                     cart_item_reservation_slots_attributes: cart_item_reservation_slots,
                                     normal_tickets: object.reservation.nb_reserve_places,
                                     cart_item_event_reservation_tickets_attributes: object.reservation.tickets)
    when Space
      CartItem::SpaceReservation.new(customer_profile: @customer.invoicing_profile,
                                     operator_profile: @operator.invoicing_profile,
                                     reservable: reservable,
                                     cart_item_reservation_slots_attributes: cart_item_reservation_slots,
                                     plan: plan,
                                     new_subscription: true)
    else
      Rails.logger.warn "WARNING: the reservable #{reservable} is not implemented"
      raise NotImplementedError
    end
  end
end
