<?php

declare(strict_types=1);

namespace Tests\Feature;

use App\Models\Paper;
use App\Models\QuestionBank;
use App\Models\SchoolClass;
use App\Models\User;
use App\Services\QuestionImportService;
use App\Services\QuizService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use Tests\TestCase;
use Tymon\JWTAuth\Facades\JWTAuth;

final class QuizFlowTest extends TestCase
{
    use RefreshDatabase;

    public function test_practice_answer_updates_wrong_question_state(): void
    {
        $user = User::factory()->create(['role' => 'user']);
        $bank = QuestionBank::create([
            'owner_id' => $user->id,
            'name' => '测试题库',
            'visibility' => 'public',
            'is_active' => true,
        ]);
        app(QuestionImportService::class)->importJsonText($bank, $user, file_get_contents(base_path('question.json')));

        $service = app(QuizService::class);
        $attempt = $service->startPractice($user, $bank, 'practice', ['limit' => 100]);
        $item = $attempt->items()->with('question.options')->firstOrFail();
        $wrongOption = $item->question->options->firstWhere('is_correct', false);

        $service->answer($user, $attempt, $item->question_id, [$wrongOption->id]);

        $this->assertDatabaseHas('wrong_questions', [
            'user_id' => $user->id,
            'question_id' => $item->question_id,
            'wrong_count' => 1,
        ]);
    }

    public function test_paper_attempt_uses_override_score_and_enforces_attempt_limit(): void
    {
        $user = User::factory()->create(['role' => 'user']);
        $bank = QuestionBank::create([
            'owner_id' => $user->id,
            'name' => '固定试卷题库',
            'visibility' => 'public',
            'is_active' => true,
        ]);
        app(QuestionImportService::class)->importJsonText($bank, $user, file_get_contents(base_path('question.json')));
        $question = $bank->questions()->with('options')->firstOrFail();
        $paper = Paper::create([
            'owner_id' => $user->id,
            'question_bank_id' => $bank->id,
            'title' => '限次测试',
            'attempt_limit' => 1,
            'is_active' => true,
        ]);
        $paper->questions()->attach($question->id, ['score' => 5, 'sort' => 0]);

        $service = app(QuizService::class);
        $attempt = $service->startPaper($user, $paper);
        $item = $attempt->items()->with('question.options')->firstOrFail();
        $correctOptionIds = $item->question->options->where('is_correct', true)->pluck('id')->all();

        $service->answer($user, $attempt, $item->question_id, $correctOptionIds);
        $submitted = $service->submit($user, $attempt);

        $this->assertSame('5.00', $submitted->score);

        $this->expectException(ValidationException::class);
        $service->startPaper($user, $paper);
    }

    public function test_wrong_question_is_marked_mastered_after_three_correct_answers(): void
    {
        $user = User::factory()->create(['role' => 'user']);
        $bank = QuestionBank::create([
            'owner_id' => $user->id,
            'name' => '错题题库',
            'visibility' => 'public',
            'is_active' => true,
        ]);
        app(QuestionImportService::class)->importJsonText($bank, $user, file_get_contents(base_path('question.json')));

        $service = app(QuizService::class);
        $attempt = $service->startPractice($user, $bank, 'practice', ['limit' => 1]);
        $item = $attempt->items()->with('question.options')->firstOrFail();
        $wrongOption = $item->question->options->firstWhere('is_correct', false);
        $correctOptionIds = $item->question->options->where('is_correct', true)->pluck('id')->all();

        $service->answer($user, $attempt, $item->question_id, [$wrongOption->id]);

        for ($i = 0; $i < 3; $i++) {
            $nextAttempt = $service->startPractice($user, $bank, 'practice', ['limit' => 100]);
            $service->answer($user, $nextAttempt, $item->question_id, $correctOptionIds);
        }

        $this->assertDatabaseMissing('wrong_questions', [
            'user_id' => $user->id,
            'question_id' => $item->question_id,
            'mastered_at' => null,
        ]);
    }

    public function test_attempt_position_can_be_saved_and_resumed(): void
    {
        $user = User::factory()->create(['role' => 'user', 'password' => bcrypt('password')]);
        $bank = QuestionBank::create([
            'owner_id' => $user->id,
            'name' => '续做题库',
            'visibility' => 'public',
            'is_active' => true,
        ]);
        app(QuestionImportService::class)->importJsonText($bank, $user, file_get_contents(base_path('question.json')));

        $attempt = app(QuizService::class)->startPractice($user, $bank, 'practice', ['limit' => 3]);
        $token = $this->postJson('/api/auth/login', [
            'email' => $user->email,
            'password' => 'password',
        ])->assertOk()->json('data.token');

        $this->assertSame($user->id, JWTAuth::setToken($token)->authenticate()->id);
        $this->assertSame($user->id, $attempt->user_id);

        $response = $this->withToken($token)
            ->putJson("/api/app/attempts/{$attempt->id}/position", [
                'current_index' => 1,
            ]);
        $response
            ->assertOk()
            ->assertJsonPath('data.current_index', 1);

        $this->withToken($token)
            ->getJson("/api/app/attempts/{$attempt->id}")
            ->assertOk()
            ->assertJsonPath('data.current_index', 1);
    }

