<?php

namespace NoahVet\Reef\Test\A_Unit\Domain\Validation;

use NoahVet\Reef\Domain\Validation\DateValidator;
use NoahVet\Reef\Domain\Validation\Enum\SupportedFormatEnum;
use NoahVet\Reef\Domain\Validation\Enum\SupportedTypeEnum;
use NoahVet\Reef\Domain\Validation\OpenApiValidator;
use NoahVet\Reef\Domain\Validation\OpenApiValidatorInterface;
use PHPUnit\Framework\TestCase;

final class OpenApiValidatorTest extends TestCase
{
    private OpenApiValidatorInterface $validator;

    protected function setUp(): void
    {
        $this->validator = new OpenApiValidator(
            new DateValidator(),
        );
    }

    /**
     * When all fields are correctly filled in, it must not return an error.
     * If the field is missing, then an error must be returned.
     */
    public function testAllNestedRequiredStringKeyShouldBeFilled(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'badParent',
                    'value' => null,
                    'message' => 'type.object',
                ],
                [
                    'key' => 'badParent2.child',
                    'value' => null,
                    'message' => 'type.string.not_nullable',
                ],
            ],
            $this->validator->validate(
                [
                    'parent' => [
                        'child' => 'loulou',
                    ],
                    'badParent' => null,
                    'badParent2' => [
                        'child' => null,
                    ],
                ],
                [
                    'parent' => [
                        'type' => 'object',
                        'properties' => [
                            'child' => [
                                'type' => 'string',
                                'nullable' => false,
                            ],
                        ],
                    ],
                    'badParent' => [
                        'type' => 'object',
                        'properties' => [
                            'child' => [
                                'type' => 'string',
                                'nullable' => false,
                            ],
                        ],
                    ],
                    'badParent2' => [
                        'type' => 'object',
                        'properties' => [
                            'child' => [
                                'type' => 'string',
                                'nullable' => false,
                            ],
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When all fields are correctly filled in, it must not return an error.
     * If the field is missing, then an error must be returned.
     */
    public function testAllRequiredStringKeyShouldBeFilled(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'missing',
                    'value' => null,
                    'message' => 'required',
                ],
            ],
            $this->validator->validate(
                [
                    'notMissing' => 'riri',
                ],
                [
                    'required' => [
                        'missing',
                    ],
                    'notMissing' => [
                        'type' => 'string',
                        'nullable' => false,
                    ],
                    'missing' => [
                        'type' => 'string',
                        'nullable' => false,
                    ],
                    'missingButNullable' => [
                        'type' => 'string',
                        'nullable' => true,
                    ],
                ],
            ),
        );
    }

    /**
     * When an enum field is correctly filled in, it must not return an error.
     * If the field is not in the enum list, then an error must be returned.
     */
    public function testArrayEnum(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'pokemonType2',
                    'value' => 'PIKACHU',
                    'message' => 'type.array',
                ],
                [
                    'key' => 'pokemonType4',
                    'value' => ['reptilo'],
                    'message' => 'enum.value',
                ],
                [
                    'key' => 'pokemonType5',
                    'value' => ['reptilo', 'bouleau'],
                    'message' => 'enum.value',
                ],
                [
                    'key' => 'pokemonType7',
                    'value' => [],
                    'message' => 'type.array.minItems',
                ],
            ],
            $this->validator->validate(
                [
                    'pokemonType' => ['PIKACHU'],
                    'pokemonType2' => 'PIKACHU',
                    'pokemonType3' => null,
                    'pokemonType4' => ['reptilo'],
                    'pokemonType5' => ['reptilo', 'bouleau'],
                    'pokemonType6' => [],
                    'pokemonType7' => [],
                    'pokemonType8' => [],
                    'pokemonType9' => [
                        'names' => [],
                    ],
                ],
                [
                    'pokemonType' => [
                        'type' => 'array',
                        'items' => [
                            'enum' => [
                                'PIKACHU',
                            ],
                            'type' => 'string',
                        ],
                    ],
                    'pokemonType2' => [
                        'type' => 'array',
                        'nullable' => false,
                        'items' => [
                            'enum' => [
                                'PIKACHU',
                            ],
                            'type' => 'string',
                        ],
                    ],
                    'pokemonType3' => [
                        'type' => 'array',
                        'nullable' => true,
                        'items' => [
                            'enum' => [
                                'PIKACHU',
                            ],
                            'type' => 'string',
                        ],
                    ],
                    'pokemonType4' => [
                        'type' => 'array',
                        'items' => [
                            'enum' => [
                                'PIKACHU',
                            ],
                            'type' => 'string',
                        ],
                    ],
                    'pokemonType5' => [
                        'type' => 'array',
                        'items' => [
                            'enum' => [
                                'PIKACHU',
                            ],
                            'type' => 'string',
                        ],
                    ],
                    'pokemonType6' => [
                        'type' => 'array',
                        'minItems' => 0,
                        'items' => [
                            'enum' => [
                                'PIKACHU',
                            ],
                            'type' => 'string',
                        ],
                    ],
                    'pokemonType7' => [
                        'type' => 'array',
                        'minItems' => 1,
                        'items' => [
                            'enum' => [
                                'PIKACHU',
                            ],
                            'type' => 'string',
                        ],
                    ],
                    'pokemonType8' => [
                        'type' => 'array',
                        'items' => [
                            'enum' => [
                                'PIKACHU',
                            ],
                            'type' => 'string',
                        ],
                    ],
                    'pokemonType9' => [
                        'properties' => [
                            'names' => [
                                'type' => 'array',
                                'items' => [
                                    'properties' => [
                                        'name' => [
                                            'type' => 'string',
                                        ],
                                    ],
                                    'type' => 'object',
                                ],
                            ],
                        ],
                        'type' => 'object',
                    ],
                ],
            ),
        );
    }

    /**
     * When an array must respect minItems and uniqueItems constraints, it returns the correct errors.
     */
    public function testArrayMinItemsAndUniqueItems(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'minItemsNotRespected',
                    'value' => ['one'],
                    'message' => 'type.array.minItems',
                ],
                [
                    'key' => 'uniqueItemsStrings',
                    'value' => ['fire', 'fire', 'water'],
                    'message' => 'type.array.uniqueItems',
                ],
                [
                    'key' => 'uniqueItemsObjects',
                    'value' => [
                        [
                            'id' => 1,
                        ],
                        [
                            'id' => 1,
                        ],
                    ],
                    'message' => 'type.array.uniqueItems',
                ],
            ],
            $this->validator->validate(
                [
                    'minItemsNotRespected' => ['one'],
                    'uniqueItemsStrings' => ['fire', 'fire', 'water'],
                    'uniqueItemsObjects' => [
                        [
                            'id' => 1,
                        ],
                        [
                            'id' => 1,
                        ],
                    ],
                    'validUniqueItems' => ['fire', 'water'],
                ],
                [
                    'minItemsNotRespected' => [
                        'type' => 'array',
                        'minItems' => 2,
                        'items' => [
                            'type' => 'string',
                        ],
                    ],
                    'uniqueItemsStrings' => [
                        'type' => 'array',
                        'uniqueItems' => true,
                        'items' => [
                            'type' => 'string',
                        ],
                    ],
                    'uniqueItemsObjects' => [
                        'type' => 'array',
                        'uniqueItems' => true,
                        'items' => [
                            'type' => 'object',
                            'properties' => [
                                'id' => [
                                    'type' => 'integer',
                                ],
                            ],
                        ],
                    ],
                    'validUniqueItems' => [
                        'type' => 'array',
                        'uniqueItems' => true,
                        'items' => [
                            'type' => 'string',
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When an array must respect maxItems constraints, it returns the correct errors.
     */
    public function testArrayMaxItems(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'maxItemsNotRespected',
                    'value' => ['one', 'two', 'three'],
                    'message' => 'type.array.maxItems',
                ],
            ],
            $this->validator->validate(
                [
                    'maxItemsNotRespected' => ['one', 'two', 'three'],
                    'validMaxItems' => ['one', 'two'],
                ],
                [
                    'maxItemsNotRespected' => [
                        'type' => 'array',
                        'maxItems' => 2,
                        'items' => [
                            'type' => 'string',
                        ],
                    ],
                    'validMaxItems' => [
                        'type' => 'array',
                        'maxItems' => 2,
                        'items' => [
                            'type' => 'string',
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When an array field is correctly filled in, it must not return an error.
     * If the field is of another type, then an error must be returned.
     */
    public function testArrayType(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                [
                    'idCollection' => [
                        0 => [
                            'id' => '660c1473-ad37-4aae-afd4-1ff486d7f6f2',
                        ],
                        1 => [
                            'id' => '660c1473-ad37-4aae-afd4-1ff486d7f6f3',
                        ],
                    ],
                ],
                [
                    'idCollection' => [
                        'type' => 'array',
                        'items' => [
                            'properties' => [
                                'id' => [
                                    'type' => 'string',
                                    'format' => 'uuid',
                                ],
                            ],
                            'type' => 'object',
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When an boolean field is correctly filled in, it must not return an error.
     * If the field is of another type, then an error must be returned.
     */
    public function testBooleanType(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'example1',
                    'value' => "I\'am a string.",
                    'message' => 'type.boolean',
                ],
                [
                    'key' => 'example2',
                    'value' => 0,
                    'message' => 'type.boolean',
                ],
            ],
            $this->validator->validate(
                $this->getSimpleScalarExample(),
                [
                    'example1' => [
                        'type' => 'boolean',
                    ],
                    'example2' => [
                        'type' => 'boolean',
                    ],
                    'example3' => [
                        'type' => 'boolean',
                    ],
                ],
            ),
        );
    }

    /**
     * A nullable datetime could be filled.
     */
    public function testDatetimeWithNullValue(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                [
                    'leavingDate' => null,
                ],
                [
                    'leavingDate' => [
                        'format' => 'date-time',
                        'nullable' => true,
                        'type' => 'string',
                    ],
                ],
            ),
        );
    }

    /**
     * A date with a datetime value is not correct.
     */
    public function testDateWithDatetimeValue(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'leavingDate',
                    'value' => '2022-03-28T00:00:00+00:00',
                    'message' => 'format.date',
                ],
            ],
            $this->validator->validate(
                [
                    'leavingDate' => '2022-03-28T00:00:00+00:00',
                ],
                [
                    'leavingDate' => [
                        'format' => 'date',
                        'type' => 'string',
                    ],
                ],
            ),
        );
    }

    /**
     * A date with a correct date value.
     */
    public function testDateWithGoodDateValue(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                [
                    'leavingDate' => '2022-03-28',
                ],
                [
                    'leavingDate' => [
                        'format' => 'date',
                        'type' => 'string',
                    ],
                ],
            ),
        );
    }

    /**
     * A nullable date could be filled.
     */
    public function testDateWithNullValue(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                [
                    'leavingDate' => null,
                ],
                [
                    'leavingDate' => [
                        'format' => 'date',
                        'nullable' => true,
                        'type' => 'string',
                    ],
                ],
            ),
        );
    }

    /**
     * A date or datetime formatted field with an empty string must return a format error.
     */
    public function testDateAndDatetimeWithEmptyStringValue(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'emptyDate',
                    'value' => '',
                    'message' => 'format.date',
                ],
                [
                    'key' => 'emptyDateTime',
                    'value' => '',
                    'message' => 'format.date-time',
                ],
            ],
            $this->validator->validate(
                [
                    'emptyDate' => '',
                    'emptyDateTime' => '',
                ],
                [
                    'emptyDate' => [
                        'format' => 'date',
                        'type' => 'string',
                    ],
                    'emptyDateTime' => [
                        'format' => 'date-time',
                        'type' => 'string',
                    ],
                ],
            ),
        );
    }

    /**
     * The behavior of a nested object must be the same as an object.
     *
     * @see OpenApiValidatorTest::testObjectType()
     */
    public function testDeepNestedObjectType(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                [
                    'dependency' => [
                        'nestedDependency' => [
                            'deepNestedDependency' => [
                                'id' => '660c1473-ad37-4aae-afd4-1ff486d7f6f2',
                            ],
                        ],
                    ],
                ],
                [
                    'dependency' => [
                        'type' => 'object',
                        'properties' => [
                            'nestedDependency' => [
                                'type' => 'object',
                                'properties' => [
                                    'deepNestedDependency' => [
                                        'type' => 'object',
                                        'properties' => [
                                            'id' => [
                                                'type' => 'string',
                                                'format' => 'uuid',
                                            ],
                                        ],
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When an email string field is correctly filled in, it must not return an error.
     * If the field is of another type, then an error must be returned.
     */
    public function testEmailFormat(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'badEmail',
                    'value' => null,
                    'message' => 'type.string.not_nullable',
                ],
                [
                    'key' => 'badEmail2',
                    'value' => 'fifi',
                    'message' => 'format.email',
                ],
                [
                    'key' => 'badEmail3',
                    'value' => '@lol.fr',
                    'message' => 'format.email',
                ],
                [
                    'key' => 'badEmail4',
                    'value' => '4.fr',
                    'message' => 'format.email',
                ],
            ],
            $this->validator->validate(
                [
                    'goodEmail' => 'riri@gmail.com',
                    'badEmail' => null,
                    'badEmail2' => 'fifi',
                    'badEmail3' => '@lol.fr',
                    'badEmail4' => '4.fr',
                ],
                [
                    'goodEmail' => [
                        'type' => 'string',
                        'format' => 'email',
                    ],
                    'badEmail' => [
                        'type' => 'string',
                        'format' => 'email',
                    ],
                    'badEmail2' => [
                        'type' => 'string',
                        'format' => 'email',
                    ],
                    'badEmail3' => [
                        'type' => 'string',
                        'format' => 'email',
                    ],
                    'badEmail4' => [
                        'type' => 'string',
                        'format' => 'email',
                    ],
                ],
            ),
        );
    }

    /**
     * If there is no data to submit, the validator must not return an error.
     */
    public function testEmpty(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                [],
                [],
            ),
        );
    }

    /**
     * When an enum field is correctly filled in, it must not return an error.
     * If the field is not in the enum list, then an error must be returned.
     */
    public function testEnum(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'pokemonType4',
                    'value' => 'riri',
                    'message' => 'enum.value',
                ],
            ],
            $this->validator->validate(
                [
                    'pokemonType' => 'PIKACHU',
                    'pokemonType2' => 'PIKACHU',
                    'pokemonType3' => null,
                    'pokemonType4' => 'riri',
                ],
                [
                    'pokemonType' => [
                        'enum' => [
                            'PIKACHU',
                        ],
                        'type' => 'string',
                    ],
                    'pokemonType2' => [
                        'enum' => [
                            'PIKACHU',
                        ],
                        'nullable' => false,
                        'type' => 'string',
                    ],
                    'pokemonType3' => [
                        'enum' => [
                            'PIKACHU',
                        ],
                        'nullable' => true,
                        'type' => 'string',
                    ],
                    'pokemonType4' => [
                        'enum' => [
                            'PIKACHU',
                        ],
                        'type' => 'string',
                    ],
                ],
            ),
        );
    }

    /**
     * When an int array field is correctly filled in, it must not return an error.
     * If the field is of another type, then an error must be returned.
     */
    public function testIntArrayType(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                [
                    'vat' => [
                        5,
                        11,
                        20,
                    ],
                ],
                [
                    'vat' => [
                        'type' => 'array',
                        'items' => [
                            'type' => 'integer',
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When an integer field is correctly filled in, it must not return an error.
     * If the field is of another type, then an error must be returned.
     */
    public function testIntegerType(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'example1',
                    'value' => "I\'am a string.",
                    'message' => 'type.integer',
                ],
                [
                    'key' => 'example3',
                    'value' => true,
                    'message' => 'type.integer',
                ],
            ],
            $this->validator->validate(
                $this->getSimpleScalarExample(),
                [
                    'example1' => [
                        'type' => 'integer',
                    ],
                    'example2' => [
                        'type' => 'integer',
                    ],
                    'example3' => [
                        'type' => 'integer',
                    ],
                ],
            ),
        );
    }

    /**
     * When a field matches exactly one schema, it must not return an error, otherwise one must be returned.
     */
    public function testOneOf(): void
    {
        $config = [
            'priceWithoutVat' => [
                'description' => 'Price excluding VAT, e.g. 7990000 = 7.99€',
                'minimum' => 0,
                'oneOf' => [
                    [
                        'type' => 'integer',
                        'enum' => [0],
                    ],
                    [
                        'type' => 'integer',
                        'minimum' => 1000000,
                    ],
                ],
            ],
        ];

        $this->assertSame(
            [],
            $this->validator->validate(
                [
                    'priceWithoutVat' => 0,
                ],
                $config,
            ),
        );

        $this->assertSame(
            [],
            $this->validator->validate(
                [
                    'priceWithoutVat' => 7990000,
                ],
                $config,
            ),
        );

        $this->assertSame(
            [
                [
                    'key' => 'priceWithoutVat',
                    'value' => 900000,
                    'message' => 'one_of',
                ],
            ],
            $this->validator->validate(
                [
                    'priceWithoutVat' => 900000,
                ],
                $config,
            ),
        );
    }

    /**
     * When integer field is correctly filled with maximum > value < minimum  value, it must not return an error.
     * Else an error must be returned.
     */
    public function testMinimumMaximumFormat(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'badPositiveValue',
                    'value' => 2,
                    'message' => 'integer.minimum',
                    'template' => 'The value of `badPositiveValue` key must be greater than 3.',
                ],
                [
                    'key' => 'badPositiveValue2',
                    'value' => 6,
                    'message' => 'integer.maximum',
                    'template' => 'The value of `badPositiveValue2` key must be lower than 5.',
                ],
                [
                    'key' => 'badMinimumNegativeValue',
                    'value' => -20,
                    'message' => 'integer.minimum',
                    'template' => 'The value of `badMinimumNegativeValue` key must be greater than -10.',
                ],
                [
                    'key' => 'badMaximumNegativeValue',
                    'value' => -5,
                    'message' => 'integer.maximum',
                    'template' => 'The value of `badMaximumNegativeValue` key must be lower than -10.',
                ],
                [
                    'key' => 'badMinimumNegativeNumberValue',
                    'value' => -16.666,
                    'message' => 'number.minimum',
                    'template' => 'The value of `badMinimumNegativeNumberValue` key must be greater than -15.',
                ],
                [
                    'key' => 'badMaximumNegativeNumberValue',
                    'value' => -9.999,
                    'message' => 'number.maximum',
                    'template' => 'The value of `badMaximumNegativeNumberValue` key must be lower than -10.',
                ],
            ],
            $this->validator->validate(
                [
                    'positiveValue' => 4,
                    'badPositiveValue' => 2,
                    'badPositiveValue2' => 6,
                    'minimumNegativeValue' => -2,
                    'badMinimumNegativeValue' => -20,
                    'maximumNegativeValue' => -15,
                    'badMaximumNegativeValue' => -5,
                    'minimumNegativeNumberValue' => -15.678,
                    'maximumNegativeNumberValue' => -15.677,
                    'badMinimumNegativeNumberValue' => -16.666,
                    'badMaximumNegativeNumberValue' => -9.999,
                ],
                [
                    'positiveValue' => [
                        'type' => 'integer',
                        'minimum' => 3,
                        'maximum' => 5,
                    ],
                    'badPositiveValue' => [
                        'type' => 'integer',
                        'minimum' => 3,
                        'maximum' => 5,
                    ],
                    'badPositiveValue2' => [
                        'type' => 'integer',
                        'minimum' => 3,
                        'maximum' => 5,
                    ],
                    'minimumNegativeValue' => [
                        'type' => 'integer',
                        'minimum' => -10,
                        'maximum' => 10,
                    ],
                    'badMinimumNegativeValue' => [
                        'type' => 'integer',
                        'minimum' => -10,
                        'maximum' => 10,
                    ],
                    'maximumNegativeValue' => [
                        'type' => 'integer',
                        'minimum' => -20,
                        'maximum' => -10,
                    ],
                    'badMaximumNegativeValue' => [
                        'type' => 'integer',
                        'minimum' => -20,
                        'maximum' => -10,
                    ],
                    'minimumNegativeNumberValue' => [
                        'type' => 'number',
                        'minimum' => -16,
                        'maximum' => -14,
                    ],
                    'maximumNegativeNumberValue' => [
                        'type' => 'number',
                        'minimum' => -16,
                        'maximum' => -14,
                    ],
                    'badMinimumNegativeNumberValue' => [
                        'type' => 'number',
                        'minimum' => -15,
                        'maximum' => -10,
                    ],
                    'badMaximumNegativeNumberValue' => [
                        'type' => 'number',
                        'minimum' => -13,
                        'maximum' => -10,
                    ],
                ],
            ),
        );
    }

    /**
     * When string field is correctly filled with minLength > value < maxLength  value, it must not return an error.
     * Else an error must be returned.
     */
    public function testMinMaxLengthFormat(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'tooSmallValue',
                    'value' => 'ab',
                    'message' => 'string.minLength',
                    'template' => 'The value of `tooSmallValue` key must be greater or equals to 3.',
                ],
                [
                    'key' => 'tooBigValue',
                    'value' => 'abcdefghij',
                    'message' => 'string.maxLength',
                    'template' => 'The value of `tooBigValue` key must be lower or equals to 8.',
                ],
            ],
            $this->validator->validate(
                [
                    'goodValue' => 'abcdef',
                    'tooSmallValue' => 'ab',
                    'tooBigValue' => 'abcdefghij',
                    'equalsToMinLength' => 'abc',
                    'equalsToMaxLength' => 'abcdefgh',
                ],
                [
                    'goodValue' => [
                        'type' => 'string',
                        'minLength' => 3,
                        'maxLength' => 8,
                    ],
                    'tooSmallValue' => [
                        'type' => 'string',
                        'minLength' => 3,
                        'maxLength' => 8,
                    ],
                    'tooBigValue' => [
                        'type' => 'string',
                        'minLength' => 3,
                        'maxLength' => 8,
                    ],
                    'equalsToMinLength' => [
                        'type' => 'string',
                        'minLength' => 3,
                        'maxLength' => 8,
                    ],
                    'equalsToMaxLength' => [
                        'type' => 'string',
                        'minLength' => 3,
                        'maxLength' => 8,
                    ],
                ],
            ),
        );
    }

    /**
     * When string field is correctly filled and respect the pattern, it must not return an error.
     * Else an error must be returned.
     */
    public function testPatternFormat(): void
    {
        $ibanRegex =
            '/^AL\d{10}[0-9A-Z]{16}$|^AD\d{10}[0-9A-Z]{12}$|^AT\d{18}$|^BH\d{2}[A-Z]{4}[0-9A-Z]{14}$|^BE\d{14}$|^B
        A\d{18}$|^BG\d{2}[A-Z]{4}\d{6}[0-9A-Z]{8}$|^HR\d{19}$|^CY\d{10}[0-9A-Z]{16}$|^CZ\d{22}$|^DK\d{16}$|^FO\d{16}$|^
        GL\d{16}$|^DO\d{2}[0-9A-Z]{4}\d{20}$|^EE\d{18}$|^FI\d{16}$|^FR\d{12}[0-9A-Z]{11}\d{2}$|^GE\d{2}[A-Z]{2}\d{16}$|
        ^DE\d{20}$|^GI\d{2}[A-Z]{4}[0-9A-Z]{15}$|^GR\d{9}[0-9A-Z]{16}$|^HU\d{26}$|^IS\d{24}$|^IE\d{2}[A-Z]{4}\d{14}$|^I
        L\d{21}$|^IT\d{2}[A-Z]\d{10}[0-9A-Z]{12}$|^[A-Z]{2}\d{5}[0-9A-Z]{13}$|^KW\d{2}[A-Z]{4}22!$|^LV\d{2}[A-Z]{4}[0-9
        A-Z]{13}$|^LB\d{6}[0-9A-Z]{20}$|^LI\d{7}[0-9A-Z]{12}$|^LT\d{18}$|^LU\d{5}[0-9A-Z]{13}$|^MK\d{5}[0-9A-Z]{10}\d{2
        }$|^MT\d{2}[A-Z]{4}\d{5}[0-9A-Z]{18}$|^MR13\d{23}$|^MU\d{2}[A-Z]{4}\d{19}[A-Z]{3}$|^MC\d{12}[0-9A-Z]{11}\d{2}$|
        ^ME\d{20}$|^NL\d{2}[A-Z]{4}\d{10}$|^NO\d{13}$|^PL\d{10}[0-9A-Z]{,16}n$|^PT\d{23}$|^RO\d{2}[A-Z]{4}[0-9A-Z]{16}$
        |^SM\d{2}[A-Z]\d{10}[0-9A-Z]{12}$|^SA\d{4}[0-9A-Z]{18}$|^RS\d{20}$|^SK\d{22}$|^SI\d{17}$|^ES\d{22}$|^SE\d{22}$|
        ^CH\d{7}[0-9A-Z]{12}$|^TN59\d{20}$|^TR\d{7}[0-9A-Z]{17}$|^AE\d{21}$|^GB\d{2}[A-Z]{4}\d{14}$/';

        $bicSwiftRegex = '/^[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$/';

        $phoneNumberRegex = '/^\+[1-9]\d{1,14}$/';

        $this->assertSame(
            [
                [
                    'key' => 'badIban',
                    'value' => 'BADPREFIX7630001007941234567890185',
                    'message' => 'string.pattern',
                    'template' => 'The value of `badIban` should be respect the pattern '.$ibanRegex.'.',
                ],
                [
                    'key' => 'badBicSwift',
                    'value' => 'qdsf11qs56df1562qs26d',
                    'message' => 'string.pattern',
                    'template' => 'The value of `badBicSwift` should be respect the pattern '.$bicSwiftRegex.'.',
                ],
                [
                    'key' => 'badPhoneNumber',
                    'value' => '0987878787878787',
                    'message' => 'string.pattern',
                    'template' => 'The value of `badPhoneNumber` should be respect the pattern '.$phoneNumberRegex.'.',
                ],
            ],
            $this->validator->validate(
                [
                    'goodIban' => 'FR7630001007941234567890185',
                    'goodBicSwift' => 'AGRIFRPP847',
                    'goodPhoneNumber' => '+33612345678',
                    'badIban' => 'BADPREFIX7630001007941234567890185',
                    'badBicSwift' => 'qdsf11qs56df1562qs26d',
                    'badPhoneNumber' => '0987878787878787',
                ],
                [
                    'goodIban' => [
                        'type' => 'string',
                        'pattern' => $ibanRegex,
                    ],
                    'goodBicSwift' => [
                        'type' => 'string',
                        'pattern' => $bicSwiftRegex,
                    ],
                    'goodPhoneNumber' => [
                        'type' => 'string',
                        'pattern' => $phoneNumberRegex,
                    ],
                    'badIban' => [
                        'type' => 'string',
                        'pattern' => $ibanRegex,
                    ],
                    'badBicSwift' => [
                        'type' => 'string',
                        'pattern' => $bicSwiftRegex,
                    ],
                    'badPhoneNumber' => [
                        'type' => 'string',
                        'pattern' => $phoneNumberRegex,
                    ],
                ],
            ),
        );
    }

    /**
     * When a number field is correctly filled in, it must not return an error.
     * If the field does not contain a value that is a multiple of, then an error must be returned.
     */
    public function testMultipleOf(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'example1',
                    'value' => 4,
                    'message' => 'multiple_of',
                    'template' => 'The value of `example1` key must be a multiple of 3.',
                ],
                [
                    'key' => 'example2',
                    'value' => 4,
                    'message' => 'multiple_of',
                    'template' => 'The value of `example2` key must be a multiple of 3.',
                ],
                [
                    'key' => 'example5',
                    'value' => '6',
                    'message' => 'type.integer',
                ],
                [
                    'key' => 'example6',
                    'value' => '6',
                    'message' => 'type.number',
                ],
            ],
            $this->validator->validate(
                [
                    'example1' => 4,
                    'example2' => 4,
                    'example3' => 6,
                    'example4' => 6,
                    'example5' => '6',
                    'example6' => '6',
                ],
                [
                    'example1' => [
                        'type' => 'integer',
                        'multipleOf' => 3,
                    ],
                    'example2' => [
                        'type' => 'number',
                        'multipleOf' => 3,
                    ],
                    'example3' => [
                        'type' => 'integer',
                        'multipleOf' => 3,
                    ],
                    'example4' => [
                        'type' => 'number',
                        'multipleOf' => 3,
                    ],
                    'example5' => [
                        'type' => 'integer',
                        'multipleOf' => 3,
                    ],
                    'example6' => [
                        'type' => 'number',
                        'multipleOf' => 3,
                    ],
                ],
            ),
        );
    }

    /**
     * The behavior of a nested object must be the same as an object.
     *
     * @see OpenApiValidatorTest::testObjectType()
     */
    public function testNestedObjectType(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'badDependency.badNestedDependency',
                    'value' => 'badNestedDependencyValue',
                    'message' => 'type.object',
                ],
                [
                    'key' => 'emptyBadDependency',
                    'value' => [],
                    'message' => 'type.object',
                ],
                [
                    'key' => 'nullBadDependency',
                    'value' => null,
                    'message' => 'type.object',
                ],
            ],
            $this->validator->validate(
                [
                    'dependency' => [
                        'nestedDependency' => [
                            'id' => '660c1473-ad37-4aae-afd4-1ff486d7f6f2',
                        ],
                    ],
                    'badDependency' => [
                        'badNestedDependency' => 'badNestedDependencyValue',
                    ],
                    'emptyBadDependency' => [],
                    'nullBadDependency' => null,
                    'nullableDependency' => null,
                ],
                [
                    'dependency' => [
                        'type' => 'object',
                        'properties' => [
                            'nestedDependency' => [
                                'type' => 'object',
                                'properties' => [
                                    'id' => [
                                        'type' => 'string',
                                        'format' => 'uuid',
                                    ],
                                ],
                            ],
                        ],
                    ],
                    'badDependency' => [
                        'type' => 'object',
                        'properties' => [
                            'badNestedDependency' => [
                                'type' => 'object',
                                'properties' => [
                                    'id' => [
                                        'type' => 'string',
                                        'format' => 'uuid',
                                    ],
                                ],
                            ],
                        ],
                    ],
                    'emptyBadDependency' => [
                        'type' => 'object',
                        'properties' => [
                            'badNestedDependency' => [
                                'type' => 'object',
                                'properties' => [
                                    'id' => [
                                        'type' => 'string',
                                        'format' => 'uuid',
                                    ],
                                ],
                            ],
                        ],
                    ],
                    'nullBadDependency' => [
                        'type' => 'object',
                        'properties' => [
                            'badNestedDependency' => [
                                'type' => 'object',
                                'properties' => [
                                    'id' => [
                                        'type' => 'string',
                                        'format' => 'uuid',
                                    ],
                                ],
                            ],
                        ],
                    ],
                    'nullableDependency' => [
                        'type' => 'object',
                        'nullable' => true,
                        'properties' => [
                            'badNestedDependency' => [
                                'type' => 'object',
                                'properties' => [
                                    'id' => [
                                        'type' => 'string',
                                        'format' => 'uuid',
                                    ],
                                ],
                            ],
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When a nested object contain a key with value null, if the config is nullable true, it's work.
     */
    public function testNestedObjectWithNullValue(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                [
                    'schedule' => [
                        'monday' => null,
                    ],
                ],
                [
                    'schedule' => [
                        'properties' => [
                            'monday' => [
                                'items' => [
                                    'date' => [
                                        'type' => 'string',
                                    ],
                                    'type' => 'object',
                                ],
                                'nullable' => true,
                                'type' => 'array',
                            ],
                        ],
                        'type' => 'object',
                    ],
                ],
            ),
        );
    }

    /**
     * When a nullable integer field is correctly filled in, it must not return an error.
     * If the field is of another type, then an error must be returned.
     */
    public function testNullableIntegerType(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'exampleNotNullable2',
                    'value' => null,
                    'message' => 'type.int.not_nullable',
                ],
            ],
            $this->validator->validate(
                [
                    'exampleNullable' => 50,
                    'exampleNullable2' => null,
                    'exampleNotNullable' => 100,
                    'exampleNotNullable2' => null,
                ],
                [
                    'exampleNullable' => [
                        'type' => 'integer',
                        'nullable' => true,
                    ],
                    'exampleNullable2' => [
                        'type' => 'integer',
                        'nullable' => true,
                    ],
                    'exampleNotNullable' => [
                        'type' => 'integer',
                        'nullable' => false,
                    ],
                    'exampleNotNullable2' => [
                        'type' => 'integer',
                        'nullable' => false,
                    ],
                ],
            ),
        );
    }

    /**
     * When a nullable string field is correctly filled in, it must not return an error.
     * If the field is of another type, then an error must be returned.
     */
    public function testNullableStringType(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'exampleNotNullable2',
                    'value' => null,
                    'message' => 'type.string.not_nullable',
                ],
            ],
            $this->validator->validate(
                [
                    'exampleNullable' => 'riri',
                    'exampleNullable2' => null,
                    'exampleNotNullable' => 'fifi',
                    'exampleNotNullable2' => null,
                ],
                [
                    'exampleNullable' => [
                        'type' => 'string',
                        'nullable' => true,
                    ],
                    'exampleNullable2' => [
                        'type' => 'string',
                        'nullable' => true,
                    ],
                    'exampleNotNullable' => [
                        'type' => 'string',
                        'nullable' => false,
                    ],
                    'exampleNotNullable2' => [
                        'type' => 'string',
                        'nullable' => false,
                    ],
                ],
            ),
        );
    }

    /**
     * When a number array field is correctly filled in, it must not return an error.
     * If the field is of another type, then an error must be returned.
     */
    public function testNumberArrayType(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                [
                    'vat' => [
                        5.0,
                        -11,
                        20.2,
                    ],
                ],
                [
                    'vat' => [
                        'type' => 'array',
                        'items' => [
                            'type' => 'number',
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When an object field is correctly filled in, it must not return an error.
     * If the field is of another type, then an error must be returned.
     */
    public function testObjectType(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'badDependency1',
                    'value' => 'bad',
                    'message' => 'type.object',
                ],
            ],
            $this->validator->validate(
                [
                    'dependency' => [
                        'id' => '660c1473-ad37-4aae-afd4-1ff486d7f6f2',
                    ],
                    'badDependency1' => 'bad',
                ],
                [
                    'dependency' => [
                        'type' => 'object',
                        'properties' => [
                            'id' => [
                                'type' => 'string',
                                'format' => 'uuid',
                            ],
                        ],
                    ],
                    'badDependency1' => [
                        'type' => 'object',
                        'properties' => [
                            'goodKey' => [
                                'type' => 'string',
                            ],
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When a string array field is correctly filled in, it must not return an error.
     * If the field is of another type, then an error must be returned.
     */
    public function testStringArrayType(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                [
                    'departments' => [
                        'Marketing',
                        'Com',
                        'IT',
                    ],
                ],
                [
                    'departments' => [
                        'type' => 'array',
                        'items' => [
                            'type' => 'string',
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When a string field is correctly filled in, it must not return an error.
     * If the field is of another type, then an error must be returned.
     */
    public function testStringType(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'example2',
                    'value' => 0,
                    'message' => 'type.string',
                ],
                [
                    'key' => 'example3',
                    'value' => true,
                    'message' => 'type.string',
                ],
            ],
            $this->validator->validate(
                $this->getSimpleScalarExample(),
                [
                    'example1' => [
                        'type' => 'string',
                    ],
                    'example2' => [
                        'type' => 'string',
                    ],
                    'example3' => [
                        'type' => 'string',
                    ],
                ],
            ),
        );
    }

    /**
     * If a format does not exist, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenTheFormatIsNotSupported(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage(
            \sprintf(
                'The riri format doesn\'t exist. The supported formats are %s.',
                \json_encode(
                    SupportedFormatEnum::cases(),
                ),
            ),
        );

        $this->validator->validate(
            $this->getSimpleScalarExample(),
            [
                'example1' => [
                    'type' => 'string',
                    'format' => 'riri',
                ],
                'example2' => [
                    'type' => 'string',
                    'format' => 'fifi',
                ],
                'example3' => [
                    'type' => 'string',
                    'format' => 'loulou',
                ],
            ],
        );
    }

    /**
     * If a type key does not exist for array items, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenTheArrayItemsTypeIsNotWritten(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The key type should be define for the items.');

        $this->validator->validate(
            [
                'example1' => [
                    'name' => 'example1_name1',
                ],
                'example2' => [
                    'name' => 'example2_name2',
                ],
            ],
            [
                'example1' => [
                    'type' => 'array',
                    'items' => [
                        'properties' => [
                            'name' => [
                                'type' => 'string',
                            ],
                        ],
                    ],
                ],
                'example2' => [
                    'type' => 'array',
                    'items' => [
                        'properties' => [
                            'name' => [
                                'type' => 'string',
                            ],
                        ],
                        'type' => 'object',
                    ],
                ],
            ],
        );
    }

    /**
     * If the maximum value is filled when the key type is not an integer, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenTheMaximumIsFilledOnNotStringType(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The `maximum` key can only be applied to an integer / number type.');

        $this->validator->validate(
            [
                'value' => 4,
            ],
            [
                'value' => [
                    'type' => 'string',
                    'maximum' => 0,
                ],
            ],
        );
    }

    /**
     * If the maximum value is not an integer, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenTheMaximumIsNotAnInteger(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The `maximum` value must be an integer.');

        $this->validator->validate(
            [
                'value' => 4,
            ],
            [
                'value' => [
                    'type' => 'integer',
                    'maximum' => 'fifi',
                ],
            ],
        );
    }

    /**
     * If the minimum value is filled when the key type is not an integer, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenTheMinimumIsFilledOnNotStringType(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The `minimum` key can only be applied to an integer / number type.');

        $this->validator->validate(
            [
                'value' => 4,
            ],
            [
                'value' => [
                    'type' => 'string',
                    'minimum' => 0,
                ],
            ],
        );
    }

    /**
     * If the minimum value is not an integer, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenTheMinimumIsNotAnInteger(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The `minimum` value must be an integer.');

        $this->validator->validate(
            [
                'value' => 4,
            ],
            [
                'value' => [
                    'type' => 'integer',
                    'minimum' => 'riri',
                ],
            ],
        );
    }

    /**
     * If the minimum value is not a number, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenTheMinimumIsNotANumber(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The `minimum` value must be an integer.');

        $this->validator->validate(
            [
                'value' => 4.0,
            ],
            [
                'value' => [
                    'type' => 'number',
                    'minimum' => 'fifi',
                ],
            ],
        );
    }

    /**
     * If the maximum value is not a number, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenTheMaximumIsNotANumber(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The `maximum` value must be an integer.');

        $this->validator->validate(
            [
                'value' => 4.0,
            ],
            [
                'value' => [
                    'type' => 'number',
                    'maximum' => 'fifi',
                ],
            ],
        );
    }

    /**
     * If the properties key is missing, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenThePropertiesIsNotFilledForArrayType(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The key items should be define for the type array.');

        $this->validator->validate(
            [
                'dependencies' => [
                    [
                        'id' => '660c1473-ad37-4aae-afd4-1ff486d7f6f2',
                    ],
                    [
                        'id' => '660c1473-ad37-4aae-afd4-1ff486d7f6f2',
                    ],
                ],
            ],
            [
                'dependencies' => [
                    'type' => 'array',
                ],
            ],
        );
    }

    /**
     * If the properties key is missing, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenThePropertiesIsNotFilledForObjectType(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The key properties should be define for the type object.');

        $this->validator->validate(
            [
                'dependency' => [
                    'id' => '660c1473-ad37-4aae-afd4-1ff486d7f6f2',
                ],
            ],
            [
                'dependency' => [
                    'type' => 'object',
                ],
            ],
        );
    }

    // @todo test nullable: true and false scalaire / object / array
    // attention null est différent de non présent
    // @todo test si je dis que c'est un objet mais que ça n'en est pas un
    // @todo test si je dis que c'est un array mais que ça n'en est pas un
    // @todo test si c'est un enum, doit faire parti de la liste associée

    // @todo enum, si une valeur string est passé pour un array enum boom

    /**
     * If a type key is missing, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenTheTypeIsNotFilled(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The type key is missing for the key example1.');

        $this->validator->validate(
            $this->getSimpleScalarExample(),
            [
                'example1' => [],
                'example2' => [],
                'example3' => [],
            ],
        );
    }

    /**
     * If a type does not exist, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenTheTypeIsNotSupported(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage(
            \sprintf(
                'The riri type doesn\'t exist. The supported types are %s.',
                \json_encode(
                    SupportedTypeEnum::cases(),
                ),
            ),
        );

        $this->validator->validate(
            $this->getSimpleScalarExample(),
            [
                'example1' => [
                    'type' => 'riri',
                ],
                'example2' => [
                    'type' => 'fifi',
                ],
                'example3' => [
                    'type' => 'loulou',
                ],
            ],
        );
    }

    /**
     * If the type value is not an integer / number and there is a multipleOf property, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenTheTypeOfMultipleOfIsNotAnIntegerOrNumber(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The `multipleOf` openApi property only works with integer and number types.');

        $this->validator->validate(
            [
                'example' => '6',
            ],
            [
                'example' => [
                    'type' => 'string',
                    'multipleOf' => 3,
                ],
            ],
        );
    }

    /**
     * If the value is not an integer / number and there is a multipleOf property, then a logic exception must be thrown.
     */
    public function testThrowNewLogicExceptionWhenTheValueOfMultipleOfIsNotAnIntegerOrNumber(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The `multipleOf` value must be an integer or number.');

        $this->validator->validate(
            [
                'example' => '6',
            ],
            [
                'example' => [
                    'type' => 'number',
                    'multipleOf' => '3',
                ],
            ],
        );
    }

    /**
     * When string array items have a maxLength constraint, exceeding it must return an error.
     */
    public function testStringArrayItemsMaxLength(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'tags',
                    'value' => 'toolongvalue',
                    'message' => 'string.maxLength',
                ],
            ],
            $this->validator->validate(
                [
                    'tags' => ['ok', 'toolongvalue'],
                    'validTags' => ['short', 'fine'],
                ],
                [
                    'tags' => [
                        'type' => 'array',
                        'items' => [
                            'type' => 'string',
                            'maxLength' => 5,
                        ],
                    ],
                    'validTags' => [
                        'type' => 'array',
                        'items' => [
                            'type' => 'string',
                            'maxLength' => 10,
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When string array items have a minLength constraint, items below it must return an error.
     */
    public function testStringArrayItemsMinLength(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'tags',
                    'value' => 'ab',
                    'message' => 'string.minLength',
                ],
            ],
            $this->validator->validate(
                [
                    'tags' => ['abc', 'ab', 'abcdef'],
                    'validTags' => ['hello', 'world'],
                ],
                [
                    'tags' => [
                        'type' => 'array',
                        'items' => [
                            'type' => 'string',
                            'minLength' => 3,
                        ],
                    ],
                    'validTags' => [
                        'type' => 'array',
                        'items' => [
                            'type' => 'string',
                            'minLength' => 3,
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When string array items have a url format constraint, invalid URLs must return an error.
     */
    public function testStringArrayItemsUrlFormat(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'urls',
                    'value' => 'not-a-url',
                    'message' => 'format.url',
                ],
                [
                    'key' => 'urls',
                    'value' => 'www.example.com',
                    'message' => 'format.url',
                ],
            ],
            $this->validator->validate(
                [
                    'urls' => ['https://valid.com', 'not-a-url', 'www.example.com'],
                    'validUrls' => ['https://valid.com', 'http://another.org'],
                ],
                [
                    'urls' => [
                        'type' => 'array',
                        'items' => [
                            'type' => 'string',
                            'format' => 'url',
                        ],
                    ],
                    'validUrls' => [
                        'type' => 'array',
                        'items' => [
                            'type' => 'string',
                            'format' => 'url',
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When an url string field is correctly filled in, it must not return an error.
     * If the field is of another type, then an error must be returned.
     */
    public function testUrlFormat(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'badUrl1',
                    'value' => 'www.vetinweb.fr',
                    'message' => 'format.url',
                ],
                [
                    'key' => 'badUrl2',
                    'value' => 'vetinweb.fr',
                    'message' => 'format.url',
                ],
                [
                    'key' => 'badUrl3',
                    'value' => 'vetinweb',
                    'message' => 'format.url',
                ],
            ],
            $this->validator->validate(
                [
                    'goodHttpUrl' => 'http://www.vetinweb.fr',
                    'goodHttpUrl2' => 'http://www.vetinweb.fr?q=1',
                    'goodHttpsUrl' => 'https://www.vetinweb.fr',
                    'goodHttpsUrl2' => 'https://www.vetinweb.fr?q=1',
                    'badUrl1' => 'www.vetinweb.fr',
                    'badUrl2' => 'vetinweb.fr',
                    'badUrl3' => 'vetinweb',
                ],
                [
                    'goodHttpUrl' => [
                        'type' => 'string',
                        'format' => 'url',
                    ],
                    'goodHttpUrl2' => [
                        'type' => 'string',
                        'format' => 'url',
                    ],
                    'goodHttpsUrl' => [
                        'type' => 'string',
                        'format' => 'url',
                    ],
                    'goodHttpsUrl2' => [
                        'type' => 'string',
                        'format' => 'url',
                    ],
                    'badUrl1' => [
                        'type' => 'string',
                        'format' => 'url',
                    ],
                    'badUrl2' => [
                        'type' => 'string',
                        'format' => 'url',
                    ],
                    'badUrl3' => [
                        'type' => 'string',
                        'format' => 'url',
                    ],
                ],
            ),
        );
    }

    public function testRequired(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'app',
                    'value' => null,
                    'message' => 'required',
                ],
            ],
            $this->validator->validate(
                [
                ],
                [
                    'required' => [
                        'app',
                    ],
                    'app' => [
                        'type' => 'object',
                        'properties' => [
                            'id' => [
                                'type' => 'int',
                            ],
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When uuid array field is correctly filled in uuid format, it must not return an error.
     * Else an error must be returned.
     */
    public function testUuidArrayFormat(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'badUuidExample[0].id',
                    'value' => 'bad',
                    'message' => 'format.uuid',
                ],
                [
                    'key' => 'badUuidExample[1].id',
                    'value' => 'bad too',
                    'message' => 'format.uuid',
                ],
                [
                    'key' => 'badUuidExample2',
                    'value' => '',
                    'message' => 'type.array',
                ],
                [
                    'key' => 'badUuidExample3[0].id',
                    'value' => 'bad',
                    'message' => 'format.uuid',
                ],
                [
                    'key' => 'badUuidExample3[1].id',
                    'value' => 'bad too',
                    'message' => 'format.uuid',
                ],
            ],
            $this->validator->validate(
                [
                    'goodExample' => [
                        0 => [
                            'id' => '660c1473-ad37-4aae-afd4-1ff486d7f6f2',
                        ],
                        1 => [
                            'id' => '66ac1473-ad37-4aae-afd4-1ff486d7f6f4',
                        ],
                    ],
                    'badUuidExample' => [
                        0 => [
                            'id' => 'bad',
                        ],
                        1 => [
                            'id' => 'bad too',
                        ],
                    ],
                    'badUuidExample2' => '',
                    'badUuidExample3' => [
                        0 => [
                            'id' => 'bad',
                        ],
                        1 => [
                            'id' => 'bad too',
                        ],
                    ],
                ],
                [
                    'goodExample' => [
                        'type' => 'array',
                        'items' => [
                            'properties' => [
                                'id' => [
                                    'format' => 'uuid',
                                    'type' => 'string',
                                ],
                            ],
                            'type' => 'object',
                        ],
                    ],
                    'badUuidExample' => [
                        'type' => 'array',
                        'items' => [
                            'properties' => [
                                'id' => [
                                    'format' => 'uuid',
                                    'type' => 'string',
                                ],
                            ],
                            'type' => 'object',
                        ],
                    ],
                    'badUuidExample2' => [
                        'type' => 'array',
                        'items' => [
                            'properties' => [
                                'id' => [
                                    'format' => 'uuid',
                                    'type' => 'string',
                                ],
                            ],
                            'type' => 'object',
                        ],
                    ],
                    'badUuidExample3' => [
                        'type' => 'array',
                        'items' => [
                            'properties' => [
                                'id' => [
                                    'format' => 'uuid',
                                    'type' => 'string',
                                ],
                            ],
                            'type' => 'object',
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When string field is correctly filled in uuid format, it must not return an error.
     * Else an error must be returned.
     */
    public function testUuidFormat(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'badUuidExample',
                    'value' => '660c1473&ad37!4aae5afd491ff486d7f6f2',
                    'message' => 'format.uuid',
                ],
            ],
            $this->validator->validate(
                [
                    'uuidExample' => '660c1473-ad37-4aae-afd4-1ff486d7f6f2',
                    'badUuidExample' => '660c1473&ad37!4aae5afd491ff486d7f6f2',
                ],
                [
                    'uuidExample' => [
                        'type' => 'string',
                        'format' => 'uuid',
                    ],
                    'badUuidExample' => [
                        'type' => 'string',
                        'format' => 'uuid',
                    ],
                ],
            ),
        );
    }

    // -------------------------------------------------------------------------
    // Coverage additions
    // -------------------------------------------------------------------------

    /**
     * When data has a key absent from the config, it must be silently ignored.
     */
    public function testExtraDataKeyIsIgnored(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                [
                    'name' => 'John',
                    'undeclaredKey' => 'ignored',
                ],
                [
                    'name' => ['type' => 'string'],
                ],
            ),
        );
    }

    /**
     * When a config key uses anyOf, both validation passes must bypass it without error.
     */
    public function testAnyOfIsIgnored(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                ['field' => 'some value'],
                [
                    'field' => [
                        'anyOf' => [
                            ['type' => 'string'],
                            ['type' => 'integer'],
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When a non-nullable boolean field receives null, a type.boolean error must be returned.
     */
    public function testBooleanNullNotNullable(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'active',
                    'value' => null,
                    'message' => 'type.boolean',
                ],
            ],
            $this->validator->validate(
                ['active' => null],
                ['active' => ['type' => 'boolean']],
            ),
        );
    }

    /**
     * When a nullable number receives null it must pass; when not nullable it must return type.number.not_nullable.
     */
    public function testNullableNumberType(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'notNullable',
                    'value' => null,
                    'message' => 'type.number.not_nullable',
                ],
            ],
            $this->validator->validate(
                [
                    'nullable' => null,
                    'notNullable' => null,
                ],
                [
                    'nullable' => ['type' => 'number', 'nullable' => true],
                    'notNullable' => ['type' => 'number'],
                ],
            ),
        );
    }

    /**
     * When a field has a format constraint but its value is not a string, a type.string error must be returned.
     */
    public function testFormatConstraintWithNonStringValue(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'field',
                    'value' => 42,
                    'message' => 'type.string',
                ],
            ],
            $this->validator->validate(
                ['field' => 42],
                ['field' => ['type' => 'integer', 'format' => 'email']],
            ),
        );
    }

    /**
     * When a string array contains a null item and items are not nullable, a type.string.not_nullable error must be returned.
     * When it contains a non-string item, a type.string error must be returned.
     */
    public function testStringArrayItemTypeValidation(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'tags',
                    'value' => null,
                    'message' => 'type.string.not_nullable',
                ],
                [
                    'key' => 'tags',
                    'value' => 42,
                    'message' => 'type.string',
                ],
            ],
            $this->validator->validate(
                ['tags' => ['valid', null, 42]],
                [
                    'tags' => [
                        'type' => 'array',
                        'items' => ['type' => 'string'],
                    ],
                ],
            ),
        );
    }

    /**
     * When an integer array contains null or non-integer items, errors must be returned.
     */
    public function testIntArrayItemTypeValidation(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'ids',
                    'value' => null,
                    'message' => 'type.int.not_nullable',
                ],
                [
                    'key' => 'ids',
                    'value' => 'bad',
                    'message' => 'type.int',
                ],
            ],
            $this->validator->validate(
                ['ids' => [1, null, 'bad']],
                [
                    'ids' => [
                        'type' => 'array',
                        'items' => ['type' => 'integer'],
                    ],
                ],
            ),
        );
    }

    /**
     * When a number array contains null or non-number items, errors must be returned.
     */
    public function testNumberArrayItemTypeValidation(): void
    {
        $this->assertSame(
            [
                [
                    'key' => 'values',
                    'value' => null,
                    'message' => 'type.number.not_nullable',
                ],
                [
                    'key' => 'values',
                    'value' => 'bad',
                    'message' => 'type.number',
                ],
            ],
            $this->validator->validate(
                ['values' => [1.5, null, 'bad']],
                [
                    'values' => [
                        'type' => 'array',
                        'items' => ['type' => 'number'],
                    ],
                ],
            ),
        );
    }

    /**
     * When an object-typed array has a null value and is not nullable, a type.not_nullable error must be returned.
     */
    public function testObjectArrayNullValueNotNullable(): void
    {
        $this->assertSame(
            [['key' => 'collection', 'value' => null, 'message' => 'type.not_nullable']],
            $this->validator->validate(
                ['collection' => null],
                [
                    'collection' => [
                        'type' => 'array',
                        'items' => [
                            'type' => 'object',
                            'properties' => ['id' => ['type' => 'integer']],
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When an object-typed array receives a non-array value, a type.array error must be returned.
     */
    public function testObjectArrayNonArrayValue(): void
    {
        $this->assertSame(
            [['key' => 'collection', 'value' => 'bad', 'message' => 'type.array']],
            $this->validator->validate(
                ['collection' => 'bad'],
                [
                    'collection' => [
                        'type' => 'array',
                        'items' => [
                            'type' => 'object',
                            'properties' => ['id' => ['type' => 'integer']],
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When an object array has a null item and items are not nullable, a type.array error must be returned.
     */
    public function testObjectArrayNullItemNotNullable(): void
    {
        $this->assertSame(
            [['key' => 'collection', 'value' => null, 'message' => 'type.array']],
            $this->validator->validate(
                ['collection' => [null]],
                [
                    'collection' => [
                        'type' => 'array',
                        'items' => [
                            'type' => 'object',
                            'properties' => ['id' => ['type' => 'integer']],
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When a oneOf field is nullable and receives null, no error must be returned.
     */
    public function testOneOfWithNullableNullValue(): void
    {
        $this->assertSame(
            [],
            $this->validator->validate(
                ['field' => null],
                [
                    'field' => [
                        'nullable' => true,
                        'oneOf' => [
                            ['type' => 'string'],
                            ['type' => 'integer'],
                        ],
                    ],
                ],
            ),
        );
    }

    /**
     * When the oneOf value is not an array, a LogicException must be thrown.
     */
    public function testThrowWhenOneOfIsNotArray(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('The `oneOf` key must be an array.');

        $this->validator->validate(
            ['field' => 'value'],
            ['field' => ['oneOf' => 'not-an-array']],
        );
    }

    /**
     * When a oneOf schema item is not an array, a LogicException must be thrown.
     */
    public function testThrowWhenOneOfItemIsNotArray(): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage('Each `oneOf` item must be an array.');

        $this->validator->validate(
            ['field' => 'value'],
            ['field' => ['oneOf' => ['not-an-array']]],
        );
    }

    /**
     * @return array<string, array{0: string, 1: array<string, mixed>, 2: array<string, mixed>}>
     */
    public static function provideInvalidConstraintConfigs(): array
    {
        return [
            'minItems negative integer' => [
                'The `minItems` value must be a non-negative integer.',
                ['arr' => ['one']],
                ['arr' => ['type' => 'array', 'minItems' => -1, 'items' => ['type' => 'string']]],
            ],
            'maxItems negative integer' => [
                'The `maxItems` value must be a non-negative integer.',
                ['arr' => ['one']],
                ['arr' => ['type' => 'array', 'maxItems' => -1, 'items' => ['type' => 'string']]],
            ],
            'uniqueItems not a boolean' => [
                'The `uniqueItems` value must be a boolean.',
                ['arr' => ['one']],
                ['arr' => ['type' => 'array', 'uniqueItems' => 'yes', 'items' => ['type' => 'string']]],
            ],
            'maxLength on non-string type' => [
                'The `maxLength` key can only be applied to a string type.',
                ['field' => 5],
                ['field' => ['type' => 'integer', 'maxLength' => 10]],
            ],
            'maxLength value not an int' => [
                'The `maxLength` value must be an int.',
                ['field' => 'hello'],
                ['field' => ['type' => 'string', 'maxLength' => 'ten']],
            ],
            'minLength on non-string type' => [
                'The `minLength` key can only be applied to a string type.',
                ['field' => 5],
                ['field' => ['type' => 'integer', 'minLength' => 3]],
            ],
            'minLength value not an int' => [
                'The `minLength` value must be an int.',
                ['field' => 'hello'],
                ['field' => ['type' => 'string', 'minLength' => 'three']],
            ],
            'pattern on non-string type' => [
                'The `pattern` key can only be applied to an string type.',
                ['field' => 5],
                ['field' => ['type' => 'integer', 'pattern' => '/[a-z]/']],
            ],
            'pattern value is empty string' => [
                'The `pattern` value must be an string.',
                ['field' => 'hello'],
                ['field' => ['type' => 'string', 'pattern' => '']],
            ],
        ];
    }

    /**
     * @dataProvider provideInvalidConstraintConfigs
     *
     * @param array<string, mixed> $data
     * @param array<string, mixed> $config
     */
    public function testThrowOnInvalidConstraintConfig(string $expectedMessage, array $data, array $config): void
    {
        $this->expectException(\LogicException::class);
        $this->expectExceptionMessage($expectedMessage);

        $this->validator->validate($data, $config);
    }

    /**
     * @return array<string, mixed>
     */
    private function getSimpleScalarExample(): array
    {
        return [
            'example1' => "I\'am a string.",
            'example2' => 0,
            'example3' => true,
        ];
    }
}
