<?php

declare(strict_types=1);

namespace App\Http\Controllers\Api\Admin;

use App\Http\Controllers\Api\Admin\Concerns\AuthorizesOwnedResources;
use App\Http\Controllers\Controller;
use App\Models\OperationLog;
use App\Models\Paper;
use App\Models\Question;
use App\Models\QuestionBank;
use App\Support\ApiResponse;
use hg\apidoc\annotation as Apidoc;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

#[Apidoc\Group('后台')]
#[Apidoc\Title('试卷管理')]
#[Apidoc\RouteMiddleware(['jwt.auth'])]
final class PaperController extends Controller
{
    use AuthorizesOwnedResources;

    #[Apidoc\Title('试卷列表')]
    #[Apidoc\Url('/admin/papers')]
    #[Apidoc\Method('GET')]
    #[Apidoc\RouteMiddleware(['permission:papers'])]
    public function index(Request $request): JsonResponse
    {
        $query = Paper::query()->withCount('questions')->latest();
        if ($request->user()->role !== 'admin') {
            $query->where('owner_id', $request->user()->id);
        }

        return ApiResponse::page($query->paginate((int) $request->query('per_page', 20)));
    }

    #[Apidoc\Title('试卷详情')]
    #[Apidoc\Url('/admin/papers/{paper}')]
    #[Apidoc\Method('GET')]
    #[Apidoc\RouteMiddleware(['permission:papers'])]
    public function show(Request $request, mixed $paper): JsonResponse
    {
        $paper = $this->resolvePaper($paper);
        $this->authorizePaperOwner($request, $paper);

        return ApiResponse::success($paper->load('questions.options')->loadCount('questions'));
    }

    #[Apidoc\Title('创建固定试卷')]
    #[Apidoc\Url('/admin/papers')]
    #[Apidoc\Method('POST')]
    #[Apidoc\RouteMiddleware(['permission:papers'])]
    public function store(Request $request): JsonResponse
    {
        $data = $this->validatePaper($request);

        if (! empty($data['question_bank_id'])) {
            $this->authorizeBankOwner($request, QuestionBank::findOrFail($data['question_bank_id']));
        }

        $this->authorizeQuestions($request, collect($data['questions'] ?? [])->pluck('id')->all());

        $paper = Paper::create([
            'owner_id' => $request->user()->id,
            'question_bank_id' => $data['question_bank_id'] ?? null,
            'title' => $data['title'],
            'description' => $data['description'] ?? null,
            'duration_minutes' => $data['duration_minutes'] ?? null,
            'attempt_limit' => $data['attempt_limit'] ?? null,
            'is_active' => true,
        ]);

        $this->syncQuestions($paper, $data['questions'] ?? []);

        OperationLog::create([
            'user_id' => $request->user()->id,
            'action' => 'paper.created',
            'target_type' => Paper::class,
            'target_id' => $paper->id,
            'ip' => $request->ip(),
            'payload' => ['title' => $paper->title],
        ]);

        return ApiResponse::success($paper->load('questions'), '试卷已创建');
    }

    #[Apidoc\Title('更新固定试卷')]
    #[Apidoc\Url('/admin/papers/{paper}')]
    #[Apidoc\Method('PUT')]
    #[Apidoc\RouteMiddleware(['permission:papers'])]
    public function update(Request $request, mixed $paper): JsonResponse
    {
        $paper = $this->resolvePaper($paper);
        $this->authorizePaperOwner($request, $paper);
        $data = $this->validatePaper($request, true);

        if (array_key_exists('question_bank_id', $data) && $data['question_bank_id'] !== null) {
            $this->authorizeBankOwner($request, QuestionBank::findOrFail($data['question_bank_id']));
        }

        if (array_key_exists('questions', $data)) {
            $this->authorizeQuestions($request, collect($data['questions'] ?? [])->pluck('id')->all());
        }

        $paper->update([
            ...collect($data)
                ->only(['title', 'description', 'question_bank_id', 'duration_minutes', 'attempt_limit', 'is_active'])
                ->all(),
        ]);

        if (array_key_exists('questions', $data)) {
            $this->syncQuestions($paper, $data['questions'] ?? []);
        }

        return ApiResponse::success($paper->fresh('questions')->loadCount('questions'), '试卷已更新');
    }

    #[Apidoc\Title('删除固定试卷')]
    #[Apidoc\Url('/admin/papers/{paper}')]
    #[Apidoc\Method('DELETE')]
    #[Apidoc\RouteMiddleware(['permission:papers'])]
    public function destroy(Request $request, mixed $paper): JsonResponse
    {
        $paper = $this->resolvePaper($paper);
        $this->authorizePaperOwner($request, $paper);
        $paper->delete();
        OperationLog::create([
            'user_id' => $request->user()->id,
            'action' => 'paper.deleted',
            'target_type' => Paper::class,
            'target_id' => $paper->id,
            'ip' => $request->ip(),
        ]);

        return ApiResponse::success(null, '试卷已删除');
    }

    /**
     * @return array<string, mixed>
     */
    private function validatePaper(Request $request, bool $updating = false): array
    {
        return $request->validate([
            'title' => [$updating ? 'sometimes' : 'required', 'string', 'max:120'],
            'description' => ['nullable', 'string'],
            'question_bank_id' => ['nullable', 'exists:question_banks,id'],
            'duration_minutes' => ['nullable', 'integer', 'min:1'],
            'attempt_limit' => ['nullable', 'integer', 'min:1'],
            'is_active' => ['sometimes', 'boolean'],
            'questions' => ['array'],
            'questions.*.id' => ['required_with:questions', 'exists:questions,id'],
            'questions.*.score' => ['nullable', 'numeric', 'min:0'],
        ]);
    }

    /**
     * @param  array<int, int>  $questionIds
     */
    private function authorizeQuestions(Request $request, array $questionIds): void
    {
        if ($questionIds === []) {
            return;
        }

        $visibleQuestionCount = Question::query()
            ->whereIn('id', $questionIds)
            ->whereHas('bank', function ($query) use ($request): void {
                if ($request->user()->role !== 'admin') {
                    $query->where('owner_id', $request->user()->id);
                }
            })
            ->count();

        abort_if($visibleQuestionCount !== count(array_unique($questionIds)), 403, '题目权限不足');
    }

    /**
     * @param  array<int, array{id: int, score?: float|int|null}>  $questions
     */
    private function syncQuestions(Paper $paper, array $questions): void
    {
        $syncPayload = [];
        foreach ($questions as $sort => $question) {
            $syncPayload[$question['id']] = [
                'score' => $question['score'] ?? null,
                'sort' => $sort,
            ];
        }

        $paper->questions()->sync($syncPayload);
    }

    private function resolvePaper(mixed $paper): Paper
    {
        if ($paper instanceof Paper && $paper->exists) {
            return $paper;
        }

        return Paper::query()->findOrFail((int) $paper);
    }
}
