<?php

namespace App\Jobs;

use App\Models\Paper;
use App\Services\ExamPdfExportService;
use App\Services\PaperPayloadService;
use App\Services\QuestionBankService;
use App\Services\TaskManager;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
use Throwable;

class GenerateExamPdfJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public string $taskId;

    public string $paperId;

    public int $tries = 3;

    public int $timeout = 300;

    public function __construct(string $taskId, string $paperId)
    {
        $this->taskId = $taskId;
        $this->paperId = $paperId;

        // 指定使用 pdf 队列，由独立的 pdf-worker 容器处理
        $this->onQueue('pdf');
        // 避免事务未提交时 worker 提前消费导致“试卷不存在”
        $this->afterCommit();
    }

    public function handle(
        ExamPdfExportService $pdfExportService,
        QuestionBankService $questionBankService,
        PaperPayloadService $paperPayloadService,
        TaskManager $taskManager
    ): void {
        $jobStartedAt = microtime(true);
        $taskCreatedAt = null;
        $queueWaitMs = null;

        $taskSnapshot = $taskManager->getTaskStatus($this->taskId);
        if (is_array($taskSnapshot) && !empty($taskSnapshot['created_at'])) {
            try {
                $taskCreatedAt = \Illuminate\Support\Carbon::parse($taskSnapshot['created_at']);
                $queueWaitMs = $taskCreatedAt
                    ? now()->diffInMilliseconds($taskCreatedAt, false) * -1
                    : null;
            } catch (\Throwable $e) {
                $taskCreatedAt = null;
                $queueWaitMs = null;
            }
        }

        try {
            Log::info('开始处理PDF生成队列任务', [
                'task_id' => $this->taskId,
                'paper_id' => $this->paperId,
                'attempt' => $this->attempts(),
                'queue_wait_ms' => $queueWaitMs,
            ]);
            $taskManager->updateTaskStatus($this->taskId, [
                'queue_wait_ms' => $queueWaitMs,
            ]);

            // 【修复】首先检查试卷是否存在
            // 强制走主库读取，避免读写分离下新建试卷短时不可见导致“试卷不存在”
            $paperModel = Paper::query()
                ->useWritePdo()
                ->with('questions')
                ->find($this->paperId);
            if (! $paperModel) {
                Log::error('PDF生成队列任务失败：试卷不存在', [
                    'task_id' => $this->taskId,
                    'paper_id' => $this->paperId,
                    'attempt' => $this->attempts(),
                ]);

                // 如果试卷不存在，判断是否需要重试
                if ($this->attempts() < $this->tries) {
                    Log::info('试卷不存在，将在2秒后重试', [
                        'task_id' => $this->taskId,
                        'paper_id' => $this->paperId,
                        'attempt' => $this->attempts(),
                        'next_attempt' => $this->attempts() + 1,
                    ]);
                    // 延迟2秒后重试（缩短间隔，减少对回调的影响）
                    $this->release(2);

                    return;
                } else {
                    Log::error('试卷不存在且已达到最大重试次数，标记任务失败', [
                        'task_id' => $this->taskId,
                        'paper_id' => $this->paperId,
                        'attempts' => $this->attempts(),
                    ]);
                    $taskManager->markTaskFailed($this->taskId, "试卷不存在: {$this->paperId}");

                    return;
                }
            }

            // 检查试卷是否有题目
            if ($paperModel->questions->isEmpty()) {
                Log::error('PDF生成队列任务失败：试卷没有题目数据', [
                    'task_id' => $this->taskId,
                    'paper_id' => $this->paperId,
                    'question_count' => 0,
                ]);

                if ($this->attempts() < $this->tries) {
                    Log::info('试卷没有题目，将在1秒后重试', [
                        'task_id' => $this->taskId,
                        'paper_id' => $this->paperId,
                        'attempt' => $this->attempts(),
                    ]);
                    // 延迟1秒后重试（更短间隔）
                    $this->release(1);

                    return;
                } else {
                    $taskManager->markTaskFailed($this->taskId, "试卷没有题目数据: {$this->paperId}");

                    return;
                }
            }

            $taskManager->updateTaskProgress($this->taskId, 10, '开始生成统一PDF（直接合并两个页面，效率最高）...');

            // 根据 config 或 env 配置决定是否包含知识点讲解
            // 还需要判断如果摸底（paper_type =0）的时候也是不需要插入知识点讲解内容
            $includeKpExplain = null;

            if($paperModel->paper_type === 0) {
                $includeKpExplain = false;
            }
            info("includekpexplain", [$includeKpExplain, $paperModel->paper_type]);
            $unifiedPdfUrl = $pdfExportService->generateUnifiedPdf($this->paperId, $includeKpExplain);

            $taskManager->updateTaskProgress($this->taskId, 90, 'PDF生成完成，准备返回结果...');
            $examContent = $paperPayloadService->buildExamContent($paperModel);

            // 标记任务完成（完整PDF存储到all_pdf_url字段）
            $taskManager->markTaskCompleted($this->taskId, [
                'exam_content' => $examContent,
                'pdfs' => [
                    'all_pdf' => $unifiedPdfUrl, // 【完整PDF】包含试卷和判卷，存储到all_pdf_url字段
                ],
            ]);

            $beforeCallbackAt = microtime(true);
            $queueProcessingMs = (int) round((microtime(true) - $jobStartedAt) * 1000);
            $taskTotalMs = null;
            if ($taskCreatedAt) {
                $taskTotalMs = $taskCreatedAt->diffInMilliseconds(now());
            }

            Log::info('PDF生成队列任务完成（终极优化：直接合并HTML生成一份完整PDF）', [
                'task_id' => $this->taskId,
                'paper_id' => $this->paperId,
                'all_pdf_url' => $unifiedPdfUrl,
                'question_count' => $paperModel->questions->count(),
                'method' => 'generateUnifiedPdf (direct merge, fastest)',
                'queue_processing_ms' => $queueProcessingMs,
                'task_total_ms_before_callback' => $taskTotalMs,
            ]);
            $taskManager->updateTaskStatus($this->taskId, [
                'queue_processing_ms' => $queueProcessingMs,
                'task_total_ms_before_callback' => $taskTotalMs,
            ]);

            // 发送回调通知（在合并PDF完成后）
            $taskManager->sendCallback($this->taskId);

            $callbackMs = (int) round((microtime(true) - $beforeCallbackAt) * 1000);
            $taskTotalAfterCallbackMs = null;
            if ($taskCreatedAt) {
                $taskTotalAfterCallbackMs = $taskCreatedAt->diffInMilliseconds(now());
            }
            Log::info('PDF生成队列任务回调完成', [
                'task_id' => $this->taskId,
                'paper_id' => $this->paperId,
                'callback_elapsed_ms' => $callbackMs,
                'task_total_ms_after_callback' => $taskTotalAfterCallbackMs,
            ]);
            $taskManager->updateTaskStatus($this->taskId, [
                'callback_elapsed_ms' => $callbackMs,
                'task_total_ms_after_callback' => $taskTotalAfterCallbackMs,
            ]);

            $taskSummary = $taskManager->getTaskStatus($this->taskId);
            Log::info('EXAM_TASK_TIMELINE_SUMMARY', [
                'task_id' => $this->taskId,
                'paper_id' => $this->paperId,
                'request_trace_id' => $taskSummary['request_trace_id'] ?? null,
                'sync_elapsed_sec_total' => isset($taskSummary['sync_elapsed_ms_total'])
                    ? round(((float) $taskSummary['sync_elapsed_ms_total']) / 1000, 3)
                    : null,
                'queue_wait_sec' => round(((float) ($taskSummary['queue_wait_ms'] ?? $queueWaitMs ?? 0)) / 1000, 3),
                'queue_processing_sec' => round(((float) ($taskSummary['queue_processing_ms'] ?? $queueProcessingMs ?? 0)) / 1000, 3),
                'callback_sec' => round(((float) ($taskSummary['callback_elapsed_ms'] ?? $callbackMs ?? 0)) / 1000, 3),
                'task_total_sec_after_callback' => round(((float) ($taskSummary['task_total_ms_after_callback'] ?? $taskTotalAfterCallbackMs ?? 0)) / 1000, 3),
                'status' => $taskSummary['status'] ?? null,
            ]);

        } catch (\Exception $e) {
            Log::error('PDF生成队列任务失败', [
                'task_id' => $this->taskId,
                'paper_id' => $this->paperId,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
                'queue_elapsed_ms_before_error' => (int) round((microtime(true) - $jobStartedAt) * 1000),
            ]);

            // 如果是第一次失败且试卷可能还在创建中，等待后重试
            if ($this->attempts() < $this->tries && strpos($e->getMessage(), '不存在') !== false) {
                Log::info('检测到试卷不存在错误，将在2秒后重试', [
                    'task_id' => $this->taskId,
                    'paper_id' => $this->paperId,
                    'attempt' => $this->attempts(),
                ]);
                $this->release(2);

                return;
            }

            $taskManager->markTaskFailed($this->taskId, $e->getMessage());
        }
    }

    public function failed(Throwable $exception): void
    {
        try {
            app(TaskManager::class)->markTaskFailed($this->taskId, $exception->getMessage());
        } catch (Throwable $innerException) {
            Log::error('PDF生成队列任务失败回调异常', [
                'task_id' => $this->taskId,
                'paper_id' => $this->paperId,
                'error' => $innerException->getMessage(),
            ]);
        }

        Log::error('PDF生成队列任务最终失败', [
            'task_id' => $this->taskId,
            'paper_id' => $this->paperId,
            'error' => $exception->getMessage(),
        ]);
    }
}
