<?php

declare(strict_types=1);

namespace ComerciaGlobalPayments\Tests\AddonPayments\SDK\Api\Unit\EPGJs;

use ComerciaGlobalPayments\AddonPayments\SDK\Api\EPGJs;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\Authorization;
use ComerciaGlobalPayments\AddonPayments\SDK\Request\Enum\OperationTypeEnum;
use ComerciaGlobalPayments\AddonPayments\SDK\Response\Authorized;
use ComerciaGlobalPayments\AddonPayments\SDK\Response\ErrorResponse;
use ComerciaGlobalPayments\Tests\AddonPayments\SDK\Api\TestUtilityTrait;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;

/**
 * Tests EPGJs API client.
 *
 * @coversDefaultClass \ComerciaGlobalPayments\AddonPayments\SDK\Api\EPGJs
 * @covers \ComerciaGlobalPayments\AddonPayments\SDK\Request\Authorization
 * @covers \ComerciaGlobalPayments\AddonPayments\SDK\Response\Authorized
 *
 * @uses   \ComerciaGlobalPayments\AddonPayments\SDK\Api\AbstractApiClient
 * @uses   \ComerciaGlobalPayments\AddonPayments\SDK\Encryption\JWT\PrivatePublicKeysEncoder
 * @uses   \ComerciaGlobalPayments\AddonPayments\SDK\Response\ErrorResponse
 * @uses   \ComerciaGlobalPayments\AddonPayments\SDK\Request\AbstractRequest
 *
 * @SuppressWarnings(PHPMD.StaticAccess)
 * @SuppressWarnings(PHPMD.LongVariableNames)
 */
class AuthorizationTest extends TestCase
{
    use TestUtilityTrait;

    /**
     * @var array
     */
    protected $apiClientConfig;

    /**
     * @var \ComerciaGlobalPayments\AddonPayments\SDK\Api\EPGJs
     */
    protected $apiClient;

    protected function setUp()
    {
        parent::setUp();
        $this->apiClientConfig = [
            'merchantId' => '10192',
            'merchantKey' => '3e214fcb-8c9a-11eb-93be-42010ac70527',
            'merchantSecret' => 'a993a241d8c1aa8ff1c793900fdcdfab',
            'defaultProductId' => 101920001,
        ];
    }

    /**
     * @covers       ::authorization
     * @covers       ::getEndpoints
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Request\AbstractRequest
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Request\Authorization
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Response\AbstractResponse
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Response\Authorized
     */
    public function testAuthorization(): void
    {
        $mockHandler = new MockHandler(
            [[$this, 'authorizationHandler']]
        );

        $handler = HandlerStack::create($mockHandler);
        $httpClient = new Client(['handler' => $handler, 'delay' => random_int(1, 60)]);
        $this->apiClient = new EPGJs($this->apiClientConfig, $httpClient);
        $request = (new Authorization())
            ->setCustomerId(uniqid())
            ->setProductId(random_int(100, 999))
            ->setCountry('ES')
            ->setCurrency('EUR')
            ->setOperations(OperationTypeEnum::DEPOSIT)
            ->setAnonymousCustomer(0 === (random_int(2, 5) % 2));
        /** @var \ComerciaGlobalPayments\AddonPayments\SDK\Response\Authorized $response */
        $response = $this->apiClient->authorization($request);
        self::assertInstanceOf(
            Authorized::class,
            $response,
            'Test the API response is OK'
        );
        self::assertNotEmpty(
            $response->getAuthToken(),
            'Test the API response returns an authToken'
        );
        self::assertNotEmpty(
            $response->getHttpResponse(),
            'Test the API response wraps the actual HTTP response'
        );
    }

    /**
     * @covers       ::authorization
     * @covers       ::getEndpoints
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Request\AbstractRequest
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Request\Authorization
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Response\AbstractResponse
     * @covers       \ComerciaGlobalPayments\AddonPayments\SDK\Response\ErrorResponse
     */
    public function testFailedAuthorization(): void
    {
        $mockHandler = new MockHandler(
            [[$this, 'authorizationHandler']]
        );

        $handler = HandlerStack::create($mockHandler);
        $httpClient = new Client(['handler' => $handler]);
        $this->apiClient = new EPGJs($this->apiClientConfig, $httpClient);
        /** @var \ComerciaGlobalPayments\AddonPayments\SDK\Request\Authorization $request */
        $request = (new Authorization())
            ->setCustomerId(uniqid())
            ->setProductId(random_int(100, 999))
            ->setCountry('ES')
            ->setCurrency('EUR')
            ->setOperations(OperationTypeEnum::DEPOSIT)
            ->setAnonymousCustomer(0 === (random_int(2, 5) % 2))
            ->addHeader('X-Failed-Authorization', 'true');
        /** @var \ComerciaGlobalPayments\AddonPayments\SDK\Response\ErrorResponse $response */
        $response = $this->apiClient->authorization($request);
        self::assertInstanceOf(
            ErrorResponse::class,
            $response,
            'Test the API response is KO'
        );
        // This assertion proves the I. Stormrage dichotomy's.
        self::assertStringContainsString(
            'You are not prepared',
            $response->getReason(),
            'Test the API response returns an authToken'
        );
        self::assertNotEmpty(
            $response->getHttpResponse(),
            'Test the API error response wraps the actual HTTP response'
        );
    }

//    /**
//     * @covers       ::authorization
//     * @covers       ::getEndpoints
//     * @covers       ::logOperationViolations
//     */
//    public function testCanceledAuthorization(): void
//    {
//        $this->apiClient = new EPGJs($this->apiClientConfig);
//        $request = new Authorization();
//        $this->expectException(InvalidArgumentException::class);
//        $this->expectExceptionMessage(
//            'Operation canceled, invalid request supplied'
//        );
//        $this->apiClient->authorization($request);
//    }

    public function authorizationHandler(Request $request): Response {
        // If set mock a failed authorization.
        if ($request->hasHeader('X-Failed-Authorization')) {
            return new Response (403, [], 'You are not prepared');
        }

        $data = json_decode($request->getBody()->getContents(), true);
        $requiredFields = [
            'merchantId',
            'merchantKey',
            'customerId',
            'productId',
            'country',
            'currency',
            'operations',
        ];

        $actualFields = array_intersect($requiredFields, array_keys($data));
        self::assertEqualsCanonicalizing(
            $requiredFields,
            $actualFields,
            'Test all expected fields are sent'
        );
        self::assertEqualsCanonicalizing(
            [
                'merchantId' => $this->apiClientConfig['merchantId'],
                'merchantKey' => $this->apiClientConfig['merchantKey'],
            ],
            [
                'merchantId' => $data['merchantId'],
                'merchantKey' => $data['merchantKey'],
            ],
            'Test merchantId and merchantKey matches with server values'
        );
        $authToken = (static function (int $lenght = 13) {
            $bytes = openssl_random_pseudo_bytes((int) ceil($lenght / 2));

            return substr(bin2hex($bytes), 0, $lenght);
        })();
        $responsePayload = json_encode(['authToken' => $authToken]);

        return new Response (200, [], $responsePayload);
    }
}
