<?php

declare(strict_types=1);

namespace App\Framework\Webhook\Processing;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Webhook\ValueObjects\WebhookProvider;
use DateTimeImmutable;

/**
 * Prevents duplicate webhook processing using cache-based idempotency
 * Uses framework's Cache system for persistence and expiration
 */
final readonly class IdempotencyService
{
    private const DEFAULT_TTL_HOURS = 24;
    private const STATUS_PROCESSING = 'processing';
    private const STATUS_PROCESSED = 'processed';
    private const STATUS_FAILED = 'failed';

    public function __construct(
        private Cache $cache,
        private int $ttlHours = self::DEFAULT_TTL_HOURS
    ) {
    }

    /**
     * Check if webhook has already been processed or is currently processing
     */
    public function isDuplicate(string $webhookId, WebhookProvider $provider): bool
    {
        $cacheKey = $this->createCacheKey($webhookId, $provider);
        $cacheItem = $this->cache->get($cacheKey);

        return $cacheItem !== null;
    }

    /**
     * Mark webhook as currently being processed
     * Prevents concurrent processing of the same webhook
     */
    public function markProcessing(string $webhookId, WebhookProvider $provider): void
    {
        $cacheKey = $this->createCacheKey($webhookId, $provider);
        $data = $this->createIdempotencyData(self::STATUS_PROCESSING);

        $cacheItem = CacheItem::forSetting(
            key: $cacheKey,
            value: $data,
            ttl: Duration::fromHours($this->ttlHours)
        );

        $this->cache->set($cacheItem);
    }

    /**
     * Mark webhook as successfully processed
     */
    public function markProcessed(string $webhookId, WebhookProvider $provider, ?array $result = null): void
    {
        $cacheKey = $this->createCacheKey($webhookId, $provider);
        $data = $this->createIdempotencyData(self::STATUS_PROCESSED, null, $result);

        $cacheItem = CacheItem::forSetting(
            key: $cacheKey,
            value: $data,
            ttl: Duration::fromHours($this->ttlHours)
        );

        $this->cache->set($cacheItem);
    }

    /**
     * Mark webhook processing as failed
     */
    public function markFailed(string $webhookId, WebhookProvider $provider, string $error): void
    {
        $cacheKey = $this->createCacheKey($webhookId, $provider);
        $data = $this->createIdempotencyData(self::STATUS_FAILED, $error);

        $cacheItem = CacheItem::forSetting(
            key: $cacheKey,
            value: $data,
            ttl: Duration::fromHours($this->ttlHours)
        );

        $this->cache->set($cacheItem);
    }

    /**
     * Get processing status of webhook
     */
    public function getStatus(string $webhookId, WebhookProvider $provider): ?array
    {
        $cacheKey = $this->createCacheKey($webhookId, $provider);
        $cacheItem = $this->cache->get($cacheKey);

        return $cacheItem?->value;
    }

    /**
     * Check if webhook is currently being processed
     */
    public function isProcessing(string $webhookId, WebhookProvider $provider): bool
    {
        $status = $this->getStatus($webhookId, $provider);

        return $status && $status['status'] === self::STATUS_PROCESSING;
    }

    /**
     * Check if webhook was successfully processed
     */
    public function wasProcessed(string $webhookId, WebhookProvider $provider): bool
    {
        $status = $this->getStatus($webhookId, $provider);

        return $status && $status['status'] === self::STATUS_PROCESSED;
    }

    /**
     * Check if webhook processing failed
     */
    public function hasFailed(string $webhookId, WebhookProvider $provider): bool
    {
        $status = $this->getStatus($webhookId, $provider);

        return $status && $status['status'] === self::STATUS_FAILED;
    }

    /**
     * Remove idempotency record manually (for reprocessing)
     */
    public function reset(string $webhookId, WebhookProvider $provider): bool
    {
        $cacheKey = $this->createCacheKey($webhookId, $provider);

        return $this->cache->forget($cacheKey);
    }

    /**
     * Get all webhook processing records for a provider (useful for debugging)
     */
    public function getProviderStatus(WebhookProvider $provider): array
    {
        // This would require cache tag support or cache iteration
        // For now, individual webhook status is the primary interface
        return [];
    }

    /**
     * Create cache key for webhook idempotency
     */
    private function createCacheKey(string $webhookId, WebhookProvider $provider): CacheKey
    {
        $keyString = "webhook_idempotency:{$provider->toString()}:{$webhookId}";

        return CacheKey::fromString($keyString);
    }

    /**
     * Create idempotency data structure
     */
    private function createIdempotencyData(
        string $status,
        ?string $error = null,
        ?array $result = null
    ): array {
        $data = [
            'status' => $status,
            'timestamp' => (new DateTimeImmutable())->format('c'),
            'ttl_hours' => $this->ttlHours,
        ];

        if ($error !== null) {
            $data['error'] = $error;
        }

        if ($result !== null) {
            $data['result'] = $result;
        }

        return $data;
    }
}
