# frozen_string_literal: true

require 'spec_helper'

RSpec.describe EnterpriseRelationship do
  describe "scopes" do
    let(:e1)  { create(:enterprise, name: 'A') }
    let(:e2)  { create(:enterprise, name: 'B') }
    let(:e3)  { create(:enterprise, name: 'C') }

    it "sorts by child, parent enterprise name" do
      er1 = create(:enterprise_relationship, parent: e3, child: e1)
      er2 = create(:enterprise_relationship, parent: e1, child: e2)
      er3 = create(:enterprise_relationship, parent: e2, child: e1)

      expect(EnterpriseRelationship.by_name).to eq([er3, er1, er2])
    end

    describe "finding relationships involving some enterprises" do
      let!(:er) { create(:enterprise_relationship, parent: e1, child: e2) }

      it "returns relationships where an enterprise is the parent" do
        expect(EnterpriseRelationship.involving_enterprises([e1])).to eq([er])
      end

      it "returns relationships where an enterprise is the child" do
        expect(EnterpriseRelationship.involving_enterprises([e2])).to eq([er])
      end

      it "does not return other relationships" do
        expect(EnterpriseRelationship.involving_enterprises([e3])).to eq([])
      end
    end

    describe "creating with a permission list" do
      context "creating a new list of permissions" do
        it "creates a new permission for each item in the list" do
          er = EnterpriseRelationship.create! parent: e1, child: e2,
                                              permissions_list: ['one', 'two']
          er.reload
          expect(er.permissions.map(&:name)).to match_array ['one', 'two']
        end

        it "does nothing when the list is nil" do
          er = EnterpriseRelationship.create! parent: e1, child: e2, permissions_list: nil
          er.reload
          expect(er.permissions).to be_empty
        end
      end

      context "updating an existing list of permissions" do
        let(:er) {
          create(:enterprise_relationship, parent: e1, child: e2,
                                           permissions_list: ["one", "two", "three"])
        }
        it "creates a new permission for each item in the list that has no existing permission" do
          er.permissions_list = ['four']
          er.save!
          er.reload
          expect(er.permissions.map(&:name)).to include 'four'
        end

        it "does not duplicate existing permissions" do
          er.permissions_list = ["one", "two", "three"]
          er.save!
          er.reload
          expect(er.permissions.map(&:name).count).to eq(3)
          expect(er.permissions.map(&:name)).to match_array ["one", "two", "three"]
        end

        it "removes permissions that are not in the list" do
          er.permissions_list = ['one', 'three']
          er.save!
          er.reload
          expect(er.permissions.map(&:name)).to include 'one', 'three'
          expect(er.permissions.map(&:name)).not_to include 'two'
        end

        it "does removes all permissions when the list provided is nil" do
          er.permissions_list = nil
          er.save!
          er.reload
          expect(er.permissions).to be_empty
        end
      end
    end

    describe "finding by permission" do
      let!(:er1) { create(:enterprise_relationship, parent: e2, child: e1) }
      let!(:er2) { create(:enterprise_relationship, parent: e3, child: e2) }
      let!(:er3) { create(:enterprise_relationship, parent: e1, child: e3) }

      it "finds relationships that grant permissions to some enterprises" do
        expect(EnterpriseRelationship.permitting([e1, e2])).to match_array [er1, er2]
      end

      it "finds relationships that are granted by particular enterprises" do
        expect(EnterpriseRelationship.permitted_by([e1, e2])).to match_array [er1, er3]
      end
    end

    it "finds relationships that grant a particular permission" do
      er1 = create(:enterprise_relationship, parent: e1, child: e2,
                                             permissions_list: ['one', 'two'])
      er2 = create(:enterprise_relationship, parent: e2, child: e3,
                                             permissions_list: ['two', 'three'])
      er3 = create(:enterprise_relationship, parent: e3, child: e1,
                                             permissions_list: ['three', 'four'])

      expect(EnterpriseRelationship.with_permission('two')).to match_array [er1, er2]
    end
  end

  describe "finding relatives" do
    let(:e1) { create(:supplier_enterprise) }
    let(:e2) { create(:distributor_enterprise) }
    let!(:er) { create(:enterprise_relationship, parent: e1, child: e2) }
    let(:er_reverse) { create(:enterprise_relationship, parent: e2, child: e1) }

    it "includes self where appropriate" do
      expect(EnterpriseRelationship.relatives[e2.id][:distributors]).to include e2.id
      expect(EnterpriseRelationship.relatives[e2.id][:producers]).not_to include e2.id
    end

    it "categorises enterprises into distributors and producers" do
      e2.update_attribute :is_primary_producer, true
      expect(EnterpriseRelationship.relatives).to eq(
        e1.id => { distributors: Set.new([e2.id]), producers: Set.new([e1.id, e2.id]) },
        e2.id => { distributors: Set.new([e2.id]), producers: Set.new([e2.id, e1.id]) }
      )
    end

    it "finds inactive enterprises by default" do
      e1.update_attribute :sells, 'unspecified'
      expect(EnterpriseRelationship.relatives[e2.id][:producers]).to eq(Set.new([e1.id]))
    end

    it "does not find inactive enterprises when requested" do
      e1.update_attribute :sells, 'unspecified'
      expect(EnterpriseRelationship.relatives(true)[e2.id][:producers]).to be_empty
    end

    it "does not show duplicates" do
      er_reverse
      expect(EnterpriseRelationship.relatives[e2.id][:producers]).to eq(Set.new([e1.id]))
    end
  end

  describe "callbacks" do
    describe "updating variant override permissions" do
      let(:hub) { create(:distributor_enterprise) }
      let(:producer) { create(:supplier_enterprise) }
      let(:some_other_producer) { create(:supplier_enterprise) }

      context "when variant_override permission is present" do
        let!(:er) {
          create(:enterprise_relationship, child: hub, parent: producer,
                                           permissions_list: [:add_to_order_cycles,
                                                              :create_variant_overrides] )
        }
        let!(:some_other_er) {
          create(:enterprise_relationship, child: hub, parent: some_other_producer,
                                           permissions_list: [:add_to_order_cycles,
                                                              :create_variant_overrides] )
        }
        let!(:vo1) {
          create(
            :variant_override,
            hub:,
            variant: create(:variant, product: create(:product), supplier: producer)
          )
        }
        let!(:vo2) {
          create(
            :variant_override,
            hub:,
            variant: create(:variant, product: create(:product), supplier: producer)
          )
        }
        let!(:vo3) {
          create(
            :variant_override,
            hub:,
            variant: create(:variant, product: create(:product), supplier: some_other_producer)
          )
        }

        context "revoking variant override permissions" do
          context "when the enterprise relationship is destroyed" do
            before { er.destroy }

            it "should set permission_revoked_at to the current time " \
               "for all variant overrides of the relationship" do
              expect(vo1.reload.permission_revoked_at).not_to be_nil
              expect(vo2.reload.permission_revoked_at).not_to be_nil
              expect(vo2.reload.permission_revoked_at).not_to be_nil
            end
          end
        end

        context "and is then removed" do
          before { er.permissions_list = [:add_to_order_cycles]; er.save! }

          it "should set permission_revoked_at to the current time " \
             "for all relevant variant overrides" do
            expect(vo1.reload.permission_revoked_at).not_to be_nil
            expect(vo2.reload.permission_revoked_at).not_to be_nil
          end

          it "should not affect other variant overrides" do
            expect(vo3.reload.permission_revoked_at).to be_nil
          end
        end

        context "and then some other permission is removed" do
          before { er.permissions_list = [:create_variant_overrides]; er.save! }

          it "should have no effect on existing variant_overrides" do
            expect(vo1.reload.permission_revoked_at).to be_nil
            expect(vo2.reload.permission_revoked_at).to be_nil
            expect(vo3.reload.permission_revoked_at).to be_nil
          end
        end
      end

      context "when variant_override permission is not present" do
        let!(:er) {
          create(:enterprise_relationship, child: hub, parent: producer,
                                           permissions_list: [:add_to_order_cycles] )
        }
        let!(:some_other_er) {
          create(:enterprise_relationship, child: hub, parent: some_other_producer,
                                           permissions_list: [:add_to_order_cycles] )
        }
        let!(:vo1) {
          create(
            :variant_override,
            hub:,
            variant: create(:variant, product: create(:product), supplier: producer),
            permission_revoked_at: Time.now.in_time_zone
          )
        }
        let!(:vo2) {
          create(
            :variant_override,
            hub:,
            variant: create(:variant, product: create(:product), supplier: producer),
            permission_revoked_at: Time.now.in_time_zone
          )
        }
        let!(:vo3) {
          create(
            :variant_override,
            hub:,
            variant: create(:variant, product: create(:product), supplier: some_other_producer),
            permission_revoked_at: Time.now.in_time_zone
          )
        }

        context "and is then added" do
          before {
            er.permissions_list = [:add_to_order_cycles, :create_variant_overrides]; er.save!
          }
          it "should set permission_revoked_at to nil for all relevant variant overrides" do
            expect(vo1.reload.permission_revoked_at).to be_nil
            expect(vo2.reload.permission_revoked_at).to be_nil
          end

          it "should not affect other variant overrides" do
            expect(vo3.reload.permission_revoked_at).not_to be_nil
          end
        end

        context "and then some other permission is added" do
          before { er.permissions_list = [:add_to_order_cycles, :manage_products]; er.save! }

          it "should have no effect on existing variant_overrides" do
            expect(vo1.reload.permission_revoked_at).not_to be_nil
            expect(vo2.reload.permission_revoked_at).not_to be_nil
            expect(vo3.reload.permission_revoked_at).not_to be_nil
          end
        end
      end
    end

    describe "updating order cycles" do
      let(:hub) { create(:distributor_enterprise) }
      let(:producer) { create(:supplier_enterprise) }
      let(:order_cycle) { create(:simple_order_cycle) }
      let(:some_other_producer) { create(:supplier_enterprise) }

      context "when order_cycle permission is present" do
        let!(:er) {
          create(:enterprise_relationship, child: hub, parent: producer,
                                           permissions_list: [
                                             :add_to_order_cycle,
                                             :create_variant_overrides
                                           ] )
        }
        let!(:incoming_external_exchange) {
          order_cycle.exchanges.create! sender: producer, receiver: hub, incoming: true
        }
        let!(:other_external_exchange) {
          order_cycle.exchanges.create! sender: some_other_producer, receiver: hub, incoming: true
        }
        let!(:incoming_internal_exchange) {
          order_cycle.exchanges.create! sender: hub, receiver: hub, incoming: true
        }
        let!(:outgoing_internal_exchange) {
          order_cycle.exchanges.create! sender: hub, receiver: hub, incoming: false
        }
        let!(:variant) { create(:variant) }
        let!(:some_other_variant) { create(:variant) }
        let!(:incoming_external_variant) {
          incoming_external_exchange.exchange_variants.create!(
            exchange: incoming_external_exchange, variant:
          )
        }
        let!(:incoming_internal_only_variant) {
          incoming_internal_exchange.exchange_variants.create!(
            exchange: incoming_internal_exchange, variant: some_other_variant
          )
        }
        let!(:outgoing_internal_variant) {
          outgoing_internal_exchange.exchange_variants.create!(
            exchange: outgoing_internal_exchange, variant:
          )
        }
        let!(:outgoing_internal_only_variant) {
          outgoing_internal_exchange.exchange_variants.create!(
            exchange: outgoing_internal_exchange, variant: some_other_variant
          )
        }

        # We need to destroy the exchange variants on all order cycles related to the ER if
        # 'add_to_order_cycle' permission is removed. If they are left on the order cycle, the
        # Taxons of the variants will still appear on the /shops page, despite the hub not
        # actually offering the variants anymore.
        context "removing exchanges and exchange variants" do
          context "when the enterprise relationship is destroyed" do
            before { er.destroy }
            it "should destroy all exchanges and exchange variants related to ER" do
              expect(Exchange.exists?(incoming_external_exchange.id)).to be false
              expect(Exchange.exists?(other_external_exchange.id)).to be true
              expect(ExchangeVariant.exists?(incoming_external_variant.id)).to be false
              expect(ExchangeVariant.exists?(outgoing_internal_variant.id)).to be false
              expect(ExchangeVariant.exists?(incoming_internal_only_variant.id)).to be true
              expect(ExchangeVariant.exists?(outgoing_internal_only_variant.id)).to be true
            end
          end
        end

        context "and is then removed" do
          before { er.permissions_list = [:create_variant_overrides]; er.save! }
          it "should destroy all exchanges and exchange variants related to ER" do
            expect(Exchange.exists?(incoming_external_exchange.id)).to be false
            expect(Exchange.exists?(other_external_exchange.id)).to be true
            expect(ExchangeVariant.exists?(incoming_external_variant.id)).to be false
            expect(ExchangeVariant.exists?(outgoing_internal_variant.id)).to be false
            expect(ExchangeVariant.exists?(incoming_internal_only_variant.id)).to be true
            expect(ExchangeVariant.exists?(outgoing_internal_only_variant.id)).to be true
          end

          it "should not affect other exchanges or order cycles" do
            expect(Exchange.exists?(outgoing_internal_exchange.id)).to be true
          end
        end

        context "and then some other permission is removed" do
          before { er.permissions_list = [:add_to_order_cycle]; er.save! }
          it "should have no effect on existing exchanges" do
            expect(Exchange.exists?(incoming_external_exchange.id)).to be true
            expect(Exchange.exists?(other_external_exchange.id)).to be true
            expect(ExchangeVariant.exists?(incoming_external_variant.id)).to be true
            expect(ExchangeVariant.exists?(outgoing_internal_variant.id)).to be true
            expect(ExchangeVariant.exists?(incoming_internal_only_variant.id)).to be true
            expect(ExchangeVariant.exists?(outgoing_internal_only_variant.id)).to be true
          end
        end
      end
    end
  end
end
