<?php

declare(strict_types=1);

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

use NoahVet\Reef\Domain\Validation\V2\OpenApiPropertyValidator;
use NoahVet\Reef\Domain\Validation\V2\OpenApiValidator;
use NoahVet\Reef\Domain\Validation\V2\OpenApiValidatorInterface;
use PHPUnit\Framework\TestCase;

/**
 * This class respects the pattern:
 * testGiven<Context>When<Method>Then<ExpectedResult>
 */
final class RequiredOpenApiValidatorTest extends TestCase
{
    private OpenApiValidatorInterface $subject;

    protected function setUp(): void
    {
        $this->subject = new OpenApiValidator([
            new OpenApiPropertyValidator(),
        ]);
    }

    /**
     * Given a required clinicName is missing from the payload,
     * When validate call,
     * Then the validation returns false.
     */
    public function testGivenRequiredClinicNameMissingInPayloadWhenValidateCallThenReturnsFalse(): void
    {
        $rules = [
            'clinicName' => [],
            'required' => [
                'clinicName',
            ],
        ];

        $payload = [];

        $this->assertFalse($this->subject->validate($payload, $rules));
    }

    /**
     * Given a model with a required string field and a payload providing this value,
     * When validate call,
     * And payload is correct,
     * Then the validation returns true.
     */
    public function testShouldReturnTrueWhenModelHaveRequiredValueAndPayloadIsCorrect(): void
    {
        $rules = [
            'requiredStringKey' => [
                'type' => 'string',
            ],
            'required' => [
                'requiredStringKey',
            ],
        ];

        $payload = [
            'requiredStringKey' => 'requiredStringValue',
        ];

        $this->assertTrue($this->subject->validate($payload, $rules));
    }

    /**
     * Given a deep model with required fields
     * When validate call
     * And payload contain a bad products value (parent)
     * Then returns true.
     */
    public function testShouldReturnTrueWhenModelHaveDeepRequiredValueAndPayloadContainBadProducts(): void
    {
        $rules = [
            'type' => 'object',
            'properties' => [
                'products' => [
                    'type' => 'array',
                    'items' => [
                        'type' => 'object',
                        'properties' => [
                            'categories' => [
                                'type' => 'array',
                                'items' => [
                                    'type' => 'object',
                                    'properties' => [
                                        'name' => [
                                            'type' => 'string',
                                        ],
                                    ],
                                    'required' => [
                                        'name',
                                    ],
                                ],
                            ],
                        ],
                        'required' => [
                            'categories',
                        ],
                    ],
                ],
            ],
            'required' => [
                'products',
            ],
        ];

        $payload = [
            'products' => 'badValue',
        ];

        $this->assertFalse($this->subject->validate($payload, $rules));
    }

    /**
     * Given a deep model with required fields
     * When validate call
     * And payload contain a bad categories value (child)
     * Then returns true.
     */
    public function testShouldReturnTrueWhenModelHaveDeepRequiredValueAndPayloadContainBadCategories(): void
    {
        $rules = [
            'type' => 'object',
            'properties' => [
                'products' => [
                    'type' => 'array',
                    'items' => [
                        'type' => 'object',
                        'properties' => [
                            'categories' => [
                                'type' => 'array',
                                'items' => [
                                    'type' => 'object',
                                    'properties' => [
                                        'name' => [
                                            'type' => 'string',
                                        ],
                                    ],
                                    'required' => [
                                        'name',
                                    ],
                                ],
                            ],
                        ],
                        'required' => [
                            'categories',
                        ],
                    ],
                ],
            ],
            'required' => [
                'products',
            ],
        ];

        $payload = [
            'products' => [
                'categories' => 'badValue',
            ],
        ];

        $this->assertFalse($this->subject->validate($payload, $rules));
    }

    /**
     * Given a deep model with required fields
     * When validate call
     * And payload is correct
     * Then returns true.
     */
    public function testShouldReturnTrueWhenModelHaveDeepRequiredValueAndPayloadIsCorrect(): void
    {
        $rules = [
            'type' => 'object',
            'properties' => [
                'products' => [
                    'type' => 'array',
                    'items' => [
                        'type' => 'object',
                        'properties' => [
                            'categories' => [
                                'type' => 'array',
                                'items' => [
                                    'type' => 'object',
                                    'properties' => [
                                        'name' => [
                                            'type' => 'string',
                                        ],
                                    ],
                                    'required' => [
                                        'name',
                                    ],
                                ],
                            ],
                        ],
                        'required' => [
                            'categories',
                        ],
                    ],
                ],
            ],
            'required' => [
                'products',
            ],
        ];

        $payload = [
            'products' => [
                'categories' => [
                    'name' => 'goodValue',
                ],
            ],
        ];

        $this->assertTrue($this->subject->validate($payload, $rules));
    }

    /**
     * Given a model with a required array field and a payload providing a value,
     * When validate call,
     * Then the validation returns false.
     */
    public function testGivenModelWithRequiredArrayAndPayloadWithNotArrayValueWhenValidateCallThenReturnsFalse(): void
    {
        $rules = [
            'products' => [
                'type' => 'array',
                'items' => [
                    'properties' => [
                        'name' => [],
                    ],
                ],
            ],
        ];

        $payload = [
            'products' => 'badValue',
        ];

        $this->assertFalse($this->subject->validate($payload, $rules));
    }

    /**
     * Given an array of objects with required fields and a non-list payload,
     * When validate call,
     * Then validation fails because required fields are missing.
     */
    public function testGivenArrayItemsWithRequiredFieldAndAssociativePayloadWhenValidateCallThenReturnsFalse(): void
    {
        $rules = [
            'products' => [
                'type' => 'array',
                'items' => [
                    'type' => 'object',
                    'properties' => [
                        'name' => [
                            'type' => 'string',
                        ],
                    ],
                    'required' => [
                        'name',
                    ],
                ],
            ],
        ];

        $payload = [
            'products' => [
                'nameIsMissing' => 'value',
            ],
        ];

        $this->assertFalse($this->subject->validate($payload, $rules));
    }
}
