<?php

declare(strict_types=1);

namespace App\Administration\Infrastructure\Persistence\Redis;

use App\Administration\Domain\Exception\ActivationTokenNotFoundException;
use App\Administration\Domain\Model\ActivationToken\ActivationToken;
use App\Administration\Domain\Model\ActivationToken\ActivationTokenId;
use App\Administration\Domain\Repository\ActivationTokenRepository;
use App\Shared\Domain\Tenant\TenantId;
use DateTimeImmutable;
use Override;
use Psr\Cache\CacheItemPoolInterface;

final readonly class RedisActivationTokenRepository implements ActivationTokenRepository
{
    private const string KEY_PREFIX = 'activation:';
    private const int TTL_SECONDS = 7 * 24 * 60 * 60; // 7 days

    public function __construct(
        private CacheItemPoolInterface $activationTokensCache,
    ) {
    }

    #[Override]
    public function save(ActivationToken $token): void
    {
        // Store by token value for lookup during activation
        $item = $this->activationTokensCache->getItem(self::KEY_PREFIX . $token->tokenValue);
        $item->set($this->serialize($token));
        $item->expiresAfter(self::TTL_SECONDS);
        $this->activationTokensCache->save($item);

        // Also store by ID for direct access
        $idItem = $this->activationTokensCache->getItem(self::KEY_PREFIX . 'id:' . $token->id);
        $idItem->set($token->tokenValue);
        $idItem->expiresAfter(self::TTL_SECONDS);
        $this->activationTokensCache->save($idItem);
    }

    #[Override]
    public function findByTokenValue(string $tokenValue): ?ActivationToken
    {
        $item = $this->activationTokensCache->getItem(self::KEY_PREFIX . $tokenValue);

        if (!$item->isHit()) {
            return null;
        }

        /** @var array{id: string, token_value: string, user_id: string, email: string, tenant_id: string, role: string, school_name: string, created_at: string, expires_at: string, used_at: string|null, student_id?: string|null, relationship_type?: string|null} $data */
        $data = $item->get();

        return $this->deserialize($data);
    }

    #[Override]
    public function get(ActivationTokenId $id): ActivationToken
    {
        // First get the token value from the ID index
        $idItem = $this->activationTokensCache->getItem(self::KEY_PREFIX . 'id:' . $id);

        if (!$idItem->isHit()) {
            throw ActivationTokenNotFoundException::withId($id);
        }

        /** @var string $tokenValue */
        $tokenValue = $idItem->get();
        $token = $this->findByTokenValue($tokenValue);

        if ($token === null) {
            throw ActivationTokenNotFoundException::withId($id);
        }

        return $token;
    }

    #[Override]
    public function delete(ActivationTokenId $id): void
    {
        // Get token value first
        $idItem = $this->activationTokensCache->getItem(self::KEY_PREFIX . 'id:' . $id);

        if ($idItem->isHit()) {
            /** @var string $tokenValue */
            $tokenValue = $idItem->get();
            $this->activationTokensCache->deleteItem(self::KEY_PREFIX . $tokenValue);
        }

        $this->activationTokensCache->deleteItem(self::KEY_PREFIX . 'id:' . $id);
    }

    #[Override]
    public function deleteByTokenValue(string $tokenValue): void
    {
        $token = $this->findByTokenValue($tokenValue);

        if ($token !== null) {
            $this->activationTokensCache->deleteItem(self::KEY_PREFIX . 'id:' . $token->id);
        }

        $this->activationTokensCache->deleteItem(self::KEY_PREFIX . $tokenValue);
    }

    /**
     * @return array{id: string, token_value: string, user_id: string, email: string, tenant_id: string, role: string, school_name: string, created_at: string, expires_at: string, used_at: string|null, student_id: string|null, relationship_type: string|null}
     */
    private function serialize(ActivationToken $token): array
    {
        return [
            'id' => (string) $token->id,
            'token_value' => $token->tokenValue,
            'user_id' => $token->userId,
            'email' => $token->email,
            'tenant_id' => (string) $token->tenantId,
            'role' => $token->role,
            'school_name' => $token->schoolName,
            'created_at' => $token->createdAt->format(DateTimeImmutable::ATOM),
            'expires_at' => $token->expiresAt->format(DateTimeImmutable::ATOM),
            'used_at' => $token->usedAt?->format(DateTimeImmutable::ATOM),
            'student_id' => $token->studentId,
            'relationship_type' => $token->relationshipType,
        ];
    }

    /**
     * @param array{id: string, token_value: string, user_id: string, email: string, tenant_id: string, role: string, school_name: string, created_at: string, expires_at: string, used_at: string|null, student_id?: string|null, relationship_type?: string|null} $data
     */
    private function deserialize(array $data): ActivationToken
    {
        return ActivationToken::reconstitute(
            id: ActivationTokenId::fromString($data['id']),
            tokenValue: $data['token_value'],
            userId: $data['user_id'],
            email: $data['email'],
            tenantId: TenantId::fromString($data['tenant_id']),
            role: $data['role'],
            schoolName: $data['school_name'],
            createdAt: new DateTimeImmutable($data['created_at']),
            expiresAt: new DateTimeImmutable($data['expires_at']),
            usedAt: $data['used_at'] !== null ? new DateTimeImmutable($data['used_at']) : null,
            studentId: $data['student_id'] ?? null,
            relationshipType: $data['relationship_type'] ?? null,
        );
    }
}
