<?php

declare(strict_types=1);

namespace NoahVet\Reef\Test\A_Unit\Security\Token;

use Firebase\JWT\JWK;
use NoahVet\Reef\Exception\NotJWTException;
use NoahVet\Reef\Security\IAM\IAMPublicKeyProvider;
use NoahVet\Reef\Security\Token\JWTDecoder;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class JWTDecoderTest extends TestCase
{
    /**
     * @var IAMPublicKeyProvider&MockObject
     */
    protected IAMPublicKeyProvider $iamPublicKeyProvider;

    private JWTDecoder $decoder;

    protected function setUp(): void
    {
        $this->iamPublicKeyProvider = $this->createMock(IAMPublicKeyProvider::class);

        $this->decoder = new JWTDecoder($this->iamPublicKeyProvider);
    }

    public function testDecodeOk(): void
    {
        $jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjFlZTU1ZmU1LTZjMTUtNjZmMi04YmY3LWE5YmY'
            .'4MGVkZmVjOSJ9.eyJpc3MiOiJodHRwczovL3JlZWYtaWFtLnRyYWVmaWsubWV0IiwiYXVkIjoicmVlZjpic21BcGkiLCJp'
            .'YXQiOjE2OTUwNDQzMzIuNzcwMDE2LCJleHAiOjIwMTA0MDQzMzIsInN1YiI6IjkyYzI0ZjU3LWYzMzktNDZkNC1iYmE2LTJ'
            .'jZjY5NjkwYjFhMCIsInNjb3BlIjoiand0IHJlZWY6cmFiYml0bXEucmVhZDpyZWVmLyovcmVlZjpic21BcGkgcmVlZjpyYW'
            .'JiaXRtcS53cml0ZTpyZWVmLyovcmVlZjpic21BcGkgcmVlZjpyYWJiaXRtcS5jb25maWd1cmU6cmVlZi8qL3JlZWY6YnNtQ'
            .'XBpIiwiZW1haWwiOm51bGwsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfQ.o74ayQtVt6yeQs_mJ4yRnxdffqgjXTCa3KeXmrzAP'
            .'xUl4e5VC4B60WE6-BXDK9v071ejappJakyEiow33nDdlQ';

        $this->iamPublicKeyProvider
            ->expects($this->once())
            ->method('getIAMPublicKeys')
            ->willReturn(
                JWK::parseKeySet(['keys' => [
                    [
                        'crv' => 'P-256',
                        'kty' => 'EC',
                        'alg' => 'ES256',
                        'x' => '9KskQVLe6AjTBguliNKDQ61XMwbJ70bJ5kPpTK91kqs',
                        'y' => 'ECqtOuj6t9iOvGjzpcxNtzIrysijP88YJNFOH_nVLJg',
                        'use' => 'sig',
                        'kid' => '1ee55fe5-6c15-66f2-8bf7-a9bf80edfec9',
                    ],
                ]]),
            )
        ;

        $decodedJwt = $this->decoder->decodeJWT($jwt);

        self::assertIsObject($decodedJwt);
        self::assertEquals('https://reef-iam.traefik.met', $decodedJwt->iss);
        self::assertEquals('reef:bsmApi', $decodedJwt->aud);
        self::assertEquals('reef:bsmApi', $decodedJwt->aud);
        self::assertEquals(1695044332.770016, $decodedJwt->iat);
        self::assertEquals(2010404332, $decodedJwt->exp);
        self::assertEquals('92c24f57-f339-46d4-bba6-2cf69690b1a0', $decodedJwt->sub);
        self::assertEquals(
            'jwt reef:rabbitmq.read:reef/*/reef:bsmApi reef:rabbitmq.write:reef/*/reef:bsmApi reef:rabbitmq.configure:reef/*/reef:bsmApi',
            $decodedJwt->scope,
        );
        self::assertNull($decodedJwt->email);
        self::assertTrue($decodedJwt->email_verified);
    }

    public function testDecodeExpired(): void
    {
        $jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjFlZTU3OWI5LWIwZjAtNmNmMi05YWU2LWFmYjkwZDAyNTBlZCJ9'
            .'.eyJpc3MiOiJodHRwczovL2F1dGgucHJlcHJvZC52ZXRpbndlYi5jb20iLCJhdWQiOiI1bzhxczg4aHpnZzBnc3dnOHNzZ2NnO'
            .'HNzc3dzY2dnZ2cwc2MwY3djb3N3a2M0MDAwZyIsImlhdCI6MTc1NDUyMDI1Ni40MjcwNjksImV4cCI6MTc1NDYwNjY1Niwic3V'
            .'iIjoiMWVmYTMzNTgtYjA5OS02M2Y0LWIxNjctZTE2NDk1ZWQ0YTY0IiwiYXpwIjpbIjVvOHFzODhoemdnMGdzd2c4c3NnY2c4c'
            .'3Nzd3NjZ2dnZzBzYzBjd2Nvc3drYzQwMDBnIl0sInNjb3BlIjoib3BlbmlkIG9mZmxpbmVfYWNjZXNzIiwiZW1haWwiOiJsLmJ'
            .'hcmJlcm90QHZldGlud2ViLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfQ.tM87Hy-19aMwGzlpkqJPrBIu-s8b3KYo854bI2'.
            'alB5bfsZUGT6rCO03W5sjD1rxcwcNcAlmmtqs9YTlIxNj5DQ';

        $this->iamPublicKeyProvider
            ->expects($this->once())
            ->method('getIAMPublicKeys')
            ->willReturn(
                JWK::parseKeySet(['keys' => [
                    [
                        'crv' => 'P-256',
                        'kty' => 'EC',
                        'alg' => 'ES256',
                        'x' => 'KLEmqOq0_6id0I1XZ_gq9x1HPpGvwGel_7UIrHsn0ck',
                        'y' => 'eAqKAUk-GeFRE9cNwW9bJFtMJt6p9exibdQhEYx7LH8',
                        'use' => 'sig',
                        'kid' => '1ee579b9-b0f0-6cf2-9ae6-afb90d0250ed',
                    ],
                ]]),
            )
        ;

        $this->assertNull($this->decoder->decodeJWT($jwt));
    }

    public function testDecodeInvalidKey(): void
    {
        $jwt = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsImtpZCI6IjFlZTU1ZmU1LTZjMTUtNjZmMi04YmY3LWE5YmY'
            .'4MGVkZmVjOSJ9.eyJpc3MiOiJodHRwczovL3JlZWYtaWFtLnRyYWVmaWsubWV0IiwiYXVkIjoicmVlZjpic21BcGkiLCJp'
            .'YXQiOjE2OTUwNDQzMzIuNzcwMDE2LCJleHAiOjIwMTA0MDQzMzIsInN1YiI6IjkyYzI0ZjU3LWYzMzktNDZkNC1iYmE2LTJ'
            .'jZjY5NjkwYjFhMCIsInNjb3BlIjoiand0IHJlZWY6cmFiYml0bXEucmVhZDpyZWVmLyovcmVlZjpic21BcGkgcmVlZjpyYW'
            .'JiaXRtcS53cml0ZTpyZWVmLyovcmVlZjpic21BcGkgcmVlZjpyYWJiaXRtcS5jb25maWd1cmU6cmVlZi8qL3JlZWY6YnNtQ'
            .'XBpIiwiZW1haWwiOm51bGwsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfQ.o74ayQtVt6yeQs_mJ4yRnxdffqgjXTCa3KeXmrzAP'
            .'xUl4e5VC4B60WE6-BXDK9v071ejappJakyEiow33nDdlQ';

        $this->iamPublicKeyProvider
            ->expects($this->once())
            ->method('getIAMPublicKeys')
            ->willReturn(
                JWK::parseKeySet(['keys' => [
                    [
                        'crv' => 'P-256',
                        'kty' => 'EC',
                        'alg' => 'ES256',
                        'x' => '9KskQVLe6AjTBguliNKDQ61XMwbJ70bJ5kPpTK91kqs',
                        'y' => 'ECqtOuj6t9iOvGjzpcxNtzIrysijP88YJNFOH_nVLJg',
                        'use' => 'sig',
                        'kid' => '2ee55fe5-6c15-66f2-8bf7-a9bf80edfec8',
                    ],
                ]]),
            )
        ;

        $this->expectException(NotJWTException::class);

        $this->decoder->decodeJWT($jwt);
    }

    public function testDecodeInvalidToken(): void
    {
        $this->expectException(NotJWTException::class);

        $this->decoder->decodeJWT('invalid_jwt');
    }
}
