<?php

namespace App\Console\Commands;

use App\Services\ExamPdfExportService;
use App\Support\OptionLayoutDecider;
use Illuminate\Console\Command;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;

class GenerateOptionLayoutRegressionCommand extends Command
{
    protected $signature = 'exam:generate-option-layout-regression
        {--connection=remote_mysql : 数据库连接}
        {--source=main : 题库来源 main|default|ai}
        {--question-type=choice : 题型过滤 choice|fill|answer|all}
        {--keep-ids= : 固定保留题目ID，逗号分隔，例如 38848,38772,38869}
        {--limit=40 : 抽题数量}
        {--grading=1 : 是否同时生成判卷PDF 1|0}
        {--student-id=REGRESSION : 学生ID}
        {--student-name=排版回归 : 学生姓名}
        {--student-grade=初三 : 学生年级}
        {--teacher-name=回归老师 : 教师姓名}
        {--paper-name=选项排版回归 : 试卷名称}
        {--report= : 报告输出路径(JSON)}';

    protected $description = '遍历题库复杂公式题，生成真实PDF并输出选项布局回归报告';

    public function handle(
        ExamPdfExportService $pdfExportService,
        OptionLayoutDecider $layoutDecider
    ): int {
        $connection = (string) $this->option('connection');
        $source = (string) $this->option('source');
        $limit = max(1, (int) $this->option('limit'));
        $questionType = (string) $this->option('question-type');
        $keepIds = $this->parseKeepIds((string) $this->option('keep-ids'));
        $includeGrading = (string) $this->option('grading') !== '0';

        $table = match ($source) {
            'default' => 'questions_tem',
            'ai' => 'questions_ai',
            default => 'questions',
        };

        $this->info("开始回归抽题: {$connection}.{$table}, type={$questionType}, limit={$limit}, keep=".count($keepIds));

        $questions = $this->fetchComplexQuestions($connection, $table, $limit, $questionType, $keepIds);
        if ($questions->isEmpty()) {
            $this->warn('未找到复杂公式题，请调整筛选条件或检查题库数据');

            return self::FAILURE;
        }

        $groupedQuestions = $this->groupQuestionsByType($questions);
        $paperId = 'layout_regression_'.date('YmdHis').'_'.substr(uniqid('', true), -6);
        $paper = $this->buildVirtualPaper(
            $paperId,
            (string) $this->option('paper-name'),
            $groupedQuestions
        );

        $student = [
            'id' => (string) $this->option('student-id'),
            'name' => (string) $this->option('student-name'),
            'grade' => (string) $this->option('student-grade'),
        ];
        $teacher = ['name' => (string) $this->option('teacher-name')];

        $result = $pdfExportService->generateByQuestions(
            $paper,
            $groupedQuestions,
            $student,
            $teacher,
            $includeGrading
        );

        $reportPath = (string) ($this->option('report') ?: storage_path('app/regression/option-layout-'.$paperId.'.json'));
        File::ensureDirectoryExists(dirname($reportPath));
        File::put($reportPath, json_encode([
            'generated_at' => now()->toDateTimeString(),
            'paper_id' => $paperId,
            'source' => [
                'connection' => $connection,
                'table' => $table,
                'keep_ids' => $keepIds,
            ],
            'summary' => [
                'question_count' => $questions->count(),
                'choice_count' => count($groupedQuestions['choice']),
                'fill_count' => count($groupedQuestions['fill']),
                'answer_count' => count($groupedQuestions['answer']),
            ],
            'pdfs' => $result,
            'layout_checks' => $this->buildLayoutChecks($groupedQuestions, $layoutDecider),
        ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES).PHP_EOL);

        $this->info('回归PDF生成完成');
        $this->line('Exam PDF: '.($result['pdf_url'] ?? 'N/A'));
        $this->line('Grading PDF: '.($result['grading_pdf_url'] ?? 'N/A'));
        $this->line('Report JSON: '.$reportPath);

        return self::SUCCESS;
    }

    private function fetchComplexQuestions(string $connection, string $table, int $limit, string $questionType, array $keepIds): Collection
    {
        $kept = collect();
        if (! empty($keepIds)) {
            $keptRaw = DB::connection($connection)
                ->table($table)
                ->select(['id', 'question_type', 'kp_code', 'stem', 'options', 'answer', 'solution', 'difficulty'])
                ->whereIn('id', $keepIds)
                ->get()
                ->keyBy('id');

            foreach ($keepIds as $id) {
                if ($keptRaw->has($id)) {
                    $kept->push($keptRaw->get($id));
                }
            }
        }

        $remainingLimit = max(0, $limit - $kept->count());

        $query = DB::connection($connection)
            ->table($table)
            ->select(['id', 'question_type', 'kp_code', 'stem', 'options', 'answer', 'solution', 'difficulty'])
            ->when($questionType !== 'all', function ($q) use ($questionType) {
                $q->where('question_type', $questionType);
            })
            ->when($questionType === 'choice', function ($q) {
                $q->whereNotNull('options')
                    ->where('options', '!=', '')
                    ->where('options', '!=', '[]')
                    ->where('options', '!=', '{}');
            })
            ->where(function ($query) {
                $query->where('stem', 'like', '%\\frac%')
                    ->orWhere('stem', 'like', '%\\sqrt%')
                    ->orWhere('stem', 'like', '%\\log%')
                    ->orWhere('stem', 'like', '%\\sin%')
                    ->orWhere('stem', 'like', '%\\cos%')
                    ->orWhere('stem', 'like', '%\\tan%')
                    ->orWhere('stem', 'like', '%<img%')
                    ->orWhere('options', 'like', '%\\frac%')
                    ->orWhere('options', 'like', '%\\sqrt%')
                    ->orWhere('options', 'like', '%\\log%')
                    ->orWhere('options', 'like', '%\\sin%')
                    ->orWhere('options', 'like', '%\\cos%')
                    ->orWhere('options', 'like', '%\\tan%')
                    ->orWhere('options', 'like', '%^%')
                    ->orWhere('options', 'like', '%/%');
            })
            ->when(! empty($keepIds), function ($q) use ($keepIds) {
                $q->whereNotIn('id', $keepIds);
            })
            ->orderByDesc('id');

        $sampled = $remainingLimit > 0 ? $query->limit($remainingLimit)->get() : collect();

        return $kept->concat($sampled)->values();
    }

    private function groupQuestionsByType(Collection $questions): array
    {
        $grouped = [
            'choice' => [],
            'fill' => [],
            'answer' => [],
        ];

        $questionNumber = 1;
        foreach ($questions as $q) {
            $type = $this->normalizeQuestionType($q->question_type);
            $grouped[$type][] = (object) [
                'id' => $q->id,
                'question_number' => $questionNumber++,
                'content' => $q->stem,
                'options' => $this->normalizeOptions($q->options),
                'answer' => $q->answer,
                'solution' => $q->solution,
                'score' => $this->defaultScore($type),
                'difficulty' => $q->difficulty,
                'kp_code' => $q->kp_code,
            ];
        }

        return $grouped;
    }

    private function normalizeQuestionType(?string $type): string
    {
        $type = strtolower(trim((string) $type));

        return match ($type) {
            'choice', '选择题', 'single_choice', 'multiple_choice' => 'choice',
            'fill', '填空题', 'blank' => 'fill',
            default => 'answer',
        };
    }

    private function normalizeOptions(mixed $options): array
    {
        if (is_array($options)) {
            return array_values($options);
        }

        if (is_string($options) && trim($options) !== '') {
            $decoded = json_decode($options, true);
            if (is_array($decoded)) {
                return array_values($decoded);
            }
        }

        return [];
    }

    private function defaultScore(string $type): int
    {
        return match ($type) {
            'answer' => 10,
            default => 5,
        };
    }

    private function buildVirtualPaper(string $paperId, string $paperName, array $groupedQuestions): object
    {
        $totalScore = 0;
        $totalQuestions = 0;
        foreach ($groupedQuestions as $items) {
            foreach ($items as $item) {
                $totalScore += (int) ($item->score ?? 0);
                $totalQuestions++;
            }
        }

        return (object) [
            'paper_id' => $paperId,
            'paper_name' => $paperName,
            'total_score' => $totalScore,
            'total_questions' => $totalQuestions,
            'created_at' => now()->toDateTimeString(),
        ];
    }

    private function buildLayoutChecks(array $groupedQuestions, OptionLayoutDecider $layoutDecider): array
    {
        $rows = [];
        foreach (['choice', 'fill', 'answer'] as $type) {
            foreach ($groupedQuestions[$type] as $question) {
                $options = is_array($question->options ?? null) ? $question->options : [];
                if ($type !== 'choice' || empty($options)) {
                    continue;
                }

                $examDecision = $layoutDecider->decide($options, 'exam');
                $gradingDecision = $layoutDecider->decide($options, 'grading');

                $rows[] = [
                    'question_id' => $question->id,
                    'question_number' => $question->question_number,
                    'kp_code' => $question->kp_code,
                    'options_count' => count($options),
                    'exam_layout' => $examDecision['class'],
                    'grading_layout' => $gradingDecision['class'],
                    'max_length_exam' => $examDecision['max_length'],
                    'has_complex_formula' => $examDecision['has_complex_formula'],
                ];
            }
        }

        return $rows;
    }

    /**
     * @return array<int>
     */
    private function parseKeepIds(string $keepIds): array
    {
        if (trim($keepIds) === '') {
            return [];
        }

        return collect(explode(',', $keepIds))
            ->map(fn ($id) => (int) trim($id))
            ->filter(fn ($id) => $id > 0)
            ->unique()
            ->values()
            ->all();
    }
}