    public function test_private_bank_cannot_be_started_without_share(): void
    {
        $owner = User::factory()->create(['role' => 'teacher']);
        $student = User::factory()->create(['role' => 'user']);
        $bank = $this->bankWithQuestions($owner, ['visibility' => 'private']);
        $token = JWTAuth::fromUser($student);

        $this->withToken($token)
            ->postJson("/api/app/banks/{$bank->id}/attempts", [
                'mode' => 'practice',
                'limit' => 1,
            ])
            ->assertForbidden();
    }

    public function test_user_share_allows_starting_private_bank(): void
    {
        $owner = User::factory()->create(['role' => 'teacher']);
        $student = User::factory()->create(['role' => 'user']);
        $bank = $this->bankWithQuestions($owner, ['visibility' => 'assigned']);
        DB::table('bank_shares')->insert([
            'question_bank_id' => $bank->id,
            'target_type' => 'user',
            'target_id' => $student->id,
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        $this->withToken(JWTAuth::fromUser($student))
            ->postJson("/api/app/banks/{$bank->id}/attempts", [
                'mode' => 'practice',
                'limit' => 1,
            ])
            ->assertOk()
            ->assertJsonPath('data.question_bank_id', $bank->id);
    }

    public function test_class_share_allows_resources_and_paper_attempts(): void
    {
        $owner = User::factory()->create(['role' => 'teacher']);
        $student = User::factory()->create(['role' => 'user']);
        $bank = $this->bankWithQuestions($owner, ['visibility' => 'assigned']);
        $class = SchoolClass::create([
            'owner_id' => $owner->id,
            'name' => '一班',
            'join_code' => 'CLASS001',
            'is_active' => true,
        ]);
        $class->members()->attach($student->id, ['role' => 'student']);
        DB::table('bank_shares')->insert([
            'question_bank_id' => $bank->id,
            'target_type' => 'class',
            'target_id' => $class->id,
            'created_at' => now(),
            'updated_at' => now(),
        ]);
        $paper = Paper::create([
            'owner_id' => $owner->id,
            'question_bank_id' => $bank->id,
            'title' => '班级测试',
            'is_active' => true,
        ]);
        $paper->questions()->attach($bank->questions()->firstOrFail()->id, ['sort' => 0]);
        $token = JWTAuth::fromUser($student);

        $this->withToken($token)
            ->getJson('/api/app/resources')
            ->assertOk()
            ->assertJsonPath('data.banks.0.id', $bank->id)
            ->assertJsonPath('data.papers.0.id', $paper->id);

        $this->withToken($token)
            ->postJson("/api/app/papers/{$paper->id}/attempts")
            ->assertOk()
            ->assertJsonPath('data.paper_id', $paper->id);
    }

    public function test_public_bank_can_be_started_by_student(): void
    {
        $owner = User::factory()->create(['role' => 'teacher']);
        $student = User::factory()->create(['role' => 'user']);
        $bank = $this->bankWithQuestions($owner, ['visibility' => 'public']);

        $this->withToken(JWTAuth::fromUser($student))
            ->postJson("/api/app/banks/{$bank->id}/attempts", [
                'mode' => 'practice',
                'limit' => 1,
            ])
            ->assertOk()
            ->assertJsonPath('data.question_bank_id', $bank->id);
    }

    public function test_private_unshared_paper_cannot_be_started(): void
    {
        $owner = User::factory()->create(['role' => 'teacher']);
        $student = User::factory()->create(['role' => 'user']);
        $bank = $this->bankWithQuestions($owner, ['visibility' => 'private']);
        $paper = Paper::create([
            'owner_id' => $owner->id,
            'question_bank_id' => $bank->id,
            'title' => '私有测试',
            'is_active' => true,
        ]);
        $paper->questions()->attach($bank->questions()->firstOrFail()->id, ['sort' => 0]);

        $this->withToken(JWTAuth::fromUser($student))
            ->postJson("/api/app/papers/{$paper->id}/attempts")
            ->assertForbidden();
    }

    /**
     * @param  array<string, mixed>  $attributes
     */
    private function bankWithQuestions(User $owner, array $attributes = []): QuestionBank
    {
        $bank = QuestionBank::create($attributes + [
            'owner_id' => $owner->id,
            'name' => '权限题库',
            'visibility' => 'public',
            'is_active' => true,
        ]);

        app(QuestionImportService::class)->importJsonText($bank, $owner, file_get_contents(base_path('question.json')));

        return $bank;
    }
}
