<?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;

/**
 * Command Queue Wrapper
 *
 * Specialized queue wrapper for command processing with type-safe operations
 */
final readonly class CommandQueue
{
    public function __construct(
        private Queue $queue
    ) {
    }

    /**
     * Push any object as a command
     *
     * @param object $command Any object to be processed as a command
     * @param QueuePriority|null $priority Command 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 pushCommand(
        object $command,
        ?QueuePriority $priority = null,
        ?Duration $delay = null,
        ?Duration $timeout = null,
        ?RetryStrategy $retryStrategy = null
    ): void {
        $payload = JobPayload::create(
            job: $command,
            priority: $priority ?? QueuePriority::normal(),
            delay: $delay ?? Duration::zero(),
            timeout: $timeout ?? Duration::fromSeconds(30),
            retryStrategy: $retryStrategy ?? RetryStrategyHelper::forCommands(),
            metadata: JobMetadata::forCommand($command)
        );

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

    /**
     * Push a high priority command (jumps ahead in queue)
     */
    public function pushHighPriorityCommand(object $command): void
    {
        $this->pushCommand(
            command: $command,
            priority: QueuePriority::high(),
            timeout: Duration::fromSeconds(15) // Shorter timeout for high priority
        );
    }

    /**
     * Push a critical command (highest priority, immediate processing)
     */
    public function pushCriticalCommand(object $command): void
    {
        $this->pushCommand(
            command: $command,
            priority: QueuePriority::critical(),
            timeout: Duration::fromSeconds(10),
            retryStrategy: RetryStrategyHelper::none() // No retries for critical
        );
    }

    /**
     * Push a background command (low priority, longer timeout)
     */
    public function pushBackgroundCommand(object $command): void
    {
        $this->pushCommand(
            command: $command,
            priority: QueuePriority::low(),
            timeout: Duration::fromMinutes(5),
            retryStrategy: RetryStrategyHelper::forGeneral()
        );
    }

    /**
     * Push a delayed command (scheduled for future processing)
     */
    public function pushDelayedCommand(object $command, Duration $delay): void
    {
        $this->pushCommand(
            command: $command,
            delay: $delay,
            priority: QueuePriority::normal()
        );
    }

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

        return $payload?->job;
    }

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

        return $payload?->job;
    }

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

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

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

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

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

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

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

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

        for ($i = 0; $i < $count; $i++) {
            $command = $this->popCommand();

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

            $commands[] = $command;
        }

        return $commands;
    }
}
