<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\ImportJob;
use App\Models\Question;
use App\Models\QuestionBank;
use App\Models\User;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
use Maatwebsite\Excel\Concerns\ToArray;
use Maatwebsite\Excel\Facades\Excel;

final class QuestionImportService
{
    public function importJsonText(QuestionBank $bank, User $user, string $json, ?string $filePath = null): ImportJob
    {
        $rows = json_decode($json, true);

        if (! is_array($rows)) {
            throw ValidationException::withMessages(['file' => 'JSON 必须是题目数组']);
        }

        return $this->importRows($bank, $user, $rows, 'json', $filePath);
    }

    public function importUploadedFile(QuestionBank $bank, User $user, UploadedFile $file): ImportJob
    {
        $prepared = $this->prepareUploadedFile($file);

        return $this->importRows($bank, $user, $prepared['rows'], $prepared['type'], $prepared['path']);
    }

    /**
     * @return array{type:string,path:string,rows:array<int, array<string, mixed>>}
     */
    public function prepareUploadedFile(UploadedFile $file): array
    {
        $path = $file->store('imports');
        $extension = strtolower($file->getClientOriginalExtension());

        if ($extension === 'json') {
            $content = Storage::get($path);
            $rows = json_decode($content, true);

            if (! is_array($rows)) {
                throw ValidationException::withMessages(['file' => 'JSON 必须是题目数组']);
            }

            return ['type' => 'json', 'path' => $path, 'rows' => $rows];
        }

        $sheets = Excel::toArray(new class implements ToArray
        {
            public function array(array $array): array
            {
                return $array;
            }
        }, Storage::path($path));
        $rows = $this->normalizeExcelRows($sheets[0] ?? []);

        return ['type' => 'excel', 'path' => $path, 'rows' => $rows];
    }

    /**
     * @param  array<int, array<string, mixed>>  $rows
     * @return array{valid:bool,rows:array<int, array<string, mixed>>,errors:array<int, array{row:int,message:string}>,duplicates:array<int, array{row:int,message:string,content:string,type:string}>,importable_count:int}
     */
    public function validateRows(array $rows, ?QuestionBank $bank = null): array
    {
        $errors = [];
        $duplicates = [];
        $seenHashes = [];
        foreach ($rows as $index => $row) {
            try {
                $normalized = $this->normalizeQuestionRow($row, $index + 1);
                $hash = $this->dedupHash($normalized['content'], $normalized['options']);
                if (isset($seenHashes[$hash])) {
                    $duplicates[] = [
                        'row' => $index + 1,
                        'message' => "与本次上传第 {$seenHashes[$hash]} 行重复，确认导入时会跳过",
                        'content' => $normalized['content'],
                        'type' => $normalized['type'],
                    ];

                    continue;
                }

                $seenHashes[$hash] = $index + 1;

                if ($bank !== null && $this->isDuplicate($bank, $normalized)) {
                    $duplicates[] = [
                        'row' => $index + 1,
                        'message' => '与题库已有题目重复，确认导入时会跳过',
                        'content' => $normalized['content'],
                        'type' => $normalized['type'],
                    ];
                }
            } catch (ValidationException $exception) {
                $errors[] = [
                    'row' => $index + 1,
                    'message' => collect($exception->errors())->flatten()->first() ?? '格式错误',
                ];
            }
        }

        return [
            'valid' => $errors === [],
            'rows' => $rows,
            'errors' => $errors,
            'duplicates' => $duplicates,
            'importable_count' => max(0, count($rows) - count($errors) - count($duplicates)),
        ];
    }

    /**
     * @param  array<int, array<string, mixed>>  $rows
     */
    public function importRows(QuestionBank $bank, User $user, array $rows, string $type, ?string $filePath = null): ImportJob
    {
        return DB::transaction(function () use ($bank, $user, $rows, $type, $filePath): ImportJob {
            $job = ImportJob::create([
                'user_id' => $user->id,
                'question_bank_id' => $bank->id,
                'type' => $type,
                'file_path' => $filePath,
                'status' => 'running',
                'total_count' => count($rows),
                'report' => [],
            ]);

            $report = [];
            $success = 0;
            $skipped = 0;
            $seenHashes = [];

            foreach ($rows as $index => $row) {
                $normalized = $this->normalizeQuestionRow($row, $index + 1);
                $hash = $this->dedupHash($normalized['content'], $normalized['options']);

                if (isset($seenHashes[$hash])) {
                    $skipped++;
                    $report[] = [
                        'row' => $index + 1,
                        'status' => 'skipped',
                        'message' => "与本次导入第 {$seenHashes[$hash]} 行重复，已跳过",
                        'content' => $normalized['content'],
                        'type' => $normalized['type'],
                    ];

                    continue;
                }

                $seenHashes[$hash] = $index + 1;

                if ($this->isDuplicate($bank, $normalized)) {
                    $skipped++;
                    $report[] = [
                        'row' => $index + 1,
                        'status' => 'skipped',
                        'message' => '与题库已有题目重复，已跳过',
                        'content' => $normalized['content'],
                        'type' => $normalized['type'],
                    ];

                    continue;
                }

                $question = Question::create([
                    'question_bank_id' => $bank->id,
                    'category_id' => null,
                    'creator_id' => $user->id,
                    'type' => $normalized['type'],
                    'content' => $normalized['content'],
                    'explanation' => $normalized['explanation'],
                    'answers' => $normalized['answers'],
                    'source_question_id' => $normalized['source_question_id'],
                    'dedup_hash' => $hash,
                    'is_active' => true,
                ]);

                foreach ($normalized['options'] as $sort => $option) {
                    $question->options()->create([
                        'content' => $option['text'],
                        'is_correct' => $option['correct'],
                        'sort' => $sort,
                    ]);
                }

                $success++;
                $report[] = [
                    'row' => $index + 1,
                    'status' => 'success',
                    'message' => '导入成功',
                    'content' => $normalized['content'],
                    'type' => $normalized['type'],
                ];
            }

            $job->update([
                'status' => 'finished',
                'success_count' => $success,
                'skipped_count' => $skipped,
                'report' => $report,
            ]);

            return $job->fresh();
        });
    }

