<?php

namespace App\Framework\Smartlinks\Services;

use App\Framework\Cache\Cache;
use App\Framework\DateTime\Clock;
use App\Framework\Smartlinks\SmartlinkData;
use App\Framework\Smartlinks\SmartLinkToken;
use App\Framework\Smartlinks\TokenAction;
use App\Framework\Smartlinks\TokenConfig;
use App\Framework\Ulid\UlidGenerator;
use DateTimeImmutable;

final readonly class CacheSmartLinkService implements SmartlinkService
{
    public function __construct(
        private Cache $cache,
        private UlidGenerator $ulidGenerator,
        private Clock $clock
    ) {}

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

        $smartlinkData = new SmartlinkData(
            id: $id,
            action: $action,
            payload: $payload,
            expiresAt: $config->getExpiryDateTime(),
            createdAt: $now,
            oneTimeUse: $config->oneTimeUse,
            createdByIp: $createdByIp,
            userAgent: $userAgent
        );

        $token = new SmartlinkToken($id);

        // In Cache speichern
        $this->cache->set(
            "smartlink:{$id}",
            $this->serializeData($smartlinkData),
            $config->expiryHours * 3600
        );

        return $token;
    }

    public function validate(SmartlinkToken $token): ?SmartlinkData
    {
        $data = $this->cache->get("smartlink:{$token->value}");

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

        $smartlinkData = $this->deserializeData($data->value);

        return $smartlinkData->isValid() ? $smartlinkData : null;
    }

    public function markAsUsed(SmartlinkToken $token): void
    {
        $data = $this->validate($token);
        if ($data) {
            $updatedData = $data->withUsed(new DateTimeImmutable());
            $this->cache->set(
                "smartlink:{$token->value}",
                $this->serializeData($updatedData),
                $data->getRemainingTime()->h * 3600
            );
        }
    }

    public function revoke(SmartlinkToken $token): void
    {
        $this->cache->forget("smartlink:{$token->value}");
    }

    public function exists(SmartlinkToken $token): bool
    {
        return $this->cache->has("smartlink:{$token->value}");
    }

    public function getActiveTokens(int $limit = 100): array
    {
        // Cache-Keys durchsuchen
        $pattern = "smartlink:*";
        $keys = $this->cache->getKeys($pattern) ?? [];

        $tokens = [];
        foreach (array_slice($keys, 0, $limit) as $key) {
            $data = $this->cache->get($key);
            if ($data->isHit) {
                $smartlinkData = $this->deserializeData($data->value);;
                if ($smartlinkData->isValid()) {
                    $tokens[] = $smartlinkData;
                }
            }
        }

        return $tokens;
    }

    public function cleanupExpired(): int
    {
        // Bei Cache-basierter Implementation werden expired Items automatisch entfernt
        return 0;
    }

    private function serializeData(SmartlinkData $data): array
    {
        return [
            'id' => $data->id,
            'action' => $data->action->name,
            'payload' => $data->payload,
            'expires_at' => $data->expiresAt->format('Y-m-d H:i:s'),
            'created_at' => $data->createdAt->format('Y-m-d H:i:s'),
            'one_time_use' => $data->oneTimeUse,
            'created_by_ip' => $data->createdByIp,
            'user_agent' => $data->userAgent,
            'is_used' => $data->isUsed,
            'used_at' => $data->usedAt?->format('Y-m-d H:i:s')
        ];
    }

    private function deserializeData(array $data): SmartlinkData
    {
        return new SmartlinkData(
            id: $data['id'],
            action: new TokenAction($data['action']),
            payload: $data['payload'],
            expiresAt: new DateTimeImmutable($data['expires_at']),
            createdAt: new DateTimeImmutable($data['created_at']),
            oneTimeUse: $data['one_time_use'],
            createdByIp: $data['created_by_ip'],
            userAgent: $data['user_agent'],
            isUsed: $data['is_used'],
            usedAt: $data['used_at'] ? new DateTimeImmutable($data['used_at']) : null
        );
    }
}
