<?php

declare(strict_types=1);

namespace ComerciaGlobalPayments\Tests\AddonPayments\SDK\Encryption;

use ComerciaGlobalPayments\AddonPayments\SDK\Encryption\JWT\PrivatePublicKeysEncoder;
use ComerciaGlobalPayments\AddonPayments\SDK\Exception\BadPermissionsException;
use ComerciaGlobalPayments\AddonPayments\SDK\Exception\InvalidKeyFileException;
use ComerciaGlobalPayments\AddonPayments\SDK\Exception\NotAllowedAlgorithmException;
use DirectoryIterator;
use Firebase\JWT\SignatureInvalidException;
use PHPUnit\Framework\TestCase;
use RuntimeException;

/**
 * Class PPKJWTTest.
 *
 * @coversDefaultClass \ComerciaGlobalPayments\AddonPayments\SDK\Encryption\JWT\PrivatePublicKeysEncoder
 *
 * @uses \ComerciaGlobalPayments\AddonPayments\SDK\Encryption\JWT\PrivatePublicKeysEncoder::allowedAlgorithms
 */
class PrivatePublicKeysEncoderTest extends TestCase
{
    /**
     * @var string
     */
    private static $keysDirectory;

    public static function setUpBeforeClass(): void
    {
        parent::setUpBeforeClass();
        self::$keysDirectory = \dirname(__DIR__, 2).'/fixtures/keys/';
    }

    /**
     * @covers ::__construct
     * @covers ::allowedAlgorithms
     */
    public function testConstructWithGoodKeys(): void
    {
        $instance = new PrivatePublicKeysEncoder(
            self::$keysDirectory.'good.pem',
            self::$keysDirectory.'good.pub.pem'
        );
        self::assertInstanceOf(
            PrivatePublicKeysEncoder::class,
            $instance,
            'Test instance is created with proper keys'
        );
    }

    /**
     * @covers ::__construct
     */
    public function testBadAlgorithm(): void
    {
        $this->expectException(NotAllowedAlgorithmException::class);
        new PrivatePublicKeysEncoder(
            self::$keysDirectory.'good.pem',
            self::$keysDirectory.'good.pub.pem', 'INVALID_ALGO'
        );
    }

    /**
     * @covers ::__construct
     */
    public function testNotExistingPrivateKey(): void
    {
        $this->expectException(InvalidKeyFileException::class);
        new PrivatePublicKeysEncoder(
            self::$keysDirectory.'none.pem',
            self::$keysDirectory.'none.pub.pem'
        );
    }

    /**
     * @covers ::__construct
     */
    public function testNotExistingPublicKey(): void
    {
        $this->expectException(InvalidKeyFileException::class);
        new PrivatePublicKeysEncoder(
            self::$keysDirectory.'good.pem',
            self::$keysDirectory.'none.pub.pem'
        );
    }

    /**
     * @covers ::__construct
     */
    public function testPrivateKeyBadPermissions(): void
    {
        $this->expectException(BadPermissionsException::class);
        new PrivatePublicKeysEncoder(
            self::$keysDirectory.'bad.pem',
            self::$keysDirectory.'good.pub.pem'
        );
    }

    /**
     * @covers ::__construct
     */
    public function testPublicKeyBadPermissions(): void
    {
        $this->expectException(BadPermissionsException::class);
        new PrivatePublicKeysEncoder(
            self::$keysDirectory.'good.pem',
            self::$keysDirectory.'bad.pub.pem'
        );
    }

    /**
     * @covers ::__construct
     * @covers ::encode
     * @covers ::decode
     * @dataProvider jsonDataSamples
     */
    public function testEncryption(array $data): void
    {
        $instance = new PrivatePublicKeysEncoder(
            self::$keysDirectory.'good.pem',
            self::$keysDirectory.'good.pub.pem'
        );
        $encrypted = $instance->encode($data);
        self::assertNotEmpty($encrypted, 'Test encryption returns an output');
        self::assertIsString($encrypted, 'Test data is an encrypted string');
        $decrypted = $instance->decode($encrypted);
        self::assertJsonStringEqualsJsonString(
            json_encode($data),
            json_encode($decrypted),
            'Test decrypted data matches input data'
        );
        $altInstance = new PrivatePublicKeysEncoder(
            self::$keysDirectory.'alt.pem', self::$keysDirectory.'alt.pub.pem'
        );
        // Test we cannot open the encrypted data with another key.
        $this->expectException(SignatureInvalidException::class);
        $altInstance->decode($encrypted);
    }

    public function jsonDataSamples(): array
    {
        $data = [];
        $dataDirectoryPath = \dirname(__DIR__, 2).'/fixtures/data/jwt/';
        $dataDirectory = new DirectoryIterator($dataDirectoryPath);
        while ($dataDirectory->valid()) {
            if ($dataDirectory->isDot() ||
                $dataDirectory->isDir() ||
                !$dataDirectory->isReadable() ||
                'json' !== $dataDirectory->getExtension()) {
                $dataDirectory->next();
                continue;
            }

            $content = file_get_contents($dataDirectory->getRealPath());
            $data[] = json_decode($content, true);
            $dataDirectory->next();
        }

        if (0 === \count($data)) {
            throw new RuntimeException(sprintf("Could not read fixtures data from '%s'", $dataDirectoryPath));
        }

        return $data;
    }
}
