SUPPORTED_MODEL_PROPERTIES = [
  :not_nil,
  :maybe_nil,
  :raise_error_on_nil,
  :non_empty_string,
  :maybe_empty_string,
  :non_empty_array,
  :maybe_empty_array,
  :only_word_symbols,
  :run_list,
  :field_validator
]

RSpec.shared_examples 'field type validation' do |field, *properties|
  validated_model_name = described_class.name.demodulize.underscore
  unsupported_properties = properties - SUPPORTED_MODEL_PROPERTIES
  with_field_validators = properties.include?(:field_validator)

  raise_error "Unsupported properties: #{unsupported_properties.join(', ')}" unless unsupported_properties.empty?

  describe field do
    it 'should not be nil' do
      model = build(validated_model_name)
      model.send("#{field}=", nil)
      expect(model).not_to be_valid
    end if properties.include?(:not_nil)

    it 'may be nil' do
      expect(build(validated_model_name, field => nil)).to be_valid
    end if properties.include?(:maybe_nil)

    it 'should be non empty string' do
      expect(build(validated_model_name, field => '')).not_to be_valid
    end if properties.include?(:non_empty_string)

    it 'may be empty string' do
      expect(build(validated_model_name, field => '')).to be_valid
    end if properties.include?(:maybe_empty_string)

    it 'should be non empty array' do
      expect(build(validated_model_name, field => [])).not_to be_valid
    end if properties.include?(:non_empty_array)

    it 'may be empty array' do
      expect(build(validated_model_name, field => [])).to be_valid
    end if properties.include?(:maybe_empty_array)

    it 'should contain only word symbols' do
      expect(build(validated_model_name, field => '!')).not_to be_valid
      expect(build(validated_model_name, field => '/')).not_to be_valid
    end if properties.include?(:only_word_symbols)

    it 'should contain elements like role[asd] or recipe[asd]' do
      expect(build(validated_model_name, field => ['role[asd]'])).to be_valid
      expect(build(validated_model_name, field => ['recipe[asd]'])).to be_valid
      expect(build(validated_model_name, field => ['wrong_format'])).not_to be_valid
    end if properties.include?(:run_list)
  end

  if with_field_validators
    field_validation_method = "validate_#{field}!"

    describe field_validation_method do
      it 'should not be nil' do
        expect{
          build(validated_model_name, field => nil).send(field_validation_method)
        }.to raise_error InvalidRecord
      end if properties.include?(:not_nil)

      it 'may be nil' do
        expect{
          build(validated_model_name, field => nil).send(field_validation_method)
        }.not_to raise_error
      end if properties.include?(:maybe_nil)

      it 'should be non empty string' do
        expect{
          build(validated_model_name, field => '').send(field_validation_method)
        }.to raise_error InvalidRecord
      end if properties.include?(:non_empty_string)

      it 'may be empty string' do
        expect{
          build(validated_model_name, field => 0).send(field_validation_method)
        }.to raise_error InvalidRecord
        expect{
          build(validated_model_name, field => '').send(field_validation_method)
        }.not_to raise_error
      end if properties.include?(:maybe_empty_string)

      it 'should be non empty array' do
        expect{
          build(validated_model_name, field => 0).send(field_validation_method)
        }.to raise_error InvalidRecord
        expect{
          build(validated_model_name, field => []).send(field_validation_method)
        }.to raise_error InvalidRecord
      end if properties.include?(:non_empty_array)

      it 'may be empty array' do
        expect{
          build(validated_model_name, field => 0).send(field_validation_method)
        }.to raise_error InvalidRecord
        expect{
          build(validated_model_name, field => []).send(field_validation_method)
        }.not_to raise_error
      end if properties.include?(:maybe_empty_array)

      it 'should contain only symbols in [a-zA-Z0-9_-]' do
        expect{
          build(validated_model_name, field => '!').send(field_validation_method)
        }.to raise_error InvalidRecord
        expect{
          build(validated_model_name, field => '/').send(field_validation_method)
        }.to raise_error InvalidRecord
      end if properties.include?(:only_word_symbols)

      it 'should contain elements like role[asd] or recipe[asd]' do
        expect{
          build(validated_model_name, field => ['role[asd]']).send(field_validation_method)
        }.not_to raise_error
        expect{
          build(validated_model_name, field => ['recipe[asd]']).send(field_validation_method)
        }.not_to raise_error
        expect{
          build(validated_model_name, field => ['wrong_format']).send(field_validation_method)
        }.to raise_error InvalidRecord
      end if properties.include?(:run_list)
    end
  end
end