<?php

declare(strict_types=1);

namespace App\Framework\Queue\Services;

use App\Framework\Queue\Contracts\DeadLetterQueueInterface;
use App\Framework\Queue\Entities\DeadLetterJob;
use App\Framework\Queue\Entities\JobIndexEntry;
use App\Framework\Queue\ProductionJobPersistenceLayer;
use App\Framework\Queue\ValueObjects\DeadLetterQueueName;
use App\Framework\Queue\ValueObjects\FailureReason;
use App\Framework\Queue\ValueObjects\QueueName;

/**
 * Manages dead letter queue operations and job failure handling
 */
final readonly class DeadLetterManager
{
    public function __construct(
        private DeadLetterQueueInterface $deadLetterQueue,
        private ProductionJobPersistenceLayer $persistenceLayer
    ) {
    }

    /**
     * Move a failed job to the dead letter queue
     */
    public function moveJobToDeadLetterQueue(
        JobIndexEntry $failedJob,
        FailureReason $failureReason,
        ?DeadLetterQueueName $deadLetterQueueName = null
    ): void {
        // Use default dead letter queue name if not provided
        $dlqName = $deadLetterQueueName ?? DeadLetterQueueName::forQueue(
            QueueName::fromString($failedJob->queueType)
        );

        // Create dead letter job entry
        $deadLetterJob = DeadLetterJob::fromFailedJob(
            failedJob: $failedJob,
            deadLetterQueueName: $dlqName,
            failureReason: $failureReason
        );

        // Add to dead letter queue
        $this->deadLetterQueue->addFailedJob($deadLetterJob);

        // Remove from original job index
        $this->persistenceLayer->deleteJob($failedJob->jobId);
    }

    /**
     * Move a job to dead letter queue when max attempts are exceeded
     */
    public function handleJobFailure(
        string $jobId,
        \Throwable $exception,
        int $maxAttempts = 3
    ): bool {
        $jobEntry = $this->persistenceLayer->getJobById($jobId);
        if (! $jobEntry) {
            return false;
        }

        // Check if max attempts exceeded
        if ($jobEntry->attempts >= $maxAttempts) {
            $failureReason = FailureReason::fromException($exception);

            $this->moveJobToDeadLetterQueue(
                failedJob: $jobEntry,
                failureReason: $failureReason
            );

            return true;
        }

        return false;
    }

    /**
     * Retry a job from dead letter queue
     */
    public function retryJob(string $deadLetterJobId): bool
    {
        return $this->deadLetterQueue->retryJob($deadLetterJobId);
    }

    /**
     * Get failed jobs for monitoring/admin interface
     */
    public function getFailedJobs(
        ?QueueName $originalQueue = null,
        int $limit = 100
    ): array {
        if ($originalQueue) {
            return $this->deadLetterQueue->getJobsByOriginalQueue($originalQueue, $limit);
        }

        // Get jobs from all dead letter queues
        $allJobs = [];
        $queues = $this->deadLetterQueue->getAvailableQueues();

        foreach ($queues as $queueName) {
            $jobs = $this->deadLetterQueue->getJobs($queueName, $limit);
            $allJobs = array_merge($allJobs, $jobs);
        }

        // Sort by moved_to_dlq_at descending
        usort($allJobs, fn ($a, $b) => strcmp($b->movedToDlqAt, $a->movedToDlqAt));

        return array_slice($allJobs, 0, $limit);
    }

    /**
     * Get dead letter queue statistics
     */
    public function getStatistics(): array
    {
        $queues = $this->deadLetterQueue->getAvailableQueues();
        $stats = [];

        foreach ($queues as $queueName) {
            $stats[$queueName->toString()] = $this->deadLetterQueue->getQueueStats($queueName);
        }

        return $stats;
    }

    /**
     * Clear all jobs from a dead letter queue
     */
    public function clearDeadLetterQueue(DeadLetterQueueName $queueName): int
    {
        return $this->deadLetterQueue->clearQueue($queueName);
    }

    /**
     * Retry all jobs in a dead letter queue
     */
    public function retryAllJobs(DeadLetterQueueName $queueName): int
    {
        return $this->deadLetterQueue->retryAllJobs($queueName);
    }

    /**
     * Delete a specific job from dead letter queue
     */
    public function deleteJob(string $deadLetterJobId): bool
    {
        return $this->deadLetterQueue->deleteJob($deadLetterJobId);
    }

    /**
     * Get all available dead letter queues
     */
    public function getAvailableQueues(): array
    {
        return $this->deadLetterQueue->getAvailableQueues();
    }
}
