<?php

declare(strict_types=1);

namespace App\Framework\WebPush\Repository;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\WebPush\ValueObjects\PushSubscription;

/**
 * Cache-based Subscription Repository.
 */
final readonly class CacheSubscriptionRepository implements SubscriptionRepository
{
    private const string PREFIX = 'web_push_subscription';
    private const string USER_PREFIX = 'web_push_user';
    private const string ALL_KEY = 'web_push_all';

    public function __construct(
        private Cache $cache
    ) {
    }

    public function save(PushSubscription $subscription): bool
    {
        $hash = $subscription->getHash();
        $key = CacheKey::fromString(self::PREFIX . "_{$hash}");

        // Save subscription data
        $item = CacheItem::forSet($key, $subscription->toArray(), Duration::fromDays(30));
        $saved = $this->cache->set($item);

        if (! $saved) {
            return false;
        }

        // Update user index
        if ($subscription->userId !== null) {
            $this->addToUserIndex($subscription->userId, $hash);
        }

        // Update all subscriptions index
        $this->addToAllIndex($hash);

        return true;
    }

    public function findByHash(string $hash): ?PushSubscription
    {
        $key = CacheKey::fromString(self::PREFIX . "_{$hash}");
        $result = $this->cache->get($key);

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

        return PushSubscription::fromDatabaseRow($result->value);
    }

    public function findByUserId(string $userId): array
    {
        $indexKey = CacheKey::fromString(self::USER_PREFIX . "_{$userId}");
        $result = $this->cache->get($indexKey);
        $hashes = $result->isHit ? $result->value : [];

        $subscriptions = [];

        foreach ($hashes as $hash) {
            $subscription = $this->findByHash($hash);

            if ($subscription !== null) {
                $subscriptions[] = $subscription;
            }
        }

        return $subscriptions;
    }

    public function findAll(): array
    {
        $allKey = CacheKey::fromString(self::ALL_KEY);
        $result = $this->cache->get($allKey);
        $hashes = $result->isHit ? $result->value : [];

        $subscriptions = [];

        foreach ($hashes as $hash) {
            $subscription = $this->findByHash($hash);

            if ($subscription !== null && ! $subscription->isExpired()) {
                $subscriptions[] = $subscription;
            }
        }

        return $subscriptions;
    }

    public function delete(string $hash): bool
    {
        $subscription = $this->findByHash($hash);

        if ($subscription === null) {
            return false;
        }

        // Delete subscription data
        $key = CacheKey::fromString(self::PREFIX . "_{$hash}");
        $this->cache->forget($key);

        // Update user index
        if ($subscription->userId !== null) {
            $this->removeFromUserIndex($subscription->userId, $hash);
        }

        // Update all index
        $this->removeFromAllIndex($hash);

        return true;
    }

    public function deleteByUserId(string $userId): int
    {
        $subscriptions = $this->findByUserId($userId);
        $count = 0;

        foreach ($subscriptions as $subscription) {
            if ($this->delete($subscription->getHash())) {
                $count++;
            }
        }

        return $count;
    }

    public function deleteExpired(): int
    {
        $subscriptions = $this->findAll();
        $count = 0;

        foreach ($subscriptions as $subscription) {
            if ($subscription->isExpired()) {
                if ($this->delete($subscription->getHash())) {
                    $count++;
                }
            }
        }

        return $count;
    }

    public function exists(string $hash): bool
    {
        $key = CacheKey::fromString(self::PREFIX . "_{$hash}");

        return $this->cache->has($key);
    }

    /**
     * Fügt Hash zum User Index hinzu.
     */
    private function addToUserIndex(string $userId, string $hash): void
    {
        $key = CacheKey::fromString(self::USER_PREFIX . "_{$userId}");
        $result = $this->cache->get($key);
        $hashes = $result->isHit ? $result->value : [];

        if (! in_array($hash, $hashes, true)) {
            $hashes[] = $hash;
            $item = CacheItem::forSet($key, $hashes, Duration::fromDays(30));
            $this->cache->set($item);
        }
    }

    /**
     * Entfernt Hash vom User Index.
     */
    private function removeFromUserIndex(string $userId, string $hash): void
    {
        $key = CacheKey::fromString(self::USER_PREFIX . "_{$userId}");
        $result = $this->cache->get($key);
        $hashes = $result->isHit ? $result->value : [];

        $hashes = array_filter($hashes, fn ($h) => $h !== $hash);
        $item = CacheItem::forSet($key, array_values($hashes), Duration::fromDays(30));
        $this->cache->set($item);
    }

    /**
     * Fügt Hash zum All Index hinzu.
     */
    private function addToAllIndex(string $hash): void
    {
        $key = CacheKey::fromString(self::ALL_KEY);
        $result = $this->cache->get($key);
        $hashes = $result->isHit ? $result->value : [];

        if (! in_array($hash, $hashes, true)) {
            $hashes[] = $hash;
            $item = CacheItem::forSet($key, $hashes, Duration::fromDays(30));
            $this->cache->set($item);
        }
    }

    /**
     * Entfernt Hash vom All Index.
     */
    private function removeFromAllIndex(string $hash): void
    {
        $key = CacheKey::fromString(self::ALL_KEY);
        $result = $this->cache->get($key);
        $hashes = $result->isHit ? $result->value : [];

        $hashes = array_filter($hashes, fn ($h) => $h !== $hash);
        $item = CacheItem::forSet($key, array_values($hashes), Duration::fromDays(30));
        $this->cache->set($item);
    }
}
