<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\DocStoreBundle\Tests\Serializer\Normalizer;

use Chill\DocStoreBundle\AsyncUpload\SignedUrl;
use Chill\DocStoreBundle\AsyncUpload\TempUrlGeneratorInterface;
use Chill\DocStoreBundle\Entity\StoredObject;
use Chill\DocStoreBundle\Security\Authorization\StoredObjectRoleEnum;
use Chill\DocStoreBundle\Security\Guard\JWTDavTokenProviderInterface;
use Chill\DocStoreBundle\Serializer\Normalizer\StoredObjectNormalizer;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

/**
 * @internal
 *
 * @coversNothing
 */
class StoredObjectNormalizerTest extends TestCase
{
    public function testNormalize(): void
    {
        $storedObject = new StoredObject();
        $storedObject->setTitle('test');
        $reflection = new \ReflectionClass(StoredObject::class);
        $idProperty = $reflection->getProperty('id');
        $idProperty->setValue($storedObject, 1);

        $jwtProvider = $this->createMock(JWTDavTokenProviderInterface::class);
        $jwtProvider->expects($this->once())->method('createToken')->withAnyParameters()->willReturn('token');
        $jwtProvider->expects($this->once())->method('getTokenExpiration')->with('token')->willReturn($d = new \DateTimeImmutable());

        $urlGenerator = $this->createMock(UrlGeneratorInterface::class);
        $urlGenerator->expects($this->once())->method('generate')
            ->with(
                'chill_docstore_dav_document_get',
                [
                    'uuid' => $storedObject->getUuid(),
                    'access_token' => 'token',
                ],
                UrlGeneratorInterface::ABSOLUTE_URL,
            )
            ->willReturn($davLink = 'http://localhost/dav/token');

        $security = $this->createMock(Security::class);
        $security->expects($this->exactly(2))->method('isGranted')
            ->with(
                $this->logicalOr(StoredObjectRoleEnum::EDIT->value, StoredObjectRoleEnum::SEE->value),
                $storedObject
            )
            ->willReturn(true);

        $globalNormalizer = $this->createMock(NormalizerInterface::class);
        $globalNormalizer->expects($this->exactly(3))->method('normalize')
            ->withAnyParameters()
            ->willReturnCallback(function (?object $object, string $format, array $context) {
                if (null === $object) {
                    return null;
                }

                return ['sub' => 'sub'];
            });

        $tempUrlGenerator = $this->createMock(TempUrlGeneratorInterface::class);

        $normalizer = new StoredObjectNormalizer($jwtProvider, $urlGenerator, $security, $tempUrlGenerator);
        $normalizer->setNormalizer($globalNormalizer);

        $actual = $normalizer->normalize($storedObject, 'json');

        self::assertArrayHasKey('id', $actual);
        self::assertEquals(1, $actual['id']);
        self::assertArrayHasKey('title', $actual);
        self::assertEquals('test', $actual['title']);
        self::assertArrayHasKey('uuid', $actual);
        self::assertArrayHasKey('prefix', $actual);
        self::assertArrayHaskey('status', $actual);
        self::assertArrayHasKey('currentVersion', $actual);
        self::assertEquals(null, $actual['currentVersion']);
        self::assertArrayHasKey('totalVersions', $actual);
        self::assertEquals(0, $actual['totalVersions']);
        self::assertArrayHasKey('datas', $actual);
        self::assertArrayHasKey('createdAt', $actual);
        self::assertArrayHasKey('createdBy', $actual);
        self::assertArrayHasKey('_permissions', $actual);
        self::assertEqualsCanonicalizing(['canEdit' => true, 'canSee' => true], $actual['_permissions']);
        self::assertArrayHaskey('_links', $actual);
        self::assertArrayHasKey('dav_link', $actual['_links']);
        self::assertEqualsCanonicalizing(['href' => $davLink, 'expiration' => $d->getTimestamp()], $actual['_links']['dav_link']);
    }

    public function testWithDownloadLinkOnly(): void
    {
        $storedObject = new StoredObject();
        $storedObject->registerVersion();
        $storedObject->setTitle('test');
        $reflection = new \ReflectionClass(StoredObject::class);
        $idProperty = $reflection->getProperty('id');
        $idProperty->setValue($storedObject, 1);

        $jwtProvider = $this->createMock(JWTDavTokenProviderInterface::class);
        $jwtProvider->expects($this->never())->method('createToken')->withAnyParameters();

        $urlGenerator = $this->createMock(UrlGeneratorInterface::class);
        $urlGenerator->expects($this->never())->method('generate');

        $security = $this->createMock(Security::class);
        $security->expects($this->never())->method('isGranted');

        $globalNormalizer = $this->createMock(NormalizerInterface::class);
        $globalNormalizer->expects($this->exactly(4))->method('normalize')
            ->withAnyParameters()
            ->willReturnCallback(function (?object $object, string $format, array $context) {
                if (null === $object) {
                    return null;
                }

                return ['sub' => 'sub'];
            });

        $tempUrlGenerator = $this->createMock(TempUrlGeneratorInterface::class);
        $tempUrlGenerator->expects($this->once())->method('generate')->with('GET', $storedObject->getCurrentVersion()->getFilename(), $this->isType('int'))
            ->willReturn(new SignedUrl('GET', 'https://some-link/test', new \DateTimeImmutable('300 seconds'), $storedObject->getCurrentVersion()->getFilename()));

        $normalizer = new StoredObjectNormalizer($jwtProvider, $urlGenerator, $security, $tempUrlGenerator);
        $normalizer->setNormalizer($globalNormalizer);

        $actual = $normalizer->normalize($storedObject, 'json', ['groups' => ['read', 'read:download-link-only']]);

        self::assertIsArray($actual);
        self::assertArrayHasKey('_links', $actual);
        self::assertArrayHasKey('downloadLink', $actual['_links']);
        self::assertEquals(['sub' => 'sub'], $actual['_links']['downloadLink']);
    }
}
