<?php

declare(strict_types=1);

use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\OAuth\Storage\StoredOAuthToken;
use App\Framework\OAuth\ValueObjects\AccessToken;
use App\Framework\OAuth\ValueObjects\OAuthToken;
use App\Framework\OAuth\ValueObjects\RefreshToken;
use App\Framework\OAuth\ValueObjects\TokenType;

describe('StoredOAuthToken Entity', function () {
    it('creates new stored token', function () {
        $accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
        $oauthToken = new OAuthToken($accessToken, null, TokenType::BEARER, null);

        $stored = StoredOAuthToken::create('user-123', 'spotify', $oauthToken);

        expect($stored->id)->toBeNull(); // Not persisted yet
        expect($stored->userId)->toBe('user-123');
        expect($stored->provider)->toBe('spotify');
        expect($stored->token)->toBe($oauthToken);
    });

    it('sets timestamps on creation', function () {
        $accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
        $oauthToken = new OAuthToken($accessToken, null, TokenType::BEARER, null);

        $beforeCreate = Timestamp::now();
        $stored = StoredOAuthToken::create('user-123', 'spotify', $oauthToken);
        $afterCreate = Timestamp::now();

        expect($stored->createdAt->toTimestamp())->toBeGreaterThanOrEqual($beforeCreate->toTimestamp());
        expect($stored->createdAt->toTimestamp())->toBeLessThanOrEqual($afterCreate->toTimestamp());
        expect($stored->updatedAt->toTimestamp())->toBe($stored->createdAt->toTimestamp());
    });

    it('creates from database row', function () {
        $now = Timestamp::now();
        $row = [
            'id' => 1,
            'user_id' => 'user-456',
            'provider' => 'github',
            'access_token' => 'access_1234567890',
            'refresh_token' => 'refresh_1234567890',
            'token_type' => 'Bearer',
            'expires_at' => $now->add(Duration::fromSeconds(3600))->format('Y-m-d H:i:s'),
            'scope' => 'read write',
            'created_at' => $now->format('Y-m-d H:i:s'),
            'updated_at' => $now->format('Y-m-d H:i:s'),
        ];

        $stored = StoredOAuthToken::fromArray($row);

        expect($stored->id)->toBe(1);
        expect($stored->userId)->toBe('user-456');
        expect($stored->provider)->toBe('github');
        expect($stored->token->accessToken->toString())->toBe('access_1234567890');
        expect($stored->token->refreshToken->toString())->toBe('refresh_1234567890');
    });

    it('updates token immutably', function () {
        $originalAccessToken = AccessToken::create('original_access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
        $originalToken = new OAuthToken($originalAccessToken, null, TokenType::BEARER, null);
        $originalStored = StoredOAuthToken::create('user-123', 'spotify', $originalToken);

        $newAccessToken = AccessToken::create('new_access_1234567890', Timestamp::now()->add(Duration::fromSeconds(7200)));
        $newToken = new OAuthToken($newAccessToken, null, TokenType::BEARER, null);
        $updatedStored = $originalStored->withRefreshedToken($newToken);

        // Original unchanged
        expect($originalStored->token->accessToken->toString())->toBe('original_access_1234567890');
        expect($originalStored->userId)->toBe('user-123');

        // New instance updated
        expect($updatedStored->token->accessToken->toString())->toBe('new_access_1234567890');
        expect($updatedStored->userId)->toBe('user-123');
        expect($updatedStored->provider)->toBe('spotify');
    });

    it('updates updatedAt timestamp on token refresh', function () {
        $originalAccessToken = AccessToken::create('original_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
        $originalToken = new OAuthToken($originalAccessToken, null, TokenType::BEARER, null);
        $originalStored = StoredOAuthToken::create('user-123', 'spotify', $originalToken);

        // Wait a full second to ensure different timestamp
        sleep(1);

        $newAccessToken = AccessToken::create('new_1234567890', Timestamp::now()->add(Duration::fromSeconds(7200)));
        $newToken = new OAuthToken($newAccessToken, null, TokenType::BEARER, null);
        $updatedStored = $originalStored->withRefreshedToken($newToken);

        expect($updatedStored->updatedAt->toTimestamp())
            ->toBeGreaterThanOrEqual($originalStored->updatedAt->toTimestamp() + 1);
        expect($updatedStored->createdAt->toTimestamp())
            ->toBe($originalStored->createdAt->toTimestamp());
    });

    it('preserves id during token refresh', function () {
        $accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
        $oauthToken = new OAuthToken($accessToken, null, TokenType::BEARER, null);
        $stored = new StoredOAuthToken(
            id: 42,
            userId: 'user-123',
            provider: 'spotify',
            token: $oauthToken,
            createdAt: Timestamp::now(),
            updatedAt: Timestamp::now()
        );

        $newAccessToken = AccessToken::create('new_1234567890', Timestamp::now()->add(Duration::fromSeconds(7200)));
        $newToken = new OAuthToken($newAccessToken, null, TokenType::BEARER, null);
        $updated = $stored->withRefreshedToken($newToken);

        expect($updated->id)->toBe(42);
    });

    it('detects expired token', function () {
        $expiredAccessToken = AccessToken::create('expired_1234567890', Timestamp::now()->subtract(Duration::fromSeconds(100)));
        $expiredToken = new OAuthToken($expiredAccessToken, null, TokenType::BEARER, null);
        $stored = StoredOAuthToken::create('user-123', 'spotify', $expiredToken);

        expect($stored->isExpired())->toBeTrue();
    });

    it('detects valid non-expired token', function () {
        $validAccessToken = AccessToken::create('valid_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
        $validToken = new OAuthToken($validAccessToken, null, TokenType::BEARER, null);
        $stored = StoredOAuthToken::create('user-123', 'spotify', $validToken);

        expect($stored->isExpired())->toBeFalse();
    });

    it('checks if token can refresh', function () {
        $accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
        $refreshToken = RefreshToken::create('refresh_1234567890');
        $oauthToken = new OAuthToken($accessToken, $refreshToken, TokenType::BEARER, null);
        $stored = StoredOAuthToken::create('user-123', 'spotify', $oauthToken);

        expect($stored->canRefresh())->toBeTrue();
    });

    it('checks if token cannot refresh without refresh token', function () {
        $accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
        $oauthToken = new OAuthToken($accessToken, null, TokenType::BEARER, null);
        $stored = StoredOAuthToken::create('user-123', 'spotify', $oauthToken);

        expect($stored->canRefresh())->toBeFalse();
    });

    it('converts to array for database storage', function () {
        $accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
        $refreshToken = RefreshToken::create('refresh_1234567890');
        $oauthToken = new OAuthToken($accessToken, $refreshToken, TokenType::BEARER, null);
        $stored = new StoredOAuthToken(
            id: 1,
            userId: 'user-123',
            provider: 'spotify',
            token: $oauthToken,
            createdAt: Timestamp::now(),
            updatedAt: Timestamp::now()
        );

        $array = $stored->toArray();

        expect($array['id'])->toBe(1);
        expect($array['user_id'])->toBe('user-123');
        expect($array['provider'])->toBe('spotify');
        expect($array['access_token'])->toBe('access_1234567890');
        expect($array['refresh_token'])->toBe('refresh_1234567890');
        expect($array['token_type'])->toBe('Bearer');
        expect($array)->toHaveKey('expires_at');
        expect($array)->toHaveKey('created_at');
        expect($array)->toHaveKey('updated_at');
    });

    it('handles null id in array conversion', function () {
        $accessToken = AccessToken::create('access_1234567890', Timestamp::now()->add(Duration::fromSeconds(3600)));
        $oauthToken = new OAuthToken($accessToken, null, TokenType::BEARER, null);
        $stored = StoredOAuthToken::create('user-123', 'spotify', $oauthToken);

        $array = $stored->toArray();

        expect(array_key_exists('id', $array))->toBeFalse();
    });
});
