<?php

declare(strict_types=1);

namespace CloudXM\NFe\Services;

use CloudXM\NFe\Persistence\NfeQueueRepository;
use CloudXM\SDK\Support\HasLogger;

/**
 * NFe Queue Service
 *
 * Handles queue task processing, retry strategy, and orchestration
 * 
 * V2 API Compatibility:
 * - Queue payloads are stored as JSON and support v2 data structures
 * - Task payloads may contain v2-specific fields (processing_strategy, municipal_service_code, etc.)
 * - Task results may contain InvoiceResource DTOs serialized as arrays
 * - Logging includes v2 fields when present in task data or results
 * - All existing queue logic (retry, priority, cleanup) remains unchanged
 */
class NFeQueueService
{
    use HasLogger;

    private const MAX_RETRIES = 3;
    private const RETRY_DELAY_SECONDS = 300; // 5 minutes
    private const STALE_TASK_HOURS = 24;

    public function __construct(
        private NfeQueueRepository $queueRepo
    ) {}

    /**
     * Add task to queue
     *
     * @param string $type Task type (emit, cancel, download, etc.)
     * @param int $orderId Related order ID
     * @param array $payload Task payload data (supports v2 data structures)
     * @param int $priority Task priority (higher = more important)
     * @return int|false Queue task ID or false on failure
     */
    public function addTask(string $type, int $orderId, array $payload = [], int $priority = 10): int|false
    {
        $taskData = [
            'type' => $type,
            'order_id' => $orderId,
            'payload' => json_encode($payload),
            'priority' => $priority,
            'status' => 'pending',
            'attempts' => 0,
            'scheduled_at' => current_time('mysql'),
            'created_at' => current_time('mysql')
        ];

        $taskId = $this->queueRepo->add($taskData);

        if ($taskId) {
            // Extract v2 fields from payload for logging if present
            $logContext = [
                'task_id' => $taskId,
                'type' => $type,
                'order_id' => $orderId,
                'priority' => $priority
            ];

            // Include v2-specific fields in log context if present
            if (isset($payload['processing_strategy'])) {
                $logContext['processing_strategy'] = $payload['processing_strategy'];
            }
            if (isset($payload['municipal_service_code'])) {
                $logContext['municipal_service_code'] = $payload['municipal_service_code'];
            }
            if (isset($payload['national_service_code'])) {
                $logContext['national_service_code'] = $payload['national_service_code'];
            }

            $this->log()->info('nfe.queue.task_added', $logContext);
        }

        return $taskId;
    }

    /**
     * Process next pending task
     *
     * @param callable $processor Callback function to process the task
     * @return array|null Processed task data or null if no tasks
     */
    public function processNextTask(callable $processor): ?array
    {
        // Get next pending task ordered by priority and scheduled time
        $tasks = $this->queueRepo->getPendingTasks(1);

        if (empty($tasks)) {
            return null;
        }

        $task = $tasks[0];

        // Mark as processing
        $this->queueRepo->markAsProcessing($task['id']);

        try {
            // Decode payload for logging
            $payload = json_decode($task['payload'], true) ?? [];

            // Build log context with v2 fields if present
            $logContext = [
                'task_id' => $task['id'],
                'type' => $task['type'],
                'order_id' => $task['order_id'],
                'attempt' => $task['attempts'] + 1
            ];

            // Include v2-specific fields in log context if present in payload
            if (isset($payload['processing_strategy'])) {
                $logContext['processing_strategy'] = $payload['processing_strategy'];
            }
            if (isset($payload['invoice_id'])) {
                $logContext['invoice_id'] = $payload['invoice_id'];
            }

            $this->log()->info('nfe.queue.task_processing', $logContext);

            // Call processor callback
            $result = $processor($task);

            // Mark as completed
            $this->queueRepo->markAsCompleted($task['id'], $result);

            // Build completion log context
            $completionContext = [
                'task_id' => $task['id'],
                'result' => $result
            ];

            // If result contains v2 InvoiceResource data, include key fields
            if (is_array($result)) {
                if (isset($result['processing_strategy'])) {
                    $completionContext['processing_strategy'] = $result['processing_strategy'];
                }
                if (isset($result['status'])) {
                    $completionContext['invoice_status'] = $result['status'];
                }
                if (isset($result['allowed_actions'])) {
                    $completionContext['allowed_actions'] = $result['allowed_actions'];
                }
            }

            $this->log()->info('nfe.queue.task_completed', $completionContext);

            return $task;

        } catch (\Exception $e) {
            // Increment attempts
            $this->queueRepo->incrementAttempts($task['id']);

            $this->log()->error('nfe.queue.task_failed', [
                'task_id' => $task['id'],
                'error' => $e->getMessage(),
                'attempts' => $task['attempts'] + 1
            ]);

            // Check if should retry
            if ($task['attempts'] + 1 < self::MAX_RETRIES) {
                // Schedule retry
                $retryAt = date('Y-m-d H:i:s', time() + self::RETRY_DELAY_SECONDS);
                $this->queueRepo->update($task['id'], [
                    'status' => 'pending',
                    'scheduled_at' => $retryAt
                ]);

                $this->log()->info('nfe.queue.task_retry_scheduled', [
                    'task_id' => $task['id'],
                    'retry_at' => $retryAt
                ]);
            } else {
                // Mark as failed
                $this->queueRepo->markAsFailed($task['id'], $e->getMessage());

                $this->log()->error('nfe.queue.task_max_retries_exceeded', [
                    'task_id' => $task['id'],
                    'attempts' => $task['attempts'] + 1
                ]);
            }

            return $task;
        }
    }

