<?php

declare(strict_types=1);

/*
 * This file is part of the Ferienpass package.
 *
 * (c) Richard Henkenjohann <richard@ferienpass.online>
 *
 * For more information visit the project website <https://ferienpass.online>
 * or the documentation under <https://docs.ferienpass.online>.
 */

namespace Ferienpass\CoreBundle\Tests\Entity;

use Ferienpass\CoreBundle\Entity\ApiToken;
use Ferienpass\CoreBundle\Entity\User;
use PHPUnit\Framework\TestCase;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

final class ApiTokenTest extends TestCase
{
    public function testGetLocatorReturnsLocator(): void
    {
        $token = $this->makeToken('mylocator');

        self::assertSame('mylocator', $token->getLocator());
    }

    public function testGetSecretReturnsSecret(): void
    {
        $token = $this->makeToken(secret: 'mysecret');

        self::assertSame('mysecret', $token->getSecret());
    }

    public function testGetScopesReturnsScopes(): void
    {
        $token = $this->makeToken(scopes: ['read', 'write']);

        self::assertSame(['read', 'write'], $token->getScopes());
    }

    public function testGetIdIsNullByDefault(): void
    {
        $token = $this->makeToken();

        self::assertNull($token->getId());
    }

    public function testGetOwnedByReturnsUser(): void
    {
        $user = new User();
        $token = new ApiToken('loc', 'sec', $user, []);

        self::assertSame($user, $token->getOwnedBy());
    }

    public function testGetCreatedAtIsSetOnConstruct(): void
    {
        $token = $this->makeToken();

        self::assertInstanceOf(\DateTimeInterface::class, $token->getCreatedAt());
    }

    public function testGetValidUntilIsNullByDefault(): void
    {
        $token = $this->makeToken();

        self::assertNull($token->getValidUntil());
    }

    public function testIsValidReturnsTrueWhenNoExpiry(): void
    {
        $token = $this->makeToken();

        self::assertTrue($token->isValid());
    }

    public function testIsValidReturnsFalseWhenExpired(): void
    {
        $token = $this->makeToken();
        $prop = new \ReflectionProperty(ApiToken::class, 'validUntil');
        $prop->setValue($token, new \DateTimeImmutable('-1 day'));

        self::assertFalse($token->isValid());
    }

    public function testIsValidReturnsTrueWhenExpiryInFuture(): void
    {
        $token = $this->makeToken();
        $prop = new \ReflectionProperty(ApiToken::class, 'validUntil');
        $prop->setValue($token, new \DateTimeImmutable('+1 day'));

        self::assertTrue($token->isValid());
    }

    public function testFromTokenThrowsWhenMissingPrefix(): void
    {
        $hasher = $this->createStub(UserPasswordHasherInterface::class);
        $user = new User();

        $this->expectException(\InvalidArgumentException::class);
        ApiToken::fromToken('invalid.token', [], $user, $hasher);
    }

    public function testFromTokenThrowsWhenNoDelimiter(): void
    {
        $hasher = $this->createStub(UserPasswordHasherInterface::class);
        $user = new User();

        $this->expectException(\InvalidArgumentException::class);
        ApiToken::fromToken('pat-invalidtoken', [], $user, $hasher);
    }

    public function testFromTokenCreatesTokenWithCorrectLocator(): void
    {
        $hasher = $this->createMock(UserPasswordHasherInterface::class);
        $hasher->method('hashPassword')->willReturn('hashedsecret');

        $user = new User();
        $token = ApiToken::fromToken('pat-locator.secret', ['read'], $user, $hasher);

        self::assertSame('pat-locator', $token->getLocator());
        self::assertSame(['read'], $token->getScopes());
        self::assertSame($user, $token->getOwnedBy());
    }

    private function makeToken(string $locator = 'loc123', string $secret = 'secret456', array $scopes = []): ApiToken
    {
        return new ApiToken($locator, $secret, new User(), $scopes);
    }
}
