<?php

declare(strict_types=1);

namespace App\Framework\Queue\Wrappers;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Queue\Queue;
use App\Framework\Queue\RetryStrategyHelper;
use App\Framework\Queue\ValueObjects\JobMetadata;
use App\Framework\Queue\ValueObjects\JobPayload;
use App\Framework\Queue\ValueObjects\QueuePriority;
use App\Framework\Retry\RetryStrategy;

/**
 * Event Queue Wrapper
 *
 * Specialized queue wrapper for event processing with type-safe operations
 * Optimized for domain events, system events, and integration events
 */
final readonly class EventQueue
{
    public function __construct(
        private Queue $queue
    ) {
    }

    /**
     * Push any object as an event
     *
     * @param object $event Any event object to be processed
     * @param QueuePriority|null $priority Event priority (defaults to normal)
     * @param Duration|null $delay Delay before processing (defaults to immediate)
     * @param Duration|null $timeout Maximum processing time
     * @param RetryStrategy|null $retryStrategy Custom retry strategy
     */
    public function pushEvent(
        object $event,
        ?QueuePriority $priority = null,
        ?Duration $delay = null,
        ?Duration $timeout = null,
        ?RetryStrategy $retryStrategy = null
    ): void {
        $payload = JobPayload::create(
            job: $event,
            priority: $priority ?? QueuePriority::normal(),
            delay: $delay ?? Duration::zero(),
            timeout: $timeout ?? Duration::fromMinutes(2),
            retryStrategy: $retryStrategy ?? RetryStrategyHelper::forEvents(),
            metadata: JobMetadata::forEvent($event)
        );

        $this->queue->push($payload);
    }

    /**
     * Push a domain event (high priority for business logic)
     */
    public function pushDomainEvent(object $event): void
    {
        $this->pushEvent(
            event: $event,
            priority: QueuePriority::high(),
            timeout: Duration::fromMinutes(1),
            retryStrategy: RetryStrategyHelper::forEvents()
        );
    }

    /**
     * Push a system event (normal priority for system operations)
     */
    public function pushSystemEvent(object $event): void
    {
        $this->pushEvent(
            event: $event,
            priority: QueuePriority::normal(),
            timeout: Duration::fromMinutes(3),
            retryStrategy: RetryStrategyHelper::forEvents()
        );
    }

    /**
     * Push an integration event (low priority, high retry tolerance)
     */
    public function pushIntegrationEvent(object $event): void
    {
        $this->pushEvent(
            event: $event,
            priority: QueuePriority::low(),
            timeout: Duration::fromMinutes(10),
            retryStrategy: RetryStrategyHelper::forWebhooks()
        );
    }

    /**
     * Push a notification event (background processing)
     */
    public function pushNotificationEvent(object $event): void
    {
        $this->pushEvent(
            event: $event,
            priority: QueuePriority::low(),
            timeout: Duration::fromMinutes(5),
            retryStrategy: RetryStrategyHelper::forEmails()
        );
    }

    /**
     * Push a delayed event (scheduled for future processing)
     */
    public function pushDelayedEvent(object $event, Duration $delay): void
    {
        $this->pushEvent(
            event: $event,
            delay: $delay,
            priority: QueuePriority::normal(),
            timeout: Duration::fromMinutes(2)
        );
    }

    /**
     * Push a critical event (immediate processing, no retries)
     */
    public function pushCriticalEvent(object $event): void
    {
        $this->pushEvent(
            event: $event,
            priority: QueuePriority::critical(),
            timeout: Duration::fromSeconds(30),
            retryStrategy: RetryStrategyHelper::none()
        );
    }

    /**
     * Pop the next event from the queue
     *
     * @return object|null The next event object or null if queue is empty
     */
    public function popEvent(): ?object
    {
        $payload = $this->queue->pop();

        return $payload?->job;
    }

    /**
     * Peek at the next event without removing it
     *
     * @return object|null The next event object or null if queue is empty
     */
    public function peekEvent(): ?object
    {
        $payload = $this->queue->peek();

        return $payload?->job;
    }

    /**
     * Pop the next event with its metadata
     *
     * @return JobPayload|null The complete job payload or null if queue is empty
     */
    public function popEventWithMetadata(): ?JobPayload
    {
        return $this->queue->pop();
    }

    /**
     * Get number of pending events
     */
    public function size(): int
    {
        return $this->queue->size();
    }

    /**
     * Check if queue is empty
     */
    public function isEmpty(): bool
    {
        return $this->size() === 0;
    }

    /**
     * Clear all events from queue
     *
     * @return int Number of events removed
     */
    public function clear(): int
    {
        return $this->queue->clear();
    }

    /**
     * Get event queue statistics
     */
    public function getStats(): array
    {
        $stats = $this->queue->getStats();

        return [
            'type' => 'event',
            'size' => $this->size(),
            'is_empty' => $this->isEmpty(),
            ...$stats,
        ];
    }

    /**
     * Batch push multiple events
     *
     * @param array<object> $events Array of event objects
     * @param QueuePriority|null $priority Priority for all events
     */
    public function pushBatch(array $events, ?QueuePriority $priority = null): void
    {
        foreach ($events as $event) {
            $this->pushEvent($event, $priority);
        }
    }

    /**
     * Push events with different priorities based on event type
     *
     * @param array<object> $events Array of event objects
     */
    public function pushSmartBatch(array $events): void
    {
        foreach ($events as $event) {
            $this->pushEventByType($event);
        }
    }

    /**
     * Pop multiple events in batch
     *
     * @param int $count Maximum number of events to pop
     * @return array<object> Array of event objects
     */
    public function popBatch(int $count): array
    {
        $events = [];

        for ($i = 0; $i < $count; $i++) {
            $event = $this->popEvent();

            if ($event === null) {
                break;
            }

            $events[] = $event;
        }

        return $events;
    }

    /**
     * Filter events by type/class
     *
     * @param string $eventClass The event class to filter by
     * @return array<object> Events of the specified type
     */
    public function getEventsByType(string $eventClass): array
    {
        $events = [];
        $tempEvents = [];

        // Pop all events temporarily
        while (! $this->isEmpty()) {
            $payload = $this->popEventWithMetadata();
            if ($payload === null) {
                break;
            }

            if ($payload->job instanceof $eventClass) {
                $events[] = $payload->job;
            } else {
                $tempEvents[] = $payload;
            }
        }

        // Push back non-matching events
        foreach ($tempEvents as $payload) {
            $this->queue->push($payload);
        }

        return $events;
    }

    /**
     * Get events count by priority
     *
     * @return array<string, int> Priority name => count mapping
     */
    public function getEventCountByPriority(): array
    {
        $stats = $this->getStats();

        return $stats['priority_breakdown'] ?? [];
    }

    /**
     * Push event with automatic priority based on event type
     */
    private function pushEventByType(object $event): void
    {
        $className = get_class($event);

        // Automatic priority assignment based on event name patterns
        if (str_contains($className, 'Domain') || str_contains($className, 'Business')) {
            $this->pushDomainEvent($event);
        } elseif (str_contains($className, 'System') || str_contains($className, 'Application')) {
            $this->pushSystemEvent($event);
        } elseif (str_contains($className, 'Integration') || str_contains($className, 'External')) {
            $this->pushIntegrationEvent($event);
        } elseif (str_contains($className, 'Notification') || str_contains($className, 'Email')) {
            $this->pushNotificationEvent($event);
        } elseif (str_contains($className, 'Critical') || str_contains($className, 'Alert')) {
            $this->pushCriticalEvent($event);
        } else {
            // Default to system event
            $this->pushSystemEvent($event);
        }
    }
}