    /**
     * @param  array<string, mixed>  $row
     * @return array{type:string,content:string,explanation:?string,answers:array<int, mixed>,source_question_id:?string,options:array<int, array{text:string,correct:bool}>}
     */
    private function normalizeQuestionRow(array $row, int $rowNumber): array
    {
        $content = trim((string) ($row['questionText'] ?? $row['content'] ?? $row['题干'] ?? ''));
        if ($content === '') {
            throw ValidationException::withMessages(['file' => "第 {$rowNumber} 行题干不能为空"]);
        }

        $options = $row['options'] ?? null;
        if (! is_array($options)) {
            $options = $this->optionsFromFlatRow($row);
        }

        $normalizedOptions = [];
        foreach ($options as $option) {
            if (! is_array($option)) {
                continue;
            }
            $text = trim((string) ($option['text'] ?? $option['content'] ?? $option['选项'] ?? ''));
            if ($text === '') {
                continue;
            }
            $normalizedOptions[] = [
                'text' => $text,
                'correct' => (bool) ($option['correct'] ?? $option['is_correct'] ?? false),
            ];
        }

        $correctCount = count(array_filter($normalizedOptions, fn (array $option): bool => $option['correct']));
        if ($correctCount < 1 && empty($row['answer'])) {
            throw ValidationException::withMessages(['file' => "第 {$rowNumber} 行至少需要一个正确答案"]);
        }

        $type = $this->detectType($normalizedOptions, $correctCount, (string) ($row['type'] ?? ''));
        if ($type === 'blank') {
            $answers = array_values(array_filter(array_map('trim', explode('|', (string) ($row['answer'] ?? '')))));
            if ($answers === []) {
                throw ValidationException::withMessages(['file' => "第 {$rowNumber} 行填空题答案不能为空"]);
            }

            return [
                'type' => 'blank',
                'content' => $content,
                'explanation' => $row['explanation'] ?? null,
                'answers' => $answers,
                'source_question_id' => isset($row['questionId']) ? (string) $row['questionId'] : null,
                'options' => [],
            ];
        }

        return [
            'type' => $type,
            'content' => $content,
            'explanation' => $row['explanation'] ?? null,
            'answers' => [],
            'source_question_id' => isset($row['questionId']) ? (string) $row['questionId'] : null,
            'options' => $normalizedOptions,
        ];
    }

    /**
     * @param  array<string, mixed>  $row
     * @return array<int, array{text:string,correct:bool}>
     */
    private function optionsFromFlatRow(array $row): array
    {
        $answer = strtoupper(trim((string) ($row['answer'] ?? $row['答案'] ?? '')));
        $letters = ['A', 'B', 'C', 'D', 'E', 'F', 'G'];
        $correctLetters = array_filter(array_map('trim', preg_split('/[,，|]/', $answer) ?: []));
        $options = [];

        foreach ($letters as $letter) {
            $text = $row[$letter] ?? $row['option_'.$letter] ?? $row['选项'.$letter] ?? null;
            if ($text === null || trim((string) $text) === '') {
                continue;
            }
            $options[] = [
                'text' => trim((string) $text),
                'correct' => in_array($letter, $correctLetters, true),
            ];
        }

        return $options;
    }

    /**
     * @param  array<int, array{text:string,correct:bool}>  $options
     */
    private function detectType(array $options, int $correctCount, string $explicit): string
    {
        $explicit = strtolower($explicit);
        if (in_array($explicit, ['single', 'multiple', 'judge', 'blank'], true)) {
            return $explicit;
        }

        if ($options === []) {
            return 'blank';
        }

        $texts = array_map(fn (array $option): string => $option['text'], $options);
        sort($texts);
        if (count($options) === 2 && $texts === ['对', '错']) {
            return 'judge';
        }

        return $correctCount > 1 ? 'multiple' : 'single';
    }

    /**
     * @param  array<int, array{text:string,correct:bool}>  $options
     */
    private function dedupHash(string $content, array $options): string
    {
        return hash('sha256', json_encode([
            'content' => preg_replace('/\s+/u', '', $content),
            'options' => array_map(fn (array $option): array => [
                'text' => preg_replace('/\s+/u', '', $option['text']),
                'correct' => $option['correct'],
            ], $options),
        ], JSON_UNESCAPED_UNICODE));
    }

    /**
     * @param  array{content:string,options:array<int, array{text:string,correct:bool}>}  $normalized
     */
    private function isDuplicate(QuestionBank $bank, array $normalized): bool
    {
        return Question::query()
            ->where('question_bank_id', $bank->id)
            ->where('dedup_hash', $this->dedupHash($normalized['content'], $normalized['options']))
            ->exists();
    }

    /**
     * @param  array<int, array<int, mixed>>  $rows
     * @return array<int, array<string, mixed>>
     */
    private function normalizeExcelRows(array $rows): array
    {
        if ($rows === []) {
            return [];
        }

        $headers = array_map(fn ($header): string => trim((string) $header), array_shift($rows));

        return array_values(array_filter(array_map(function (array $row) use ($headers): array {
            $item = [];
            foreach ($headers as $index => $header) {
                if ($header !== '') {
                    $item[$header] = $row[$index] ?? null;
                }
            }

            return $item;
        }, $rows), fn (array $row): bool => array_filter($row) !== []));
    }
}
