<?php

declare(strict_types=1);

namespace NoahVet\Reef\Test\A_Unit\Security\IAM\TokenExchange;

use NoahVet\Reef\Factory\HttpClientFactoryInterface;
use NoahVet\Reef\Security\IAM\TokenExchange\CachedTokenExchangerDecorator;
use NoahVet\Reef\Security\IAM\TokenExchange\TokenExchangerInterface;
use NoahVet\Reef\Security\Token\JWTDecoderInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;

class CachedTokenExchangerDecoratorTest extends TestCase
{
    /**
     * @var CacheItemPoolInterface&MockObject
     */
    private CacheItemPoolInterface $cache;

    /**
     * @var TokenExchangerInterface&MockObject
     */
    private TokenExchangerInterface $wrappedTokenExchanged;

    /**
     * @var JWTDecoderInterface&MockObject
     */
    private JWTDecoderInterface $jwtDecoder;

    private CachedTokenExchangerDecorator $tokenExchanger;

    protected function setUp(): void
    {
        $this->cache = $this->createMock(CacheItemPoolInterface::class);
        $this->httpClientFactory = $this->createMock(HttpClientFactoryInterface::class);
        $this->jwtDecoder = $this->createMock(JWTDecoderInterface::class);
        $this->wrappedTokenExchanged = $this->createMock(TokenExchangerInterface::class);
        $this->tokenExchanger = new CachedTokenExchangerDecorator(
            $this->cache,
            $this->jwtDecoder,
            'testClientId',
            $this->wrappedTokenExchanged,
        );
    }

    public function testTokenIsReturnedFromCacheWhenAvailable(): void
    {
        $bearerToken = 'testBearerToken';
        $cacheKey = 'reef_token_exchanged_testClientId_'.$bearerToken;

        $cacheItem = $this->createMock(CacheItemInterface::class);
        $cacheItem->method('isHit')->willReturn(true);
        $cacheItem->method('get')->willReturn('testExchangedToken');

        $this->cache->method('getItem')->with($cacheKey)->willReturn($cacheItem);

        $result = $this->tokenExchanger->exchangeTokenForCurrentClient($bearerToken);

        $this->assertSame('testExchangedToken', $result);
    }

    public function testTokenIsFetchedAndCachedWhenNotInCache(): void
    {
        $bearerToken = 'testBearerToken';
        $cacheKey = 'reef_token_exchanged_testClientId_'.$bearerToken;

        $cacheItem = $this->createMock(CacheItemInterface::class);
        $cacheItem->method('isHit')->willReturn(false);

        $this->cache->method('getItem')->with($cacheKey)->willReturn($cacheItem);

        $this->wrappedTokenExchanged
            ->expects($this->once())
            ->method('exchangeTokenForCurrentClient')
            ->with($bearerToken)
            ->willReturn('newAccessToken')
        ;

        $this->jwtDecoder
            ->method('decodeJWT')
            ->with('newAccessToken')
            ->willReturn((object) ['exp' => (new \DateTime('+1 hour'))->getTimestamp()],
            )
        ;

        $cacheItem->expects($this->once())->method('set')->with('newAccessToken');
        $cacheItem->expects($this->once())->method('expiresAt');
        $this->cache->expects($this->once())->method('save')->with($cacheItem);
        $cacheItem->expects($this->once())->method('get')->willReturn('newAccessToken');

        $result = $this->tokenExchanger->exchangeTokenForCurrentClient($bearerToken);

        $this->assertSame('newAccessToken', $result);
    }
}
