# frozen_string_literal: true

#  Copyright (c) 2023, Schweizer Wanderwege. This file is part of
#  hitobito and licensed under the Affero General Public License version 3
#  or later. See the COPYING file at the top-level directory or at
#  https://github.com/hitobito/hitobito.

require "spec_helper"

describe PersonDetailsReadables do
  [:index, :layer_search, :deep_search, :global].each do |action|
    context action do
      let(:action) { action }
      let(:user) { role.person.reload }
      let(:ability) { PersonDetailsReadables.new(user, (action == :index) ? group : nil) }

      let(:all_accessibles) do
        people = Person.accessible_by(ability)
        case action
        when :index then people
        when :layer_search then people.in_layer(group.layer_group)
        when :deep_search then people.in_or_below(group.layer_group)
        when :global then people
        end
      end

      subject { all_accessibles }

      context :layer_and_below_full do
        let(:role) { Fabricate(Group::TopGroup::Leader.name.to_sym, group: groups(:top_group)) }

        context "own group" do
          let(:group) { role.group }

          it "may read himself" do
            is_expected.to include(role.person)
          end

          it "may read people in his group" do
            other = Fabricate(Group::TopGroup::Leader.name.to_sym, group: groups(:top_group))
            is_expected.to include(other.person)
          end

          it "may read external people in his group" do
            other = Fabricate(Role::External.name.to_sym, group: groups(:top_group))
            is_expected.to include(other.person)
          end
        end

        context "lower layer" do
          let(:group) { groups(:bottom_layer_one) }

          it "may read visible people" do
            other = Fabricate(Group::BottomLayer::Leader.name.to_sym, group: groups(:bottom_layer_one))
            is_expected.to include(other.person)
          end

          it "may not read external people" do
            other = Fabricate(Role::External.name.to_sym, group: groups(:bottom_layer_one))
            is_expected.not_to include(other.person)
          end
        end
      end

      context :layer_and_below_read do
        let(:role) { Fabricate(Group::TopGroup::Secretary.name.to_sym, group: groups(:top_group)) }

        context "own group" do
          let(:group) { role.group }

          it "may read himself" do
            is_expected.to include(role.person)
          end

          it "may read people in his group" do
            other = Fabricate(Group::TopGroup::Member.name.to_sym, group: groups(:top_group))
            is_expected.to include(other.person)
          end

          it "may read external people in his group" do
            other = Fabricate(Role::External.name.to_sym, group: groups(:top_group))
            is_expected.to include(other.person)
          end
        end

        context "group in same layer" do
          let(:group) { groups(:toppers) }

          it "may read people" do
            other = Fabricate(Group::GlobalGroup::Leader.name.to_sym, group: group)
            is_expected.to include(other.person)
          end

          it "may read external people" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.to include(other.person)
          end
        end

        context "lower layer" do
          let(:group) { groups(:bottom_layer_one) }

          it "may read visible people" do
            other = Fabricate(Group::BottomLayer::Leader.name.to_sym, group: group)
            is_expected.to include(other.person)
          end

          it "may not read external people" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end
        end

        context "bottom group" do
          let(:group) { groups(:bottom_group_one_one) }

          it "may not read non-visible" do
            other = Fabricate(Group::BottomGroup::Member.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end
        end
      end

      context :layer_full do
        let(:role) { Fabricate(Group::TopGroup::LocalGuide.name.to_sym, group: groups(:top_group)) }

        context "own group" do
          let(:group) { role.group }

          it "may read himself" do
            is_expected.to include(role.person)
          end

          it "may read people in his group" do
            other = Fabricate(Group::TopGroup::Leader.name.to_sym, group: groups(:top_group))
            is_expected.to include(other.person)
          end

          it "may read external people in his group" do
            other = Fabricate(Role::External.name.to_sym, group: groups(:top_group))
            is_expected.to include(other.person)
          end
        end

        context "lower layer" do
          let(:group) { groups(:bottom_layer_one) }

          it "may not read visible people" do
            other = Fabricate(Group::BottomLayer::Leader.name.to_sym, group: groups(:bottom_layer_one))
            is_expected.not_to include(other.person)
          end

          it "may not read external people" do
            other = Fabricate(Role::External.name.to_sym, group: groups(:bottom_layer_one))
            is_expected.not_to include(other.person)
          end
        end
      end

      context :layer_read do
        let(:role) { Fabricate(Group::TopGroup::LocalSecretary.name.to_sym, group: groups(:top_group)) }

        it "has layer_read permission" do
          expect(role.permissions).to include(:layer_read)
        end

        context "own group" do
          let(:group) { role.group }

          it "may read himself" do
            is_expected.to include(role.person)
          end

          it "may read people in his group" do
            other = Fabricate(Group::TopGroup::Member.name.to_sym, group: groups(:top_group))
            is_expected.to include(other.person)
          end

          it "may read external people in his group" do
            other = Fabricate(Role::External.name.to_sym, group: groups(:top_group))
            is_expected.to include(other.person)
          end
        end

        context "group in same layer" do
          let(:group) { groups(:toppers) }

          it "may read people" do
            other = Fabricate(Group::GlobalGroup::Leader.name.to_sym, group: group)
            is_expected.to include(other.person)
          end

          it "may read external people" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.to include(other.person)
          end
        end

        context "lower layer" do
          let(:group) { groups(:bottom_layer_one) }

          it "may not read visible people" do
            other = Fabricate(Group::BottomLayer::Leader.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end

          it "may not read external people" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end
        end

        context "bottom group" do
          let(:group) { groups(:bottom_group_one_one) }

          it "may not read non-visible" do
            other = Fabricate(Group::BottomGroup::Member.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end
        end
      end

      context :group_and_below_full do
        let(:role) { Fabricate(Group::TopLayer::TopAdmin.name.to_sym, group: groups(:top_layer)) }

        context "own group" do
          let(:group) { role.group }

          it "may read himself" do
            is_expected.to include(role.person)
          end

          it "may read people in his group" do
            other = Fabricate(Group::TopLayer::TopAdmin.name.to_sym, group: group)
            is_expected.to include(other.person)
          end

          it "may read external people in his group" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.to include(other.person)
          end
        end

        context "below group" do
          let(:group) { groups(:top_group) }

          it "may read people" do
            other = Fabricate(Group::TopGroup::Member.name.to_sym, group: group)
            is_expected.to include(other.person)
          end

          it "may read external people" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.to include(other.person)
          end
        end

        context "in below layer" do
          let(:group) { groups(:bottom_layer_one) }

          it "may not read people" do
            other = Fabricate(Group::BottomLayer::Leader.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end

          it "may not read external people" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end
        end
      end

      context :group_full do
        let(:role) { Fabricate(Group::BottomGroup::Leader.name.to_sym, group: groups(:bottom_group_one_one)) }

        context "own group" do
          let(:group) { role.group }

          it "may read himself" do
            is_expected.to include(role.person)
          end

          it "may read people in his group" do
            other = Fabricate(Group::BottomGroup::Member.name.to_sym, group: group)
            is_expected.to include(other.person)
          end

          it "may read external people in his group" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.to include(other.person)
          end
        end

        context "group in same layer" do
          let(:group) { groups(:bottom_group_one_two) }

          it "may not read people" do
            other = Fabricate(Group::BottomGroup::Leader.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end

          it "may not read external people" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end
        end
      end

      context :contact_data do
        let(:role) { Fabricate(Group::GlobalGroup::Leader.name.to_sym, group: groups(:toppers)) }

        context "group in same layer" do
          let(:group) { groups(:top_group) }

          it "may not read people with contact data" do
            other = Fabricate(Group::TopGroup::Leader.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end

          it "may not read external people" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end
        end

        context "lower layer" do
          let(:group) { groups(:bottom_layer_one) }

          it "may not read people with contact data" do
            other = Fabricate(Group::BottomLayer::Leader.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end

          it "may not get people without contact data" do
            other = Fabricate(Group::BottomLayer::Member.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end
        end
      end

      context :group_read do
        let(:role) { Fabricate(Group::GlobalGroup::Member.name.to_sym, group: groups(:toppers)) }

        it "has only login permission" do
          expect(role.permissions).to eq([:group_read])
        end

        context "own group" do
          let(:group) { role.group }

          it "may read himself" do
            is_expected.to include(role.person)
          end

          it "may read people in his group" do
            other = Fabricate(Group::GlobalGroup::Leader.name.to_sym, group: group)
            is_expected.to include(other.person)
          end

          it "may read external people in his group" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.to include(other.person)
          end
        end

        context "group in same layer" do
          let(:group) { groups(:top_group) }

          it "may not read people with contact data" do
            other = Fabricate(Group::TopGroup::Leader.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end

          it "may not read external people" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end
        end

        context "lower layer" do
          let(:group) { groups(:bottom_layer_one) }

          it "may not read people with contact data" do
            other = Fabricate(Group::BottomLayer::Leader.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end

          it "may not read external people" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end
        end
      end

      describe "no permissions" do
        let(:role) { Fabricate(Role::External.name.to_sym, group: groups(:top_group)) }

        it "has no permissions" do
          expect(role.permissions).to eq([])
        end

        context "own group" do
          let(:group) { role.group }

          if action == :index
            it "may not read himself" do
              is_expected.not_to include(role.person)
            end
          else
            it "may read himself" do
              is_expected.to include(role.person)
            end
          end

          it "may not read people in his group" do
            other = Fabricate(Group::TopGroup::Leader.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end

          it "may not read external people in his group" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.not_to include(other.person)
          end
        end
      end

      context :root do
        let(:user) { people(:root) }

        context "every group" do
          let(:group) { groups(:top_group) }

          it "may read all people" do
            other = Fabricate(Group::TopGroup::Member.name.to_sym, group: group)
            is_expected.to include(other.person)
          end

          it "may read external people" do
            other = Fabricate(Role::External.name.to_sym, group: group)
            is_expected.to include(other.person)
          end
        end

        if action == :global
          it "may read herself" do
            is_expected.to include(user)
          end

          it "may read people outside groups" do
            other = Fabricate(:person)
            is_expected.to include(other)
          end
        end
      end
    end
  end
end
