require 'rails_helper'
require 'models/application_model_examples'
require 'models/concerns/can_be_imported_examples'
require 'models/concerns/has_groups_examples'

RSpec.describe Role do
  it_behaves_like 'ApplicationModel'
  it_behaves_like 'CanBeImported'
  it_behaves_like 'HasGroups', group_access_factory: :role

  subject(:role) { create(:role) }

  describe 'Default state' do
    describe 'of whole table:' do
      it 'has three records ("Admin", "Agent", and "Customer")' do
        expect(described_class.pluck(:name)).to match_array(%w[Admin Agent Customer])
      end
    end

    describe 'of "Admin" role:' do
      it 'has default admin permissions' do
        expect(described_class.find_by(name: 'Admin').permissions.pluck(:name))
          .to match_array(%w[admin user_preferences report knowledge_base.editor])
      end
    end

    describe 'of "Agent" role:' do
      it 'has default agent permissions' do
        expect(described_class.find_by(name: 'Agent').permissions.pluck(:name))
          .to match_array(%w[ticket.agent chat.agent cti.agent user_preferences knowledge_base.reader])
      end
    end

    describe 'of "Customer" role:' do
      it 'has default customer permissions' do
        expect(described_class.find_by(name: 'Customer').permissions.pluck(:name))
          .to match_array(
            %w[
              user_preferences.password
              user_preferences.language
              user_preferences.linked_accounts
              user_preferences.avatar
              ticket.customer
            ]
          )
      end
    end
  end

  describe 'Callbacks -' do
    describe 'Permission validation:' do
      context 'with normal permission' do
        let(:permission) { create(:permission) }

        it 'can be created' do
          expect { create(:role, permissions: [permission]) }
            .to change(described_class, :count).by(1)
        end

        it 'can be added' do
          expect { role.permissions << permission }
            .to change { role.permissions.count }.by(1)
        end
      end

      context 'with disabled permission' do
        let(:permission) { create(:permission, preferences: { disabled: true }) }

        it 'cannot be created' do
          expect { create(:role, permissions: [permission]) }
            .to raise_error(/is disabled/)
            .and change(described_class, :count).by(0)
        end

        it 'cannot be added' do
          expect { role.permissions << permission }
            .to raise_error(/is disabled/)
            .and change { role.permissions.count }.by(0)
        end
      end

      context 'with multiple, explicitly incompatible permissions' do
        let(:permission) { create(:permission, preferences: { not: [Permission.first.name] }) }

        it 'cannot be created' do
          expect { create(:role, permissions: [Permission.first, permission]) }
            .to raise_error(/conflicts with/)
            .and change(described_class, :count).by(0)
        end

        it 'cannot be added' do
          role.permissions << Permission.first

          expect { role.permissions << permission }
            .to raise_error(/conflicts with/)
            .and change { role.permissions.count }.by(0)
        end
      end

      context 'with multiple, compatible permissions' do
        let(:permission) { create(:permission, preferences: { not: [Permission.pluck(:name).max.next] }) }

        it 'can be created' do
          expect { create(:role, permissions: [Permission.first, permission]) }
            .to change(described_class, :count).by(1)
        end

        it 'can be added' do
          role.permissions << Permission.first

          expect { role.permissions << permission }
            .to change { role.permissions.count }.by(1)
        end
      end
    end

    describe 'System-wide agent limit checks:' do
      let(:agents) { User.with_permissions('ticket.agent') }

      describe '#validate_agent_limit_by_attributes' do
        context 'when reactivating a role adds new agents' do
          subject(:role) { create(:agent_role, active: false) }

          before { create(:user, roles: [role])  }

          context 'exceeding the system limit' do
            before { Setting.set('system_agent_limit', agents.count) }

            it 'fails and raises an error' do
              expect { role.update!(active: true) }
                .to raise_error(Exceptions::UnprocessableEntity)
                .and change(agents, :count).by(0)
            end
          end
        end
      end
    end

    describe 'Restrictions on #default_at_signup:' do
      context 'for roles with "admin" permissions' do
        subject(:role) { build(:role, permissions: Permission.where(name: 'admin')) }

        it 'cannot be set to true on creation' do
          role.default_at_signup = true

          expect { role.save }
            .to raise_error(Exceptions::UnprocessableEntity, /Cannot set default at signup/)
        end

        it 'cannot be changed to true' do
          role.save

          expect { role.update(default_at_signup: true) }
            .to raise_error(Exceptions::UnprocessableEntity, /Cannot set default at signup/)
        end
      end

      context 'for roles with permissions that are children of "admin"' do
        subject(:role) { build(:role, permissions: [permission]) }

        let(:permission) { create(:permission, name: 'admin.foo') }

        it 'cannot be set to true on creation' do
          role.default_at_signup = true

          expect { role.save }
            .to raise_error(Exceptions::UnprocessableEntity, /Cannot set default at signup/)
        end

        it 'cannot be changed to true' do
          role.save

          expect { role.update(default_at_signup: true) }
            .to raise_error(Exceptions::UnprocessableEntity, /Cannot set default at signup/)
        end
      end

      context 'for roles with "ticket.agent" permissions' do
        subject(:role) { build(:role, permissions: Permission.where(name: 'ticket.agent')) }

        it 'cannot be set to true on creation' do
          role.default_at_signup = true

          expect { role.save }
            .to raise_error(Exceptions::UnprocessableEntity, /Cannot set default at signup/)
        end

        it 'cannot be changed to true' do
          role.save

          expect { role.update(default_at_signup: true) }
            .to raise_error(Exceptions::UnprocessableEntity, /Cannot set default at signup/)
        end
      end
    end
  end

  describe '.with_permissions' do
    context 'when given a name not matching any permissions' do
      let(:permission) { 'foo' }
      let(:result) { [] }

      it 'returns an empty array' do
        expect(described_class.with_permissions(permission)).to match_array(result)
      end
    end

    context 'when given the name of a top-level permission' do
      let(:permission) { 'user_preferences' }
      let(:result) { described_class.where(name: %w[Admin Agent]) }

      it 'returns an array of roles with that permission' do
        expect(described_class.with_permissions(permission)).to match_array(result)
      end
    end

    context 'when given the name of a child permission' do
      let(:permission) { 'user_preferences.language' }
      let(:result) { described_class.all }

      it 'returns an array of roles with either that permission or an ancestor' do
        expect(described_class.with_permissions(permission)).to match_array(result)
      end
    end

    context 'when given the names of multiple permissions' do
      let(:permissions) { %w[ticket.agent ticket.customer] }
      let(:result) { described_class.where(name: %w[Agent Customer]) }

      it 'returns an array of roles matching ANY given permission' do
        expect(described_class.with_permissions(permissions)).to match_array(result)
      end
    end
  end

  describe '#with_permission?' do
    subject(:role) { described_class.find_by(name: 'Admin') }

    context 'when given the name of a permission it has' do
      it 'returns true' do
        expect(role.with_permission?('admin')).to be(true)
      end
    end

    context 'when given the name of a permission it does NOT have' do
      it 'returns false' do
        expect(role.with_permission?('ticket.customer')).to be(false)
      end
    end

    context 'when given the name of multiple permissions' do
      it 'returns true as long as ANY match' do
        expect(role.with_permission?(['admin', 'ticket.customer'])).to be(true)
      end
    end
  end
end
