# frozen_string_literal: true

require 'open_food_network/permissions'

module Permissions
  class Order
    def initialize(user, search_params = nil)
      @user = user
      @permissions = OpenFoodNetwork::Permissions.new(@user)
      @search_params = search_params
    end

    # Find orders that the user can see. This includes any order where the producer has permissions
    # and has at least *one* of their supplied products in the order. Additional scoping may be
    # needed for queries showing line items per producer.
    def visible_orders
      orders = Spree::Order.
        with_line_items_variants_and_products_outer.
        where(visible_orders_where_values)

      filtered_orders(orders)
    end

    def managed_or_coordinated_orders_where_clause
      Spree::Order.where(
        managed_orders_where_values.or(coordinated_orders_where_values)
      )
    end

    # Any orders that the user can edit
    def editable_orders
      orders = if @user.admin?
                 # It returns all orders if the user is an admin
                 managed_or_coordinated_orders_where_clause
               else
                 Spree::Order.joins(:distributor).where(
                   id: produced_orders.select(:id),
                   distributor: { enable_producers_to_edit_orders: true }
                 ).or(
                   managed_or_coordinated_orders_where_clause
                 )
               end

      filtered_orders(orders)
    end

    def visible_line_items
      editable_line_items.or(produced_line_items)
    end

    # Any line items that I can edit
    def editable_line_items
      managed_or_coordinated_line_items_where_clause = Spree::LineItem.where(
        order_id: filtered_orders(managed_or_coordinated_orders_where_clause).select(:id)
      )

      if @user.admin?
        # It returns all line_items if the user is an admin
        managed_or_coordinated_line_items_where_clause
      else
        Spree::LineItem.editable_by_producers(
          @permissions.managed_enterprises.select("enterprises.id")
        ).or(
          managed_or_coordinated_line_items_where_clause
        )
      end
    end

    private

    attr_reader :search_params

    def filtered_orders(orders)
      return orders unless filter_orders?

      orders.complete.not_state(:canceled).ransack(search_params).result
    end

    def filter_orders?
      search_params.present?
    end

    def visible_orders_where_values
      # Grouping keeps the 2 where clauses from produced_orders_where_values inside parentheses
      #   This way it makes the OR work between the 3 types of orders:
      #     produced, managed and coordinated
      Spree::Order.arel_table.
        grouping(produced_orders_where_values).
        or(managed_orders_where_values).
        or(coordinated_orders_where_values)
    end

    # Any orders placed through any hub that I manage
    def managed_orders_where_values
      Spree::Order.
        where(distributor_id: @permissions.managed_enterprises.select("enterprises.id")).
        where_clause.__send__(:predicates).
        reduce(:and)
    end

    # Any order that is placed through an order cycle one of my managed enterprises coordinates
    def coordinated_orders_where_values
      Spree::Order.
        where(order_cycle_id: @permissions.coordinated_order_cycles.select(:id)).
        where_clause.__send__(:predicates).
        reduce(:and)
    end

    def produced_orders
      Spree::Order.with_line_items_variants_and_products_outer.
        where(
          spree_variants: { supplier_id: @permissions.managed_enterprises.select("enterprises.id") }
        )
    end

    def produced_orders_where_values
      Spree::Order.with_line_items_variants_and_products_outer.
        where(
          distributor_id: granted_distributor_ids,
          spree_variants: { supplier_id: enterprises_with_associated_orders }
        ).
        where_clause.__send__(:predicates).
        reduce(:and)
    end

    def enterprises_with_associated_orders
      # Any orders placed through hubs that my producers have granted P-OC,
      #   and which contain their products. This is pretty complicated but it's looking for order
      #   where at least one of my producers has granted P-OC to the distributor
      #   AND the order contains products of at least one of THE SAME producers
      @permissions.related_enterprises_granting(:add_to_order_cycle, to: granted_distributor_ids).
        merge(@permissions.managed_enterprises.is_primary_producer)
    end

    def granted_distributor_ids
      @granted_distributor_ids ||= @permissions.related_enterprises_granted(
        :add_to_order_cycle,
        by: @permissions.managed_enterprises.is_primary_producer.select("enterprises.id")
      ).select("enterprises.id")
    end

    # Any from visible orders, where the product is produced by one of my managed producers
    def produced_line_items
      Spree::LineItem.where(order_id: visible_orders.select("DISTINCT spree_orders.id")).
        supplied_by_any(
          @permissions.managed_enterprises.is_primary_producer.select("enterprises.id")
        )
    end
  end
end
