# frozen_string_literal: true

RSpec.describe Orders::AvailableShippingMethodsService do
  context "when the order has no current_distributor" do
    it "returns an empty array" do
      order_cycle = create(:sells_own_order_cycle)
      order = build(:order, distributor: nil, order_cycle:)

      expect(Orders::AvailableShippingMethodsService.new(order).to_a).to eq []
    end
  end

  it "does not return 'back office only' shipping method" do
    distributor = create(:distributor_enterprise)
    frontend_shipping_method = create(:shipping_method, distributors: [distributor])
    backoffice_only_shipping_method = create(:shipping_method,
                                             distributors: [distributor], display_on: 'back_end')
    order_cycle = create(:sells_own_order_cycle)
    order = build(:order, distributor:, order_cycle:)

    available_shipping_methods = Orders::AvailableShippingMethodsService.new(order).to_a

    expect(available_shipping_methods).to eq [frontend_shipping_method]
  end

  context "when no tag rules are in effect" do
    context "sells own order cycle i.e. simple" do
      it "only returns the shipping methods which are available on the order cycle
          and belong to the order distributor" do
        distributor_i = create(:distributor_enterprise)
        distributor_ii = create(:distributor_enterprise)
        distributor_iii = create(:distributor_enterprise)
        shipping_method_i = create(:shipping_method, distributors: [distributor_i])
        shipping_method_ii = create(:shipping_method, distributors: [distributor_ii])
        shipping_method_iii = create(:shipping_method, distributors: [distributor_iii])
        order_cycle = create(:sells_own_order_cycle, distributors: [distributor_i, distributor_ii])
        order = build(:order, distributor: distributor_i, order_cycle:)

        available_shipping_methods = Orders::AvailableShippingMethodsService.new(order).to_a

        expect(available_shipping_methods).to eq [shipping_method_i]
      end
    end

    context "distributor order cycle i.e. not simple" do
      it "only returns the shipping methods which are available on the order cycle
          and belong to the order distributor" do
        distributor_i = create(:distributor_enterprise, shipping_methods: [])
        distributor_ii = create(:distributor_enterprise, shipping_methods: [])
        shipping_method_i = create(:shipping_method, distributors: [distributor_i])
        shipping_method_ii = create(:shipping_method, distributors: [distributor_i])
        shipping_method_iii = create(:shipping_method, distributors: [distributor_ii])
        shipping_method_iv = create(:shipping_method, distributors: [distributor_ii])
        order_cycle = create(:distributor_order_cycle,
                             distributors: [distributor_i, distributor_ii])
        order_cycle.selected_distributor_shipping_methods << [
          distributor_i.distributor_shipping_methods.first,
          distributor_ii.distributor_shipping_methods.first,
        ]
        order = build(:order, distributor: distributor_i, order_cycle:)

        available_shipping_methods = Orders::AvailableShippingMethodsService.new(order).to_a

        expect(available_shipping_methods).to eq [shipping_method_i]
      end
    end
  end

  context "when FilterShippingMethods tag rules are in effect" do
    let(:user) { create(:user) }
    let(:distributor) { create(:distributor_enterprise) }
    let(:other_distributor) { create(:distributor_enterprise) }
    let!(:distributor_shipping_method) { create(:shipping_method, distributors: [distributor]) }
    let!(:other_distributor_shipping_method) do
      create(:shipping_method, distributors: [other_distributor])
    end
    let(:customer) { create(:customer, user:, enterprise: distributor) }
    let!(:tag_rule) {
      create(:filter_shipping_methods_tag_rule,
             enterprise: distributor,
             preferred_customer_tags: "local",
             preferred_shipping_method_tags: "local-delivery")
    }
    let!(:default_tag_rule) {
      create(:filter_shipping_methods_tag_rule,
             enterprise: distributor,
             is_default: true,
             preferred_shipping_method_tags: "local-delivery")
    }
    let!(:tagged_sm) { distributor_shipping_method }
    let!(:untagged_sm) { other_distributor_shipping_method }

    before do
      tagged_sm.update_attribute(:tag_list, 'local-delivery')
      distributor.shipping_methods = [tagged_sm, untagged_sm]
    end

    context "with a preferred visiblity of 'visible', default visibility of 'hidden'" do
      before {
        tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, 'visible')
      }
      before {
        default_tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility,
                                          'hidden')
      }

      let(:order_cycle) { create(:sells_own_order_cycle) }
      let(:order) { build(:order, distributor:, order_cycle:) }

      context "when the customer is nil" do
        let(:available_shipping_methods) { Orders::AvailableShippingMethodsService.new(order).to_a }

        it "applies default action (hide)" do
          expect(available_shipping_methods).to include untagged_sm
          expect(available_shipping_methods).not_to include tagged_sm
        end
      end

      context "when a customer is present" do
        let(:available_shipping_methods) {
          Orders::AvailableShippingMethodsService.new(order, customer).to_a
        }

        context "and the customer's tags match" do
          before do
            customer.update_attribute(:tag_list, 'local')
          end

          it "applies the action (show)" do
            expect(available_shipping_methods).to include tagged_sm, untagged_sm
          end
        end

        context "and the customer's tags don't match" do
          before do
            customer.update_attribute(:tag_list, 'something')
          end

          it "applies the default action (hide)" do
            expect(available_shipping_methods).to include untagged_sm
            expect(available_shipping_methods).not_to include tagged_sm
          end
        end
      end
    end

    context "with a preferred visiblity of 'hidden', default visibility of 'visible'" do
      before {
        tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility, 'hidden')
      }
      before {
        default_tag_rule.update_attribute(:preferred_matched_shipping_methods_visibility,
                                          'visible')
      }

      let(:order_cycle) { create(:sells_own_order_cycle) }
      let(:order) { build(:order, distributor:, order_cycle:) }

      context "when the customer is nil" do
        let(:available_shipping_methods) { Orders::AvailableShippingMethodsService.new(order).to_a }

        it "applies default action (show)" do
          expect(available_shipping_methods).to include tagged_sm, untagged_sm
        end
      end

      context "when a customer is present" do
        let(:available_shipping_methods) {
          Orders::AvailableShippingMethodsService.new(order, customer).to_a
        }

        context "and the customer's tags match" do
          before do
            customer.update_attribute(:tag_list, 'local')
          end

          it "applies the action (hide)" do
            expect(available_shipping_methods).to include untagged_sm
            expect(available_shipping_methods).not_to include tagged_sm
          end
        end

        context "and the customer's tags don't match" do
          before do
            customer.update_attribute(:tag_list, 'something')
          end

          it "applies the default action (show)" do
            expect(available_shipping_methods).to include tagged_sm, untagged_sm
          end
        end
      end
    end
  end

  context "when two distributors implement the same shipping methods" do
    context "only one distributor supports the two shipping methods in the order cycle" do
      let(:oc){ create(:order_cycle) }
      let(:shipping_method){ create(:shipping_method) }
      let(:shipping_method2){ create(:shipping_method) }
      let(:d1){ oc.distributors.first }
      let(:d2){ oc.distributors.second }
      before {
        d1.shipping_methods << shipping_method
        d1.shipping_methods << shipping_method2
        d2.shipping_methods << shipping_method
        d2.shipping_methods << shipping_method2
        oc.selected_distributor_shipping_methods << d1.distributor_shipping_methods.first
        oc.selected_distributor_shipping_methods << d1.distributor_shipping_methods.second
        oc.selected_distributor_shipping_methods << d2.distributor_shipping_methods.first
      }
      it do
        order = build(:order, distributor: d2, order_cycle: oc)
        order_available_shipping_methods = Orders::AvailableShippingMethodsService.new(order).to_a
        expect(order_available_shipping_methods).to eq([d2.shipping_methods.first])
      end
    end
  end

  context "when certain shipping categories are required", feature: :match_shipping_categories do
    subject { Orders::AvailableShippingMethodsService.new(order) }
    let(:order) { build(:order, distributor:, order_cycle: oc) }
    let(:oc) { create(:order_cycle) }
    let(:distributor) { oc.distributors.first }
    let(:standard_shipping) {
      create(:shipping_method, distributors: [distributor], shipping_categories: [bike_transport])
    }
    let(:cooled_shipping) {
      create(:shipping_method, distributors: [distributor], shipping_categories: [refrigerated])
    }
    let(:bike_transport) { Spree::ShippingCategory.new(name: "bike") }
    let(:refrigerated) { Spree::ShippingCategory.new(name: "fridge") }

    before {
      standard_shipping
      cooled_shipping
    }

    it "provides all shipping methods for an empty order" do
      expect(subject.to_a).to match_array [standard_shipping, cooled_shipping]
    end

    it "provides all shipping methods for normal products" do
      order.line_items << build(:line_item)
      expect(subject.to_a).to match_array [standard_shipping, cooled_shipping]
    end

    it "filters shipping methods for products needing refrigeration" do
      product = oc.products.first
      product.variants.first.update!(shipping_category: refrigerated)
      order.line_items << build(:line_item, variant: product.variants.first)
      expect(subject.to_a).to eq [cooled_shipping]
    end
  end
end
