<?php

declare(strict_types=1);

namespace App\Framework\OAuth\Storage;

use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\OAuth\ValueObjects\OAuthToken;

/**
 * In-Memory OAuth Token Repository
 *
 * Test implementation for OAuth token persistence
 */
final class InMemoryOAuthTokenRepository implements OAuthTokenRepositoryInterface
{
    /** @var array<int, StoredOAuthToken> */
    private array $tokens = [];

    private int $nextId = 1;

    public function findForUser(string $userId, string $provider): ?StoredOAuthToken
    {
        foreach ($this->tokens as $token) {
            if ($token->userId === $userId && $token->provider === $provider) {
                return $token;
            }
        }

        return null;
    }

    public function findById(int $id): ?StoredOAuthToken
    {
        return $this->tokens[$id] ?? null;
    }

    public function save(StoredOAuthToken $storedToken): StoredOAuthToken
    {
        if ($storedToken->id === null) {
            return $this->insert($storedToken);
        }

        return $this->update($storedToken);
    }

    public function saveForUser(
        string $userId,
        string $provider,
        OAuthToken $token
    ): StoredOAuthToken {
        $existing = $this->findForUser($userId, $provider);

        if ($existing !== null) {
            $updated = $existing->withRefreshedToken($token);
            return $this->update($updated);
        }

        $storedToken = StoredOAuthToken::create($userId, $provider, $token);
        return $this->insert($storedToken);
    }

    public function deleteForUser(string $userId, string $provider): bool
    {
        foreach ($this->tokens as $id => $token) {
            if ($token->userId === $userId && $token->provider === $provider) {
                unset($this->tokens[$id]);
                return true;
            }
        }

        return false;
    }

    public function deleteExpiredWithoutRefreshToken(): int
    {
        $deleted = 0;
        $now = Timestamp::now();

        foreach ($this->tokens as $id => $token) {
            if ($token->isExpired() && !$token->canRefresh()) {
                unset($this->tokens[$id]);
                $deleted++;
            }
        }

        return $deleted;
    }

    public function findAllForUser(string $userId): array
    {
        $result = [];

        foreach ($this->tokens as $token) {
            if ($token->userId === $userId) {
                $result[] = $token;
            }
        }

        return $result;
    }

    public function findExpiringSoon(int $withinSeconds = 300): array
    {
        $result = [];
        $now = Timestamp::now();
        $threshold = $now->toTimestamp() + $withinSeconds;

        foreach ($this->tokens as $token) {
            $expiresAt = $token->token->accessToken->getExpiresAt()->toTimestamp();

            if ($expiresAt < $threshold && $token->canRefresh()) {
                $result[] = $token;
            }
        }

        return $result;
    }

    /**
     * Clear all tokens (test utility)
     */
    public function clear(): void
    {
        $this->tokens = [];
        $this->nextId = 1;
    }

    /**
     * Get all tokens (test utility)
     *
     * @return array<StoredOAuthToken>
     */
    public function all(): array
    {
        return array_values($this->tokens);
    }

    /**
     * Insert new token
     */
    private function insert(StoredOAuthToken $storedToken): StoredOAuthToken
    {
        $id = $this->nextId++;

        $persisted = new StoredOAuthToken(
            id: $id,
            userId: $storedToken->userId,
            provider: $storedToken->provider,
            token: $storedToken->token,
            createdAt: $storedToken->createdAt,
            updatedAt: $storedToken->updatedAt,
        );

        $this->tokens[$id] = $persisted;

        return $persisted;
    }

    /**
     * Update existing token
     */
    private function update(StoredOAuthToken $storedToken): StoredOAuthToken
    {
        if ($storedToken->id === null) {
            throw new \InvalidArgumentException('Cannot update token without ID');
        }

        if (!isset($this->tokens[$storedToken->id])) {
            throw new \InvalidArgumentException('Token not found: ' . $storedToken->id);
        }

        // Create updated token with new timestamp
        $updated = new StoredOAuthToken(
            id: $storedToken->id,
            userId: $storedToken->userId,
            provider: $storedToken->provider,
            token: $storedToken->token,
            createdAt: $storedToken->createdAt,
            updatedAt: Timestamp::now(),
        );

        $this->tokens[$storedToken->id] = $updated;

        return $updated;
    }
}
