<?php

declare(strict_types=1);

namespace App\Framework\MagicLinks\Services;

use App\Framework\DateTime\Clock;
use App\Framework\MagicLinks\MagicLinkData;
use App\Framework\MagicLinks\MagicLinkToken;
use App\Framework\MagicLinks\TokenAction;
use App\Framework\MagicLinks\TokenConfig;
use App\Framework\MagicLinks\ValueObjects\MagicLinkPayload;
use App\Framework\Ulid\UlidGenerator;

final class InMemoryMagicLinkService implements MagicLinkService
{
    private array $tokens = [];

    public function __construct(
        private readonly UlidGenerator $ulidGenerator,
        private readonly Clock $clock
    ) {
    }

    public function generate(
        TokenAction $action,
        array                   $payload,
        ?TokenConfig            $config = null,
        ?string                 $createdByIp = null,
        ?string                 $userAgent = null
    ): MagicLinkToken {
        $config ??= new TokenConfig();
        $id = $this->ulidGenerator->generate($this->clock);
        ;
        $now = new \DateTimeImmutable();

        $magiclinkData = new MagicLinkData(
            id: $id,
            action: $action,
            payload: MagicLinkPayload::fromArray($payload),
            expiresAt: $config->getExpiryDateTime(),
            createdAt: $now,
            oneTimeUse: $config->oneTimeUse,
            createdByIp: $createdByIp,
            userAgent: $userAgent
        );

        $token = new MagicLinkToken($id);
        $this->tokens[$id] = $magiclinkData;

        return $token;
    }

    public function validate(MagicLinkToken $token): ?MagicLinkData
    {
        if (! isset($this->tokens[$token->value])) {
            return null;
        }

        $data = $this->tokens[$token->value];

        if (! $data->isValid()) {
            return null;
        }

        return $data;
    }

    public function markAsUsed(MagicLinkToken $token): void
    {
        if (isset($this->tokens[$token->value])) {
            $data = $this->tokens[$token->value];
            $this->tokens[$token->value] = $data->withUsed(new \DateTimeImmutable());
        }
    }

    public function revoke(MagicLinkToken $token): void
    {
        unset($this->tokens[$token->value]);
    }

    public function exists(MagicLinkToken $token): bool
    {
        return isset($this->tokens[$token->value]) &&
            $this->tokens[$token->value]->isValid();
    }

    public function getActiveTokens(int $limit = 100): array
    {
        return array_slice(
            array_filter($this->tokens, fn ($data) => $data->isValid()),
            0,
            $limit
        );
    }

    public function cleanupExpired(): int
    {
        $count = 0;
        foreach ($this->tokens as $id => $data) {
            if ($data->isExpired()) {
                unset($this->tokens[$id]);
                $count++;
            }
        }

        return $count;
    }
}