    /**
     * Process multiple tasks in batch
     *
     * @param callable $processor Callback function to process each task
     * @param int $limit Maximum number of tasks to process
     * @return array Array of processed tasks
     */
    public function processBatch(callable $processor, int $limit = 10): array
    {
        $processed = [];
        $count = 0;

        while ($count < $limit) {
            $task = $this->processNextTask($processor);
            if (!$task) {
                break;
            }
            $processed[] = $task;
            $count++;
        }

        return $processed;
    }

    /**
     * Get tasks for specific order
     *
     * @param int $orderId Order ID
     * @return array List of tasks
     */
    public function getTasksForOrder(int $orderId): array
    {
        return $this->queueRepo->getTasksByOrderId($orderId);
    }

    /**
     * Get failed tasks for retry
     *
     * @param int $limit Maximum number of tasks
     * @return array List of failed tasks ready for retry
     */
    public function getFailedTasksForRetry(int $limit = 10): array
    {
        return $this->queueRepo->getFailedTasksForRetry($limit);
    }

    /**
     * Clean up old completed tasks
     *
     * @param int $daysOld Delete tasks older than this many days
     * @return int Number of tasks deleted
     */
    public function cleanupOldTasks(int $daysOld = 7): int
    {
        $deleted = $this->queueRepo->deleteOldCompletedTasks($daysOld);

        if ($deleted > 0) {
            $this->log()->info('nfe.queue.cleanup_completed', [
                'deleted_count' => $deleted,
                'days_old' => $daysOld
            ]);
        }

        return $deleted;
    }

    /**
     * Get queue statistics
     *
     * @return array Queue stats
     */
    public function getStatistics(): array
    {
        return $this->queueRepo->getStatistics();
    }

    /**
     * Check if order has pending tasks
     *
     * @param int $orderId Order ID
     * @return bool True if order has pending tasks
     */
    public function hasPendingTasks(int $orderId): bool
    {
        $tasks = $this->queueRepo->getTasksByOrderId($orderId);
        foreach ($tasks as $task) {
            if (in_array($task['status'], ['pending', 'processing'])) {
                return true;
            }
        }
        return false;
    }

    /**
     * Cancel pending tasks for order
     *
     * @param int $orderId Order ID
     * @return int Number of tasks cancelled
     */
    public function cancelTasksForOrder(int $orderId): int
    {
        $tasks = $this->queueRepo->getTasksByOrderId($orderId);
        $cancelled = 0;

        foreach ($tasks as $task) {
            if (in_array($task['status'], ['pending', 'processing'])) {
                $this->queueRepo->update($task['id'], [
                    'status' => 'cancelled',
                    'updated_at' => current_time('mysql')
                ]);
                $cancelled++;

                $this->log()->info('nfe.queue.task_cancelled', [
                    'task_id' => $task['id'],
                    'order_id' => $orderId
                ]);
            }
        }

        return $cancelled;
    }
}
