<?php
declare(strict_types=1);

namespace SwaggerBake\Test\TestCase\Lib\Operation;

use Cake\Routing\RouteBuilder;
use Cake\Routing\Router;
use Cake\TestSuite\TestCase;
use SwaggerBake\Lib\Attribute\OpenApiResponse;
use SwaggerBake\Lib\Configuration;
use SwaggerBake\Lib\Exception\SwaggerBakeRunTimeException;
use SwaggerBake\Lib\OpenApi\Schema;
use SwaggerBake\Lib\Operation\OperationResponseAssociation;
use SwaggerBake\Lib\Route\RouteScanner;
use SwaggerBake\Lib\SwaggerFactory;

class OperationResponseAssociationTest extends TestCase
{
    /**
     * @var string[]
     */
    public array $fixtures = [
        'plugin.SwaggerBake.DepartmentEmployees',
        'plugin.SwaggerBake.Employees',
        'plugin.SwaggerBake.EmployeeSalaries',
        'plugin.SwaggerBake.EmployeeTitles',
    ];

    private Configuration $config;

    /**
     * @var \SwaggerBake\Lib\Route\RouteDecorator[]
     */
    private array $routes;

    public function setUp(): void
    {
        parent::setUp(); // TODO: Change the autogenerated stub
        Router::createRouteBuilder('/')->scope('/', function (RouteBuilder $builder) {
            $builder->setExtensions(['json']);
            $builder->resources('Employees');
        });

        $this->config = new Configuration([
            'prefix' => '/',
            'yml' => '/config/swagger-bare-bones.yml',
            'json' => '/webroot/swagger.json',
            'webPath' => '/swagger.json',
            'hotReload' => false,
            'exceptionSchema' => 'Exception',
            'requestAccepts' => ['application/x-www-form-urlencoded'],
            'responseContentTypes' => ['application/json'],
            'namespaces' => [
                'controllers' => ['\SwaggerBakeTest\App\\'],
                'entities' => ['\SwaggerBakeTest\App\\'],
                'tables' => ['\SwaggerBakeTest\App\\'],
            ],
        ], SWAGGER_BAKE_TEST_APP);

        $cakeRoute = new RouteScanner(new Router(), $this->config);
        $this->routes = $cakeRoute->getRoutes();
    }

    public function test_schema_type_object(): void
    {
        $swagger = (new SwaggerFactory($this->config, new RouteScanner(new Router(), $this->config)))->create();

        $assoc = new OperationResponseAssociation(
            $swagger,
            $this->routes['employees:view'],
            $swagger->getSchemaByName('Employee'),
        );

        $schema = $assoc->build(new OpenApiResponse(
            schemaType: 'object',
            associations: []
        ));

        $this->assertInstanceOf(Schema::class, $schema);
        $this->assertArrayHasKey('department_employees', $schema->getProperties());
        $this->assertArrayHasKey('employee_salaries', $schema->getProperties());
        $this->assertArrayHasKey('employee_titles', $schema->getProperties());
    }

    public function test_white_list(): void
    {
        $swagger = (new SwaggerFactory($this->config, new RouteScanner(new Router(), $this->config)))->create();

        $assoc = new OperationResponseAssociation(
            $swagger,
            $this->routes['employees:view'],
            $swagger->getSchemaByName('Employee'),
        );

        $schema = $assoc->build(new OpenApiResponse(
            schemaType: 'object',
            associations: ['table' => 'DepartmentEmployees','whiteList' => ['Employees.EmployeeTitles']]
        ));

        $this->assertArrayHasKey('employee', $schema->getProperties());
        $this->assertArrayHasKey('employee_titles', $schema->getProperties()['employee']->getProperties());
    }

    public function test_false_white_list(): void
    {
        $swagger = (new SwaggerFactory($this->config, new RouteScanner(new Router(), $this->config)))->create();
        $assoc = new OperationResponseAssociation(
            $swagger,
            $this->routes['employees:view'],
            null
        );

        $schema = $assoc->build(new OpenApiResponse(
            schemaType: 'object',
            associations: ['whiteList' => false]
        ));

        $this->assertInstanceOf(Schema::class, $schema);
        $this->assertArrayNotHasKey('department_employees', $schema->getProperties());
        $this->assertArrayNotHasKey('employee_salaries', $schema->getProperties());
        $this->assertArrayNotHasKey('employee_titles', $schema->getProperties());
    }

    public function test_null_schema(): void
    {
        $assoc = new OperationResponseAssociation(
            (new SwaggerFactory($this->config, new RouteScanner(new Router(), $this->config)))->create(),
            $this->routes['employees:view'],
            null
        );

        $schema = $assoc->build(new OpenApiResponse(
            schemaType: 'object',
            associations: []
        ));

        $this->assertInstanceOf(Schema::class, $schema);
        $this->assertArrayHasKey('department_employees', $schema->getProperties());
        $this->assertArrayHasKey('employee_salaries', $schema->getProperties());
        $this->assertArrayHasKey('employee_titles', $schema->getProperties());
    }

    public function test_invalid_table_throws_exception(): void
    {
        $this->expectException(SwaggerBakeRunTimeException::class);

        (new OperationResponseAssociation(
            (new SwaggerFactory($this->config, new RouteScanner(new Router(), $this->config)))->create(),
            $this->routes['employees:view'],
            null
        ))->build(new OpenApiResponse(
            schemaType: 'object',
            associations: ['table' => 'Nopes']
        ));
    }

    public function test_invalid_schema_mode_exception(): void
    {
        $this->expectException(SwaggerBakeRunTimeException::class);
        $route = $this->routes['employees:view'];
        $route->setMethods(['NOPE']);

        (new OperationResponseAssociation(
            (new SwaggerFactory($this->config, new RouteScanner(new Router(), $this->config)))->create(),
            $route,
            null
        ))->build(new OpenApiResponse(
            schemaType: 'object',
            associations: ['table' => 'Nopes']
        ));
    }

    public function test_associate_one(): void
    {
        $schema = (new OperationResponseAssociation(
            (new SwaggerFactory($this->config, new RouteScanner(new Router(), $this->config)))->create(),
            $this->routes['employees:view'],
            null
        ))->build(new OpenApiResponse(
            schemaType: 'object',
            associations: ['table' => 'EmployeeSalaries', 'whiteList' => ['Employees']]
        ));

        /** @var \SwaggerBake\Lib\OpenApi\SchemaProperty $schemaProperty */
        $schemaProperty = $schema->getProperties()['employee'];
        $this->assertEquals('employee', $schemaProperty->getName());
    }

    public function test_associate_throws_exception_when_association_not_found(): void
    {
        $this->expectException(SwaggerBakeRunTimeException::class);
        (new OperationResponseAssociation(
            (new SwaggerFactory($this->config, new RouteScanner(new Router(), $this->config)))->create(),
            $this->routes['employees:view'],
            null
        ))->build(new OpenApiResponse(
            schemaType: 'object',
            associations: ['table' => 'EmployeeSalaries', 'whiteList' => ['Nope']]
        ));
        $this->expectExceptionMessageMatches('/OpenApiResponse association not found/');
    }
}
