<?php

declare(strict_types=1);

namespace App\Framework\Security\RequestSigning;

use App\Framework\Cache\Cache;
use App\Framework\Database\EntityManager;
use App\Framework\DateTime\Clock;

/**
 * Entity Manager based implementation of signing key repository
 */
final class EntityManagerSigningKeyRepository implements SigningKeyRepository
{
    private const CACHE_PREFIX = 'signing_key:';
    private const CACHE_TTL = 300; // 5 minutes

    public function __construct(
        private readonly EntityManager $entityManager,
        private readonly Clock $clock,
        private readonly Cache $cache,
    ) {
    }

    public function findByKeyId(string $keyId): ?SigningKey
    {
        // Try cache first
        $cached = $this->cache->get(self::CACHE_PREFIX . $keyId);
        if ($cached instanceof SigningKey) {
            return $cached;
        }

        $key = $this->entityManager->find(SigningKey::class, $keyId);

        // Cache the result if found
        if ($key !== null) {
            $this->cache->set(self::CACHE_PREFIX . $keyId, $key, self::CACHE_TTL);
        }

        return $key;
    }

    public function store(SigningKey $key): void
    {
        // Set timestamps if not set
        $now = $this->clock->now();

        if ($key->createdAt === null || $key->updatedAt === null) {
            $key = new SigningKey(
                keyId: $key->keyId,
                keyMaterial: $key->keyMaterial,
                algorithm: $key->algorithm,
                expiresAt: $key->expiresAt,
                isActive: $key->isActive,
                createdAt: $key->createdAt ?? $now,
                updatedAt: $now,
            );
        }

        $this->entityManager->persist($key);
        $this->entityManager->flush();

        // Invalidate cache
        $this->cache->delete(self::CACHE_PREFIX . $key->keyId);
    }

    public function remove(string $keyId): void
    {
        $key = $this->findByKeyId($keyId);
        if ($key !== null) {
            $this->entityManager->remove($key);
            $this->entityManager->flush();
        }

        // Invalidate cache
        $this->cache->delete(self::CACHE_PREFIX . $keyId);
    }

    public function getAllActive(): array
    {
        $now = $this->clock->now();

        // Use EntityManager's query capabilities
        $query = $this->entityManager->createQuery(
            "SELECT k FROM " . SigningKey::class . " k 
             WHERE k.isActive = true 
             AND (k.expiresAt IS NULL OR k.expiresAt > :now)
             ORDER BY k.createdAt DESC"
        );
        $query->setParameter('now', $now);

        return $query->getResult();
    }

    public function rotateKey(string $keyId, SigningKey $newKey): void
    {
        $this->entityManager->beginTransaction();

        try {
            // Deactivate old key
            $oldKey = $this->findByKeyId($keyId);
            if ($oldKey !== null) {
                $deactivatedKey = new SigningKey(
                    keyId: $oldKey->keyId,
                    keyMaterial: $oldKey->keyMaterial,
                    algorithm: $oldKey->algorithm,
                    expiresAt: $oldKey->expiresAt,
                    isActive: false,
                    createdAt: $oldKey->createdAt,
                    updatedAt: $this->clock->now(),
                );

                $this->entityManager->persist($deactivatedKey);
            }

            // Store new key
            $this->store($newKey);

            $this->entityManager->commit();

            // Invalidate cache for old key
            $this->cache->delete(self::CACHE_PREFIX . $keyId);
        } catch (\Exception $e) {
            $this->entityManager->rollback();

            throw $e;
        }
    }

    /**
     * Get keys expiring within the specified time window
     */
    public function getExpiringKeys(int $withinSeconds = 86400): array
    {
        $now = $this->clock->now();
        $expiryThreshold = $now->modify("+{$withinSeconds} seconds");

        $query = $this->entityManager->createQuery(
            "SELECT k FROM " . SigningKey::class . " k 
             WHERE k.isActive = true 
             AND k.expiresAt IS NOT NULL 
             AND k.expiresAt > :now 
             AND k.expiresAt <= :threshold
             ORDER BY k.expiresAt ASC"
        );
        $query->setParameter('now', $now);
        $query->setParameter('threshold', $expiryThreshold);

        return $query->getResult();
    }

    /**
     * Clean up expired keys
     */
    public function cleanupExpired(): int
    {
        $now = $this->clock->now();

        $query = $this->entityManager->createQuery(
            "DELETE FROM " . SigningKey::class . " k 
             WHERE k.expiresAt IS NOT NULL 
             AND k.expiresAt < :now"
        );
        $query->setParameter('now', $now);

        return $query->execute();
    }
}
