<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Persistence\InMemory;

use App\Administration\Domain\Model\RefreshToken\RefreshToken;
use App\Administration\Domain\Model\RefreshToken\RefreshTokenId;
use App\Administration\Domain\Model\RefreshToken\TokenFamilyId;
use App\Administration\Domain\Model\User\UserId;
use App\Administration\Domain\Repository\RefreshTokenRepository;

use function array_unique;
use function array_values;
use function count;

use Override;

/**
 * In-memory implementation of RefreshTokenRepository for testing.
 */
final class InMemoryRefreshTokenRepository implements RefreshTokenRepository
{
    /** @var array<string, RefreshToken> Indexed by token ID */
    private array $tokens = [];

    /** @var array<string, list<string>> Maps family ID to token IDs */
    private array $familyIndex = [];

    /** @var array<string, list<string>> Maps user ID to family IDs */
    private array $userIndex = [];

    #[Override]
    public function save(RefreshToken $token): void
    {
        $this->tokens[(string) $token->id] = $token;

        // Index by family
        $familyId = (string) $token->familyId;
        if (!isset($this->familyIndex[$familyId])) {
            $this->familyIndex[$familyId] = [];
        }
        $this->familyIndex[$familyId][] = (string) $token->id;
        $this->familyIndex[$familyId] = array_values(array_unique($this->familyIndex[$familyId]));

        // Index by user
        $userId = (string) $token->userId;
        if (!isset($this->userIndex[$userId])) {
            $this->userIndex[$userId] = [];
        }
        $this->userIndex[$userId][] = $familyId;
        $this->userIndex[$userId] = array_values(array_unique($this->userIndex[$userId]));
    }

    #[Override]
    public function find(RefreshTokenId $id): ?RefreshToken
    {
        return $this->tokens[(string) $id] ?? null;
    }

    #[Override]
    public function findByToken(string $tokenValue): ?RefreshToken
    {
        return $this->find(RefreshTokenId::fromString($tokenValue));
    }

    #[Override]
    public function delete(RefreshTokenId $id): void
    {
        unset($this->tokens[(string) $id]);
    }

    #[Override]
    public function invalidateFamily(TokenFamilyId $familyId): void
    {
        $familyIdStr = (string) $familyId;

        if (!isset($this->familyIndex[$familyIdStr])) {
            return;
        }

        // Delete all tokens in the family
        foreach ($this->familyIndex[$familyIdStr] as $tokenId) {
            unset($this->tokens[$tokenId]);
        }

        // Remove family index
        unset($this->familyIndex[$familyIdStr]);
    }

    #[Override]
    public function invalidateAllForUser(UserId $userId): void
    {
        $userIdStr = (string) $userId;

        if (!isset($this->userIndex[$userIdStr])) {
            return;
        }

        // Invalidate all families for this user
        foreach ($this->userIndex[$userIdStr] as $familyId) {
            $this->invalidateFamily(TokenFamilyId::fromString($familyId));
        }

        // Remove user index
        unset($this->userIndex[$userIdStr]);
    }

    /**
     * Helper method for testing: check if user has any active sessions.
     */
    public function hasActiveSessionsForUser(UserId $userId): bool
    {
        return isset($this->userIndex[(string) $userId]) && count($this->userIndex[(string) $userId]) > 0;
    }
}
