<?php

namespace Opentalent\OtCore\Tests\Unit\Service;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\TransferException;
use Nimut\TestingFramework\TestCase\UnitTestCase;
use Opentalent\OtCore\Exception\ApiRequestException;
use Opentalent\OtCore\Service\OpentalentApiService;
use Opentalent\OtCore\Tests\Unit\Fixtures\ApiResponseFixtures;
use PHPUnit\TextUI\RuntimeException;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use TYPO3\CMS\Core\Core\ApplicationContext;

class OpentalentApiServiceTest extends UnitTestCase
{
    use ProphecyTrait;

    protected $context;
    /**
     * @var ApiResponseFixtures
     */
    protected $fixture;
    /**
     * @var \Prophecy\Prophecy\ObjectProphecy
     */
    protected $client;

    protected $service;

    public function setUp(): void
    {
        // mock the application context
        $this->context = $this->prophesize(ApplicationContext::class);
        $this->context->isProduction()->willReturn(false);
        $this->context->isDevelopment()->willReturn(false);
        $this->context->isTesting()->willReturn(true);

        $this->client = $this->prophesize(Client::class);

        $this->service = new OpentalentApiService($this->client->reveal(), $this->context->reveal());

        $this->fixture = new ApiResponseFixtures();
    }

    protected function injectClientFor($uri, $http_method='GET', $params = []) {
        // mock the Guzzle client
        $client = $this->prophesize(Client::class);
        try {
            $willReturn = $this->fixture->get($uri);
            $client->request($http_method, $uri, Argument::any())
                ->shouldBeCalled()
                ->willReturn($willReturn);

        } catch (\RuntimeException $e) {
            $client->request($http_method, Argument::type('string'), [])
                ->shouldBeCalled()
                ->willThrow(new TransferException('bad prophecy request'));
        }

        $reflectionObject = new \ReflectionObject($this->service);
        $reflectionMethod = $reflectionObject->getMethod('injectClient');
        $reflectionMethod->setAccessible(true);

        $reflectionMethod->invokeArgs($this->service, [$client->reveal()]);
    }

    /**
     * get should build a valid url, send a query and
     * return a Guzzle response object
     *
     * @test
     */
    public function get() {

        $base_uri = "api/public/organizations";
        $params = ['_format' => 'json', 'itemsPerPage' => 10, 'foo' => 1];

        // uri as it is supposed to be processed by the repo
        $processed_uri = $base_uri . "?_format=json&itemsPerPage=10&foo=1";

        $this->injectClientFor($processed_uri, 'GET', $params);
        $actual = $this->service->request('GET', $base_uri, $params);

        $this->assertEquals(200, $actual->getStatusCode());
    }

    /**
     * get should build a valid url, send a query and
     * return a Guzzle response object
     *
     * @test
     */
    public function getWithNoParams() {

        $base_uri = "api/public/organizations";
        $params = [];

        $this->injectClientFor($base_uri, 'GET', $params);
        $actual = $this->service->request('GET', $base_uri, $params);

        $this->assertEquals(200, $actual->getStatusCode());
    }

    /**
     * get should build a valid url, send a query and
     * return a Guzzle response object
     *
     * @test
     */
    public function getInvalidUri()
    {
        $base_uri = "a very bad uri";
        $params = [];

        $this->injectClientFor($base_uri, 'GET', $params);

        try {
            $res = $this->service->request('GET', $base_uri, $params);
            throw new \AssertionError("An ApiRequestException should have been thrown");
        } catch (ApiRequestException $e) {
            $this->assertEquals('bad prophecy request', $e->getMessage());
        }
    }

    /**
     * getBody should return the response body as a string
     *
     * @test
     */
    public function getBody() {

        $base_uri = "api/public/organizations";
        $params = ['_format' => 'json', 'itemsPerPage' => 10, 'foo' => 1];

        // uri as it is supposed to be processed by the repo
        $processed_uri = $base_uri . "?_format=json&itemsPerPage=10&foo=1";

        $this->injectClientFor($processed_uri, 'GET', $params);
        $actual = $this->service->getBody($base_uri, $params);

        $this->assertEquals('{"@context": "/api/contexts/PortailOrganization"}', $actual);
    }

    /**
     * getBody should return the response body as an array
     *
     * @test
     */
    public function getJsonDecoded() {

        $base_uri = "api/public/organizations";
        $params = ['_format' => 'json', 'itemsPerPage' => 10, 'foo' => 1];

        // uri as it is supposed to be processed by the repo
        $processed_uri = $base_uri . "?_format=json&itemsPerPage=10&foo=1";

        $this->injectClientFor($processed_uri, 'GET', $params);
        $actual = $this->service->getJsonDecoded($base_uri, $params);

        $this->assertEquals(["@context" => "/api/contexts/PortailOrganization"], $actual);
    }

}
