<?php

namespace App\Tests\Unit\Service\Rest\Operation;

use App\Service\Rest\ApiRequestService;
use App\Service\Rest\Operation\BaseRestOperation;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpClient\Exception\ClientException;
use Symfony\Contracts\HttpClient\ResponseInterface;

class TestableBaseRestOperation extends BaseRestOperation {
    public function getChangeLog(): array { return []; }
}

class BaseRestOperationTest extends TestCase
{
    private ApiRequestService $apiRequestService;

    public function setUp(): void {
        $this->apiRequestService = $this->getMockBuilder(ApiRequestService::class)
            ->disableOriginalConstructor()
            ->getMock();
    }

    /**
     * @see BaseRestOperation::__construct()
     * @see BaseRestOperation::getLabel()
     * @see BaseRestOperation::getMethod()
     * @see BaseRestOperation::getPath()
     * @see BaseRestOperation::getInitialData()
     * @see BaseRestOperation::getParameters()
     * @see BaseRestOperation::getOptions()
     * @see BaseRestOperation::__toString()
     */
    public function testGetters(): void
    {
        $operation = new TestableBaseRestOperation(
            'a label',
            'GET',
            '/a/path',
            ['data' => 1],
            ['param' => 2],
            ['option' => 3]
        );

        $this->assertEquals('a label', $operation->getLabel());
        $this->assertEquals('GET', $operation->getMethod());
        $this->assertEquals('/a/path', $operation->getPath());
        $this->assertEquals(['data' => 1], $operation->getInitialData());
        $this->assertEquals(['param' => 2], $operation->getParameters());
        $this->assertEquals(['option' => 3], $operation->getOptions());

        $this->assertEquals('GET /a/path', (string)$operation);
    }

    /**
     * Test execution with a valid request
     * @see BaseRestOperation::execute()
     * @see BaseRestOperation::getStatus()
     * @see BaseRestOperation::getErrorMessage()
     */
    public function testExecuteValid(): void
    {
        $operation = $this->getMockBuilder(TestableBaseRestOperation::class)
            ->setConstructorArgs(['Update entity 1', 'PUT', 'entity/1', [], [], ['json' => '{"a":1}']])
            ->setMethodsExcept(['execute', 'getStatus', 'getErrorMessage'])
            ->getMock();

        $responseOk = $this->getMockBuilder(ResponseInterface::class)->getMock();
        $responseOk->method('getStatusCode')->willReturn(200);

        $this->apiRequestService
            ->expects($this->once())
            ->method('request')
            ->with('PUT', 'entity/1', [], ['json' => '{"a":1}'])
            ->willReturn($responseOk);

        $operation->execute($this->apiRequestService);

        $this->assertEquals(BaseRestOperation::STATUS_DONE, $operation->getStatus());
        $this->assertEquals("", $operation->getErrorMessage());
    }

    /**
     * Test execution with an invalid request (api returns an error 404 for example)
     * @see BaseRestOperation::execute()
     * @see BaseRestOperation::getStatus()
     * @see BaseRestOperation::getErrorMessage()
     */
    public function testExecuteInvalid(): void
    {
        $operation = $this->getMockBuilder(TestableBaseRestOperation::class)
            ->setConstructorArgs(['Update entity 1', 'PUT', 'entity/2'])
            ->setMethodsExcept(['execute', 'getStatus', 'getErrorMessage'])
            ->getMock();

        $responseError = $this->getMockBuilder(ResponseInterface::class)->getMock();
        $responseError->method('getStatusCode')->willReturn(404);
        $responseError->method('getContent')->willReturn('Not found');

        $this->apiRequestService
            ->expects($this->once())
            ->method('request')
            ->with('PUT', 'entity/2')
            ->willReturn($responseError);

        try {
            $operation->execute($this->apiRequestService);
        } catch (\RuntimeException) {}
        $this->assertEquals(BaseRestOperation::STATUS_ERROR, $operation->getStatus());
        self::assertMatchesRegularExpression('/.*Not found.*/', $operation->getErrorMessage());
    }

    /**
     * Test execution if the request throw an HTTP exception
     * @see BaseRestOperation::execute()
     * @see BaseRestOperation::getStatus()
     * @see BaseRestOperation::getErrorMessage()
     */
    public function testExecutionError(): void
    {
        $operation = $this->getMockBuilder(TestableBaseRestOperation::class)
            ->setConstructorArgs(['Update entity 1', 'PUT', 'entity/3'])
            ->setMethodsExcept(['execute', 'getStatus', 'getErrorMessage'])
            ->getMock();

        $responseException = $this->getMockBuilder(ResponseInterface::class)->getMock();
        $responseException->method('getStatusCode')->willReturn(500);
        $responseException->method('getContent')->willReturn('The server said the request is bad');
        $responseException->method('getInfo')->willReturnMap([
            ['http_code', 500],
            ['url', 'entity/3'],
            ['response_headers', ['content-type: json']]
        ]);

        $this->apiRequestService
            ->expects($this->once())
            ->method('request')
            ->with('PUT', 'entity/3')
            ->willThrowException(new ClientException($responseException));

        try {
            $operation->execute($this->apiRequestService);
        } catch (\RuntimeException) {
        }
        $this->assertEquals(BaseRestOperation::STATUS_ERROR, $operation->getStatus());
        $this->assertMatchesRegularExpression(
            '/.*ClientException: HTTP 500 returned for "entity\/3".*/',
            $operation->getErrorMessage()
        );
    }
}
