<?php

declare(strict_types=1);

namespace Tests\Feature\Academy;

use App\Http\Middleware\ConditionalValidateCsrfToken;
use App\Http\Middleware\HandleInertiaRequests;
use App\Models\AcademyAiComparisonResult;
use App\Models\AcademyChallenge;
use App\Models\AcademyContentMetricDaily;
use App\Models\AcademyCourse;
use App\Models\AcademyCourseEnrollment;
use App\Models\AcademyCourseLesson;
use App\Models\AcademyCourseSection;
use App\Models\AcademyLesson;
use App\Models\AcademyLessonBlock;
use App\Models\AcademyLessonProgress;
use App\Models\AcademyPromptPack;
use App\Models\AcademyPromptTemplate;
use App\Models\Artwork;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Inertia\Testing\AssertableInertia;
use Laravel\Cashier\Subscription;
use Laravel\Cashier\SubscriptionItem;
use Tests\TestCase;

final class AcademyFeatureTest extends TestCase
{
    use RefreshDatabase;

    protected function setUp(): void
    {
        parent::setUp();

        $this->withoutMiddleware(ConditionalValidateCsrfToken::class);
    }

    public function test_academy_homepage_loads_when_enabled(): void
    {
        $this->get('/academy')
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/Index')
                ->where('links.promptPopular', route('academy.prompts.popular'))
                ->where('academyAccess.signedIn', false)
                ->where('academyAccess.status', 'guest')
                ->where('academyAccess.billingUrl', route('academy.pricing')));
    }

    public function test_academy_homepage_exposes_access_summary_for_active_paid_user(): void
    {
        config()->set('academy_billing.plans', [
            'pro_monthly' => [
                'tier' => 'pro',
                'stripe_price_id' => 'price_pro_test',
            ],
        ]);

        $user = User::factory()->create();
        $subscription = Subscription::query()->create([
            'user_id' => $user->id,
            'type' => 'academy',
            'stripe_id' => 'sub_pro_test',
            'stripe_status' => 'active',
            'stripe_price' => 'price_pro_test',
            'quantity' => 1,
            'ends_at' => null,
        ]);

        SubscriptionItem::query()->create([
            'subscription_id' => $subscription->id,
            'stripe_id' => 'si_pro_test',
            'stripe_product' => 'prod_pro_test',
            'stripe_price' => 'price_pro_test',
            'quantity' => 1,
        ]);

        $this->actingAs($user)
            ->get('/academy')
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/Index')
                ->where('academyAccess.signedIn', true)
                ->where('academyAccess.tier', 'pro')
                ->where('academyAccess.tierLabel', 'Pro')
                ->where('academyAccess.status', 'active')
                ->where('academyAccess.statusLabel', 'Renews automatically')
                ->where('academyAccess.renewsAutomatically', true)
                ->where('academyAccess.billingUrl', route('academy.billing.account'))
                ->where('academyAccess.source', 'subscription'));
    }

    public function test_academy_routes_are_hidden_when_feature_is_disabled(): void
    {
        config(['academy.enabled' => false]);

        $this->get('/academy')->assertNotFound();
        $this->get('/academy/lessons')->assertNotFound();
        $this->get('/academy/courses')->assertNotFound();
    }

    public function test_published_course_index_and_show_render(): void
    {
        $course = AcademyCourse::query()->create([
            'title' => 'AI Foundations',
            'slug' => 'ai-foundations',
            'excerpt' => 'A guided course.',
            'description' => 'Course description',
            'cover_image' => 'academy/lessons/covers/course-cover.webp',
            'teaser_image' => 'academy/lessons/covers/course-teaser.webp',
            'access_level' => 'free',
            'difficulty' => 'beginner',
            'status' => 'published',
            'is_featured' => true,
            'published_at' => now()->subMinute(),
        ]);

        $section = AcademyCourseSection::query()->create([
            'course_id' => $course->id,
            'title' => 'Getting started',
            'slug' => 'getting-started',
            'order_num' => 0,
            'is_visible' => true,
        ]);

        $lesson = AcademyLesson::query()->create([
            'title' => 'Course Lesson',
            'slug' => 'course-lesson',
            'excerpt' => 'Lesson in a course.',
            'content' => 'Course lesson content',
            'cover_image' => 'academy/lessons/covers/lesson-cover.webp',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        AcademyCourseLesson::query()->create([
            'course_id' => $course->id,
            'section_id' => $section->id,
            'lesson_id' => $lesson->id,
            'order_num' => 0,
            'is_required' => true,
        ]);

        $this->get(route('academy.courses.index'))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/CoursesIndex')
                ->where('items.data.0.slug', 'ai-foundations')
                ->where('seo.json_ld.0.@type', 'CollectionPage')
                ->where('seo.json_ld.0.mainEntity.@type', 'ItemList')
                ->where('seo.json_ld.0.mainEntity.itemListElement.0.item.@type', 'Course')
                ->where('seo.json_ld.0.mainEntity.itemListElement.0.item.name', 'AI Foundations')
                ->where('seo.json_ld.1.@type', 'BreadcrumbList'));

        $this->get(route('academy.courses.show', ['course' => $course->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/CoursesShow')
                ->where('course.slug', 'ai-foundations')
                ->where('course.cover_image', 'academy/lessons/covers/course-cover.webp')
                ->where('course.teaser_image', 'academy/lessons/covers/course-teaser.webp')
                ->where('seo.json_ld.0.@type', 'ImageObject')
                ->where('seo.json_ld.0.license', route('terms-of-service'))
                ->where('seo.json_ld.1.@type', 'Course')
                ->where('seo.json_ld.1.name', 'AI Foundations — Skinbase Academy')
                ->where('seo.json_ld.1.isAccessibleForFree', true)
                ->where('seo.json_ld.1.educationalLevel', 'Beginner')
                ->where('seo.json_ld.1.hasCourseInstance.0.name', 'Course Lesson')
                ->where('seo.json_ld.2.@type', 'BreadcrumbList')
                ->where('seo.json_ld.2.itemListElement.1.name', 'Academy')
                ->where('seo.json_ld.2.itemListElement.2.name', 'Courses')
                ->where('sections.0.slug', 'getting-started')
                ->where('sections.0.lessons.0.slug', 'course-lesson')
                ->where('sections.0.lessons.0.order_num', 0)
                ->where('sections.0.lessons.0.cover_image', 'academy/lessons/covers/lesson-cover.webp'));
    }

    public function test_course_start_redirects_to_first_lesson_and_creates_enrollment(): void
    {
        $user = User::factory()->create();
        $course = AcademyCourse::query()->create([
            'title' => 'Workflow Course',
            'slug' => 'workflow-course',
            'excerpt' => 'A workflow course.',
            'access_level' => 'free',
            'difficulty' => 'beginner',
            'status' => 'published',
            'published_at' => now()->subMinute(),
        ]);

        $lesson = AcademyLesson::query()->create([
            'title' => 'First Workflow Lesson',
            'slug' => 'first-workflow-lesson',
            'content' => 'Start here',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        AcademyCourseLesson::query()->create([
            'course_id' => $course->id,
            'lesson_id' => $lesson->id,
            'order_num' => 0,
            'is_required' => true,
        ]);

        $this->actingAs($user)
            ->post(route('academy.courses.start', ['course' => $course->slug]))
            ->assertRedirect(route('academy.courses.lessons.show', ['course' => $course->slug, 'lesson' => $lesson->slug]));

        $this->assertDatabaseHas('academy_course_enrollments', [
            'user_id' => $user->id,
            'course_id' => $course->id,
            'status' => AcademyCourseEnrollment::STATUS_ACTIVE,
        ]);

        $this->actingAs($user)
            ->get(route('academy.courses.lessons.show', ['course' => $course->slug, 'lesson' => $lesson->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/Show')
                ->where('courseContext.slug', 'workflow-course')
                ->where('courseContext.completePayload.course_id', $course->id));
    }

    public function test_course_show_exposes_consistent_global_step_numbers_for_outline(): void
    {
        $course = AcademyCourse::query()->create([
            'title' => 'Ordered Course',
            'slug' => 'ordered-course',
            'excerpt' => 'Check course step order.',
            'access_level' => 'free',
            'difficulty' => 'beginner',
            'status' => 'published',
            'published_at' => now()->subMinute(),
        ]);

        $section = AcademyCourseSection::query()->create([
            'course_id' => $course->id,
            'title' => 'Module One',
            'slug' => 'module-one',
            'order_num' => 0,
            'is_visible' => true,
        ]);

        $introLesson = AcademyLesson::query()->create([
            'title' => 'Intro Lesson',
            'slug' => 'intro-lesson',
            'content' => 'Intro content',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'lesson_number' => 3,
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $sectionLesson = AcademyLesson::query()->create([
            'title' => 'Section Lesson',
            'slug' => 'section-lesson',
            'content' => 'Section content',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'lesson_number' => 1,
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        AcademyCourseLesson::query()->create([
            'course_id' => $course->id,
            'lesson_id' => $introLesson->id,
            'order_num' => 0,
            'is_required' => true,
        ]);

        AcademyCourseLesson::query()->create([
            'course_id' => $course->id,
            'section_id' => $section->id,
            'lesson_id' => $sectionLesson->id,
            'order_num' => 0,
            'is_required' => true,
        ]);

        $this->get(route('academy.courses.show', ['course' => $course->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/CoursesShow')
                ->where('unsectionedLessons.0.title', 'Intro Lesson')
                ->where('unsectionedLessons.0.course_step_number', 1)
                ->where('unsectionedLessons.0.course_step_label', 'Step 01')
                ->where('sections.0.lessons.0.title', 'Section Lesson')
                ->where('sections.0.lessons.0.course_step_number', 2)
                ->where('sections.0.lessons.0.course_step_label', 'Step 02')
                ->where('unsectionedLessons.0.formatted_lesson_number', 'Lesson 03')
                ->where('sections.0.lessons.0.formatted_lesson_number', 'Lesson 01'));
    }

    public function test_course_show_marks_completed_lessons_for_authenticated_user(): void
    {
        $user = User::factory()->create();

        $course = AcademyCourse::query()->create([
            'title' => 'Guided Course',
            'slug' => 'guided-course',
            'excerpt' => 'Track outline completion.',
            'access_level' => 'free',
            'difficulty' => 'beginner',
            'status' => 'published',
            'published_at' => now()->subMinute(),
        ]);

        $section = AcademyCourseSection::query()->create([
            'course_id' => $course->id,
            'title' => 'Module One',
            'slug' => 'module-one',
            'order_num' => 0,
            'is_visible' => true,
        ]);

        $lesson = AcademyLesson::query()->create([
            'title' => 'Completed Lesson',
            'slug' => 'completed-lesson',
            'content' => 'Lesson content',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        AcademyCourseLesson::query()->create([
            'course_id' => $course->id,
            'section_id' => $section->id,
            'lesson_id' => $lesson->id,
            'order_num' => 0,
            'is_required' => true,
        ]);

        AcademyLessonProgress::query()->create([
            'user_id' => $user->id,
            'lesson_id' => $lesson->id,
            'completed_at' => now(),
        ]);

        $this->actingAs($user)
            ->get(route('academy.courses.show', ['course' => $course->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/CoursesShow')
                ->where('course.progress.completedRequired', 1)
                ->where('sections.0.lessons.0.completed', true));
    }

    public function test_standalone_lesson_show_exposes_related_courses(): void
    {
        $lesson = AcademyLesson::query()->create([
            'title' => 'Shared Lesson',
            'slug' => 'shared-lesson',
            'content' => 'Shared content',
            'article_cover_image' => 'academy/lessons/covers/shared-lesson.webp',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $course = AcademyCourse::query()->create([
            'title' => 'Related Course',
            'slug' => 'related-course',
            'excerpt' => 'Course tied to this lesson.',
            'access_level' => 'free',
            'difficulty' => 'beginner',
            'status' => 'published',
            'published_at' => now()->subMinute(),
        ]);

        AcademyCourseLesson::query()->create([
            'course_id' => $course->id,
            'lesson_id' => $lesson->id,
            'order_num' => 0,
            'is_required' => true,
        ]);

        $this->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/Show')
                ->where('seo.json_ld.0.@type', 'ImageObject')
                ->where('seo.json_ld.0.creditText', 'Skinbase')
                ->where('seo.json_ld.1.@type', 'Article')
                ->where('seo.json_ld.1.headline', 'Shared Lesson — Skinbase Academy')
                ->where('seo.json_ld.1.articleSection', 'Academy')
                ->where('seo.json_ld.2.@type', 'BreadcrumbList')
                ->where('relatedCourses.0.slug', 'related-course'));
    }

    public function test_course_lesson_show_exposes_article_breadcrumb_and_image_license_schema(): void
    {
        $course = AcademyCourse::query()->create([
            'title' => 'AI Art Basics',
            'slug' => 'ai-art-basics',
            'excerpt' => 'Course excerpt',
            'access_level' => 'free',
            'difficulty' => 'beginner',
            'status' => 'published',
            'published_at' => now()->subMinute(),
        ]);

        $section = AcademyCourseSection::query()->create([
            'course_id' => $course->id,
            'title' => 'Foundations',
            'slug' => 'foundations',
            'order_num' => 0,
            'is_visible' => true,
        ]);

        $lesson = AcademyLesson::query()->create([
            'title' => 'What Is AI-Assisted Digital Art',
            'slug' => 'what-is-ai-assisted-digital-art',
            'excerpt' => 'Structured data test lesson.',
            'content' => 'Lesson body',
            'article_cover_image' => 'academy/lessons/covers/ai-assist.webp',
            'tags' => ['ai-art', 'workflow'],
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        AcademyCourseLesson::query()->create([
            'course_id' => $course->id,
            'section_id' => $section->id,
            'lesson_id' => $lesson->id,
            'order_num' => 0,
            'is_required' => true,
        ]);

        $this->get(route('academy.courses.lessons.show', ['course' => $course->slug, 'lesson' => $lesson->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/Show')
                ->where('seo.json_ld.0.@type', 'ImageObject')
                ->where('seo.json_ld.0.license', route('terms-of-service'))
                ->where('seo.json_ld.0.creditText', 'Skinbase')
                ->where('seo.json_ld.1.@type', 'Article')
                ->where('seo.json_ld.1.headline', 'What Is AI-Assisted Digital Art — AI Art Basics')
                ->where('seo.json_ld.1.articleSection', 'AI Art Basics')
                ->where('seo.json_ld.1.keywords.0', 'ai-art')
                ->where('seo.json_ld.2.@type', 'BreadcrumbList')
                ->where('seo.json_ld.2.itemListElement.3.name', 'AI Art Basics')
                ->where('seo.json_ld.2.itemListElement.4.name', 'What Is AI-Assisted Digital Art'));
    }

    public function test_free_lesson_is_visible_to_guest(): void
    {
        $lesson = AcademyLesson::query()->create([
            'title' => 'Free Lesson',
            'slug' => 'free-lesson',
            'excerpt' => 'Visible to guests.',
            'content' => 'Free lesson content',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'article_cover_image' => 'academy/lessons/covers/article-cover.webp',
            'tags' => ['workflow', 'publishing'],
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $this->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/Show')
                ->where('item.locked', false)
                ->where('item.content', 'Free lesson content')
                ->where('item.article_cover_image', 'academy/lessons/covers/article-cover.webp')
                ->where('item.tags.0', 'workflow')
                ->where('item.tags.1', 'publishing')
                ->where('item.article_cover_image_url', fn (?string $value) => is_string($value) && str_contains($value, 'academy/lessons/covers/article-cover.webp')));
    }

    public function test_creator_lesson_is_locked_for_regular_user(): void
    {
        $lesson = AcademyLesson::query()->create([
            'title' => 'Creator Lesson',
            'slug' => 'creator-lesson',
            'excerpt' => 'Preview only',
            'content' => 'Creator only lesson content',
            'difficulty' => 'intermediate',
            'access_level' => 'creator',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $user = User::factory()->create();

        $this->actingAs($user)
            ->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
            ->assertOk()
            ->assertDontSee('Creator only lesson content')
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->where('item.locked', true)
                ->where('item.content', null));
    }

    public function test_pro_lesson_is_locked_for_creator_user(): void
    {
        $lesson = AcademyLesson::query()->create([
            'title' => 'Pro Lesson',
            'slug' => 'pro-lesson',
            'excerpt' => 'Pro preview',
            'content' => 'Pro only lesson content',
            'difficulty' => 'pro',
            'access_level' => 'pro',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $creator = User::factory()->create(['role' => 'academy_creator']);

        $this->actingAs($creator)
            ->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
            ->assertOk()
            ->assertDontSee('Pro only lesson content')
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->where('item.locked', true)
                ->where('item.content', null));
    }

    public function test_premium_prompt_full_text_is_not_exposed_to_unauthorized_users(): void
    {
        $prompt = AcademyPromptTemplate::query()->create([
            'title' => 'Creator Prompt',
            'slug' => 'creator-prompt',
            'excerpt' => 'Locked preview.',
            'prompt' => 'SECRET PREMIUM PROMPT STRING',
            'negative_prompt' => 'SECRET NEGATIVE STRING',
            'usage_notes' => 'SECRET WORKFLOW NOTE',
            'difficulty' => 'beginner',
            'access_level' => 'creator',
            'tool_notes' => [[
                'display_type' => 'soft studio version',
                'provider' => 'ChatGPT',
                'model_name' => '4o Image',
                'image_path' => 'academy/lessons/body/cc/dd/chatgpt-comparison.webp',
                'settings' => 'SECRET SETTINGS STRING',
                'best_for' => 'SECRET BEST FOR STRING',
                'active' => true,
            ]],
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $this->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
            ->assertOk()
            ->assertDontSee('SECRET PREMIUM PROMPT STRING')
            ->assertDontSee('SECRET NEGATIVE STRING')
            ->assertDontSee('SECRET WORKFLOW NOTE')
            ->assertDontSee('SECRET SETTINGS STRING')
            ->assertDontSee('SECRET BEST FOR STRING')
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->where('item.locked', true)
                ->where('item.prompt', null)
                ->where('item.negative_prompt', null)
                ->where('item.access_requirement', 'Requires Creator or Pro access.')
                ->where('item.unlock_heading', 'Unlock the full Creator prompt.')
                ->where('item.tool_notes', [])
                ->where('item.public_examples.0.provider', 'ChatGPT')
                ->where('item.public_examples.0.model_name', '4o Image')
                ->where('item.public_examples.0.image_path', 'academy/lessons/body/cc/dd/chatgpt-comparison.webp')
                ->where('seo.json_ld.0.isAccessibleForFree', false)
                ->where('seo.json_ld.0.hasPart.cssSelector', '.academy-paywalled-content'));

        $version = app(HandleInertiaRequests::class)
            ->version(Request::create(route('academy.prompts.show', ['slug' => $prompt->slug]), 'GET'));

        $this->withHeaders([
            'X-Inertia' => 'true',
            'X-Requested-With' => 'XMLHttpRequest',
            'X-Inertia-Version' => $version,
        ])->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
            ->assertOk()
            ->assertJsonPath('props.item.locked', true)
            ->assertJsonPath('props.item.prompt', null)
            ->assertJsonPath('props.item.negative_prompt', null)
            ->assertJsonPath('props.item.tool_notes', [])
            ->assertJsonPath('props.item.public_examples.0.provider', 'ChatGPT')
            ->assertDontSee('SECRET PREMIUM PROMPT STRING')
            ->assertDontSee('SECRET NEGATIVE STRING')
            ->assertDontSee('SECRET SETTINGS STRING');
    }

    public function test_authorized_user_can_view_premium_prompt(): void
    {
        $prompt = AcademyPromptTemplate::query()->create([
            'title' => 'Creator Prompt',
            'slug' => 'creator-prompt-allowed',
            'excerpt' => 'Full prompt visible.',
            'prompt' => 'VISIBLE PREMIUM PROMPT',
            'negative_prompt' => 'VISIBLE NEGATIVE PROMPT',
            'tool_notes' => [[
                'provider' => 'ChatGPT',
                'model_name' => '4o Image',
                'image_path' => 'academy/lessons/body/cc/dd/chatgpt-comparison.webp',
                'settings' => 'ChatGPT image generation in 16:9 with cinematic mode.',
                'best_for' => 'Fast ideation and art direction.',
                'score' => 8,
                'active' => true,
            ]],
            'difficulty' => 'beginner',
            'access_level' => 'creator',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $creator = User::factory()->create(['role' => 'academy_creator']);

        $this->actingAs($creator)
            ->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
            ->assertOk()
            ->assertSee('VISIBLE PREMIUM PROMPT')
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->where('item.locked', false)
                ->where('item.prompt', 'VISIBLE PREMIUM PROMPT')
                ->where('item.public_examples.0.provider', 'ChatGPT')
                ->where('item.tool_notes.0.provider', 'ChatGPT')
                ->where('item.tool_notes.0.model_name', '4o Image')
                ->where('item.tool_notes.0.image_path', 'academy/lessons/body/cc/dd/chatgpt-comparison.webp')
                ->where('item.tool_notes.0.score', 8)
                ->where('seo.json_ld.0.isAccessibleForFree', false)
                ->where('seo.json_ld.0.hasPart.cssSelector', '.academy-paywalled-content'));
    }

    public function test_prompt_payload_exposes_responsive_preview_and_comparison_images(): void
    {
        config()->set('uploads.object_storage.disk', 's3');
        Storage::fake('s3');

        Storage::disk('s3')->put('academy-prompts/previews/sticker-pack.webp', 'preview');
        Storage::disk('s3')->put('academy-prompts/previews/sticker-pack-thumb.webp', 'preview-thumb');
        Storage::disk('s3')->put('academy-prompts/previews/sticker-pack-md.webp', 'preview-medium');
        Storage::disk('s3')->put('academy/lessons/body/cc/dd/chatgpt-comparison.webp', 'comparison');
        Storage::disk('s3')->put('academy/lessons/body/cc/dd/chatgpt-comparison-thumb.webp', 'comparison-thumb');
        Storage::disk('s3')->put('academy/lessons/body/cc/dd/chatgpt-comparison-md.webp', 'comparison-medium');

        $prompt = AcademyPromptTemplate::query()->create([
            'title' => 'Responsive Prompt Media',
            'slug' => 'responsive-prompt-media',
            'excerpt' => 'Prompt with responsive preview assets.',
            'prompt' => 'Create a chibi emoji sticker collection with bright outlines.',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'preview_image' => 'academy-prompts/previews/sticker-pack.webp',
            'tool_notes' => [[
                'display_type' => 'sticker pack',
                'provider' => 'ChatGPT',
                'model_name' => '4o Image',
                'image_path' => 'academy/lessons/body/cc/dd/chatgpt-comparison.webp',
                'thumb_path' => 'academy/lessons/body/cc/dd/chatgpt-comparison-thumb.webp',
                'settings' => 'Square canvas, bold outline, soft pastel background.',
                'best_for' => 'Sticker-ready mascot packs.',
                'active' => true,
            ]],
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $this->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->where('item.preview_image_thumb', fn ($value) => is_string($value) && str_contains($value, 'academy-prompts/previews/sticker-pack-thumb.webp'))
                ->where('item.preview_image_srcset', fn ($value) => is_string($value) && str_contains($value, 'academy-prompts/previews/sticker-pack-thumb.webp 480w') && str_contains($value, 'academy-prompts/previews/sticker-pack-md.webp 960w'))
                ->where('item.public_examples.0.thumb_path', 'academy/lessons/body/cc/dd/chatgpt-comparison-thumb.webp')
                ->where('item.public_examples.0.image_srcset', fn ($value) => is_string($value) && str_contains($value, 'chatgpt-comparison-thumb.webp 480w') && str_contains($value, 'chatgpt-comparison-md.webp 960w'))
                ->where('item.tool_notes.0.thumb_path', 'academy/lessons/body/cc/dd/chatgpt-comparison-thumb.webp')
                ->where('item.tool_notes.0.image_srcset', fn ($value) => is_string($value) && str_contains($value, 'chatgpt-comparison-thumb.webp 480w') && str_contains($value, 'chatgpt-comparison-md.webp 960w')));
    }

    public function test_authorized_user_receives_active_advanced_prompt_metadata(): void
    {
        $prompt = AcademyPromptTemplate::query()->create([
            'title' => 'Advanced Creator Prompt',
            'slug' => 'advanced-creator-prompt',
            'excerpt' => 'Full prompt visible.',
            'prompt' => 'VISIBLE PREMIUM PROMPT FOR [CITY_NAME]',
            'negative_prompt' => 'VISIBLE NEGATIVE PROMPT',
            'documentation' => [
                'summary' => 'Advanced summary visible to everyone.',
                'how_to_use' => ['Collect data', 'Prepare prompt'],
                'best_for' => ['city wallpapers'],
            ],
            'placeholders' => [
                [
                    'key' => 'CITY_NAME',
                    'label' => 'City name',
                    'required' => true,
                    'example' => 'Paris',
                    'type' => 'text',
                ],
            ],
            'helper_prompts' => [
                [
                    'title' => 'Collect city data',
                    'description' => 'Gather landmark and climate data.',
                    'prompt' => 'Collect city data for [CITY_NAME].',
                    'expected_output' => 'json',
                    'active' => true,
                ],
                [
                    'title' => 'Inactive helper',
                    'description' => 'Should stay hidden publicly.',
                    'prompt' => 'Hidden helper prompt.',
                    'expected_output' => 'text',
                    'active' => false,
                ],
            ],
            'prompt_variants' => [
                [
                    'title' => 'Image-safe version',
                    'slug' => 'image-safe-version',
                    'description' => 'Safer for image models.',
                    'prompt' => 'VISIBLE IMAGE SAFE PROMPT',
                    'negative_prompt' => 'VISIBLE VARIANT NEGATIVE',
                    'recommended' => true,
                    'recommended_for' => ['general image generation'],
                    'risk_notes' => ['Icons may still be abstract'],
                    'active' => true,
                ],
                [
                    'title' => 'Inactive variant',
                    'slug' => 'inactive-variant',
                    'description' => 'Should stay hidden publicly.',
                    'prompt' => 'HIDDEN VARIANT PROMPT',
                    'active' => false,
                ],
            ],
            'difficulty' => 'beginner',
            'access_level' => 'creator',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $creator = User::factory()->create(['role' => 'academy_creator']);

        $this->actingAs($creator)
            ->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->where('item.locked', false)
                ->where('item.documentation.summary', 'Advanced summary visible to everyone.')
                ->where('item.placeholders.0.key', 'CITY_NAME')
                ->where('item.has_placeholder_inputs', true)
                ->where('item.has_helper_prompts', true)
                ->where('item.has_prompt_variants', true)
                ->has('item.helper_prompts', 1)
                ->where('item.helper_prompts.0.title', 'Collect city data')
                ->has('item.prompt_variants', 1)
                ->where('item.prompt_variants.0.title', 'Image-safe version')
                );
    }

    public function test_locked_prompt_still_exposes_documentation_and_placeholders_but_hides_helper_prompts_and_variants(): void
    {
        $prompt = AcademyPromptTemplate::query()->create([
            'title' => 'Locked Advanced Prompt',
            'slug' => 'locked-advanced-prompt',
            'excerpt' => 'Locked prompt with public guidance.',
            'prompt' => 'SECRET ADVANCED PROMPT FOR [CITY_NAME]',
            'negative_prompt' => 'SECRET ADVANCED NEGATIVE',
            'documentation' => [
                'summary' => 'Public-facing overview.',
                'how_to_use' => ['Choose a city', 'Collect climate data'],
                'tips' => ['Use real data'],
            ],
            'placeholders' => [
                [
                    'key' => 'CITY_NAME',
                    'label' => 'City name',
                    'required' => true,
                    'example' => 'Paris',
                    'type' => 'text',
                ],
            ],
            'helper_prompts' => [
                [
                    'title' => 'Collect city data',
                    'description' => 'Hidden behind access.',
                    'prompt' => 'SECRET HELPER PROMPT',
                    'expected_output' => 'json',
                    'active' => true,
                ],
            ],
            'prompt_variants' => [
                [
                    'title' => 'Image-safe version',
                    'description' => 'Hidden behind access.',
                    'prompt' => 'SECRET VARIANT PROMPT',
                    'negative_prompt' => 'SECRET VARIANT NEGATIVE',
                    'active' => true,
                ],
            ],
            'difficulty' => 'beginner',
            'access_level' => 'creator',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $this->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
            ->assertOk()
            ->assertDontSee('SECRET ADVANCED PROMPT')
            ->assertDontSee('SECRET HELPER PROMPT')
            ->assertDontSee('SECRET VARIANT PROMPT')
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->where('item.locked', true)
                ->where('item.prompt', null)
                ->where('item.documentation.summary', 'Public-facing overview.')
                ->where('item.placeholders.0.key', 'CITY_NAME')
                ->where('item.has_placeholder_inputs', true)
                ->where('item.has_helper_prompts', true)
                ->where('item.has_prompt_variants', true)
                ->where('item.helper_prompts', [])
                ->where('item.prompt_variants', []));
    }

    public function test_prompt_without_placeholder_tokens_marks_placeholder_inputs_as_hidden(): void
    {
        $prompt = AcademyPromptTemplate::query()->create([
            'title' => 'Descriptor Only Prompt',
            'slug' => 'descriptor-only-prompt',
            'excerpt' => 'Has descriptive placeholder cards but no input tokens in the prompt.',
            'prompt' => 'Create a calm Roman rooftop garden scene at sunrise.',
            'documentation' => [
                'summary' => 'A fixed prompt with no user-substituted variables.',
            ],
            'placeholders' => [
                [
                    'key' => 'CITY_STYLE',
                    'label' => 'City style',
                    'description' => 'Editorial guidance only.',
                    'example' => 'Historic Rome rooftop terrace with distant domes',
                    'type' => 'string',
                ],
            ],
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $this->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->where('item.placeholders.0.key', 'CITY_STYLE')
                ->where('item.has_placeholder_inputs', false));
    }

    public function test_public_lesson_payload_includes_active_ai_comparison_block_and_hides_inactive_results(): void
    {
        $lesson = AcademyLesson::query()->create([
            'title' => 'Free Lesson With Comparison',
            'slug' => 'free-lesson-with-comparison',
            'excerpt' => 'Visible to guests.',
            'content' => 'Free lesson content',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);
        $block = AcademyLessonBlock::query()->create([
            'lesson_id' => $lesson->id,
            'type' => 'ai_comparison',
            'title' => 'Same Prompt, Different AI Models',
            'payload' => [
                'title' => 'Same Prompt, Different AI Models',
                'intro' => 'We used the same prompt in multiple tools.',
                'prompt' => 'A peaceful fantasy forest wallpaper.',
                'negative_prompt' => 'text, watermark',
                'aspect_ratio' => '16:9',
                'criteria' => ['Composition', 'Lighting'],
            ],
            'sort_order' => 0,
            'active' => true,
        ]);
        AcademyAiComparisonResult::query()->create([
            'lesson_block_id' => $block->id,
            'provider' => 'OpenAI',
            'model_name' => 'ChatGPT Images',
            'image_path' => 'academy/lessons/body/aa/bb/example.webp',
            'strengths' => 'Strong composition',
            'score' => 9,
            'sort_order' => 0,
            'active' => true,
        ]);
        AcademyAiComparisonResult::query()->create([
            'lesson_block_id' => $block->id,
            'provider' => 'Google',
            'model_name' => 'Gemini',
            'image_path' => 'academy/lessons/body/aa/bb/example-2.webp',
            'score' => 7,
            'sort_order' => 1,
            'active' => false,
        ]);

        $this->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/Show')
                ->where('item.blocks.0.payload.prompt', 'A peaceful fantasy forest wallpaper.')
                ->has('item.blocks.0.comparison_results', 1)
                ->where('item.blocks.0.comparison_results.0.model_name', 'ChatGPT Images'));
    }

    public function test_public_lesson_with_sparse_ai_comparison_block_still_renders_payload(): void
    {
        $lesson = AcademyLesson::query()->create([
            'title' => 'Sparse Comparison Lesson',
            'slug' => 'sparse-comparison-lesson',
            'excerpt' => 'Sparse block test.',
            'content' => 'Free lesson content',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);
        AcademyLessonBlock::query()->create([
            'lesson_id' => $lesson->id,
            'type' => 'ai_comparison',
            'title' => 'Prompt only block',
            'payload' => [
                'title' => 'Prompt only block',
                'prompt' => 'A fantasy forest at sunrise.',
            ],
            'sort_order' => 0,
            'active' => true,
        ]);

        $this->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->has('item.blocks', 1)
                ->where('item.blocks.0.payload.prompt', 'A fantasy forest at sunrise.')
                ->has('item.blocks.0.comparison_results', 0));
    }

    public function test_lessons_index_sorts_by_course_order_and_exposes_lesson_labels(): void
    {
        AcademyLesson::query()->create([
            'title' => 'Later Lesson',
            'slug' => 'later-lesson',
            'lesson_number' => 2,
            'course_order' => 2,
            'series_name' => 'AI Art Basics',
            'content' => 'Later content',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subDays(3),
        ]);

        AcademyLesson::query()->create([
            'title' => 'First Lesson',
            'slug' => 'first-lesson',
            'lesson_number' => 1,
            'course_order' => 1,
            'series_name' => 'AI Art Basics',
            'content' => 'First content',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'article_cover_image' => 'academy/lessons/covers/shared-lesson.webp',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $this->get(route('academy.lessons.index'))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/List')
                ->where('items.data.0.slug', 'first-lesson')
                ->where('items.data.0.formatted_lesson_number', 'Lesson 01')
                ->where('items.data.0.lesson_label', 'AI Art Basics · Lesson 01')
                ->where('items.data.1.slug', 'later-lesson'));
    }

    public function test_lesson_show_exposes_ordered_previous_and_next_navigation_within_series(): void
    {
        $publishMoment = Carbon::parse('2026-01-01 10:00:00');

        $previous = AcademyLesson::query()->create([
            'title' => 'Lesson One',
            'slug' => 'lesson-one',
            'lesson_number' => 1,
            'course_order' => 1,
            'series_name' => 'AI Art Basics',
            'content' => 'Lesson one body',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => $publishMoment->copy(),
        ]);

        $current = AcademyLesson::query()->create([
            'title' => 'Lesson Two',
            'slug' => 'lesson-two',
            'lesson_number' => 2,
            'course_order' => 2,
            'series_name' => 'AI Art Basics',
            'content' => 'Lesson two body',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => $publishMoment->copy()->addMinute(),
        ]);

        $next = AcademyLesson::query()->create([
            'title' => 'Lesson Three',
            'slug' => 'lesson-three',
            'lesson_number' => 3,
            'course_order' => 3,
            'series_name' => 'AI Art Basics',
            'content' => 'Lesson three body',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => $publishMoment->copy()->addMinutes(2),
        ]);

        AcademyLesson::query()->create([
            'title' => 'Other Series Lesson',
            'slug' => 'other-series-lesson',
            'lesson_number' => 99,
            'course_order' => 99,
            'series_name' => 'Other Series',
            'content' => 'Other series body',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => $publishMoment->copy()->addMinutes(3),
        ]);

        $this->get(route('academy.lessons.show', ['slug' => $current->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/Show')
                ->where('item.lesson_label', 'AI Art Basics · Lesson 02')
                ->where('previousLesson.slug', $previous->slug)
                ->where('nextLesson.slug', $next->slug));
    }

    public function test_lesson_show_falls_back_to_category_navigation_when_series_name_is_missing(): void
    {
        $categoryId = DB::table('academy_categories')->insertGetId([
            'type' => 'lesson',
            'name' => 'Prompting Basics',
            'slug' => 'prompting-basics',
            'order_num' => 1,
            'active' => true,
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        AcademyLesson::query()->create([
            'title' => 'Category Lesson One',
            'slug' => 'category-lesson-one',
            'category_id' => $categoryId,
            'lesson_number' => 1,
            'course_order' => 1,
            'content' => 'One',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subDays(2),
        ]);

        $current = AcademyLesson::query()->create([
            'title' => 'Category Lesson Two',
            'slug' => 'category-lesson-two',
            'category_id' => $categoryId,
            'lesson_number' => 2,
            'course_order' => 2,
            'content' => 'Two',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subDay(),
        ]);

        $this->get(route('academy.lessons.show', ['slug' => $current->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->where('previousLesson.slug', 'category-lesson-one')
                ->where('nextLesson', null));
    }

    public function test_lesson_payload_hides_label_when_lesson_number_is_missing(): void
    {
        $lesson = AcademyLesson::query()->create([
            'title' => 'Unnumbered Lesson',
            'slug' => 'unnumbered-lesson',
            'series_name' => 'AI Art Basics',
            'content' => 'Lesson body',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $this->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->where('item.formatted_lesson_number', null)
                ->where('item.lesson_label', null));
    }

    public function test_logged_in_user_can_mark_lesson_completed(): void
    {
        $lesson = AcademyLesson::query()->create([
            'title' => 'Trackable Lesson',
            'slug' => 'trackable-lesson',
            'content' => 'Track this lesson',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $user = User::factory()->create();

        $this->actingAs($user)
            ->postJson(route('academy.lessons.complete', ['lesson' => $lesson->id]))
            ->assertOk()
            ->assertJsonPath('completed', true);

        $this->assertDatabaseHas('academy_lesson_progress', [
            'lesson_id' => $lesson->id,
            'user_id' => $user->id,
        ]);
    }

    public function test_logged_in_user_can_save_and_unsave_prompt(): void
    {
        $prompt = AcademyPromptTemplate::query()->create([
            'title' => 'Free Prompt',
            'slug' => 'free-prompt',
            'prompt' => 'Save me',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $user = User::factory()->create();

        $this->actingAs($user)
            ->postJson(route('academy.prompts.save', ['prompt' => $prompt->id]))
            ->assertOk()
            ->assertJsonPath('saved', true);

        $this->assertDatabaseHas('academy_saved_prompts', [
            'prompt_template_id' => $prompt->id,
            'user_id' => $user->id,
        ]);

        $this->actingAs($user)
            ->deleteJson(route('academy.prompts.unsave', ['prompt' => $prompt->id]))
            ->assertOk()
            ->assertJsonPath('saved', false);

        $this->assertDatabaseMissing('academy_saved_prompts', [
            'prompt_template_id' => $prompt->id,
            'user_id' => $user->id,
        ]);
    }

    public function test_prompt_library_index_exposes_breadcrumbs_and_discovery_payloads(): void
    {
        $featured = AcademyPromptTemplate::query()->create([
            'title' => 'Featured Prompt',
            'slug' => 'featured-prompt',
            'excerpt' => 'Featured prompt excerpt.',
            'prompt' => 'Featured prompt body',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'featured' => true,
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $popular = AcademyPromptTemplate::query()->create([
            'title' => 'Popular Prompt',
            'slug' => 'popular-prompt',
            'excerpt' => 'Popular prompt excerpt.',
            'prompt' => 'Popular prompt body',
            'difficulty' => 'intermediate',
            'access_level' => 'free',
            'active' => true,
            'published_at' => now()->subMinutes(2),
        ]);

        AcademyContentMetricDaily::query()->create([
            'date' => now()->toDateString(),
            'content_type' => 'academy_prompt',
            'content_id' => $popular->id,
            'views' => 42,
            'prompt_copies' => 9,
            'popularity_score' => 88.5,
        ]);

        Cache::flush();

        $this->get(route('academy.prompts.index'))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/List')
                ->where('breadcrumbs.0.label', 'Academy')
                ->where('breadcrumbs.0.href', route('academy.index'))
                ->where('breadcrumbs.1.label', 'Prompt Library')
                ->where('coursesUrl', route('academy.courses.index'))
                ->where('packsUrl', route('academy.packs.index'))
                ->where('featuredPrompts.0.slug', $featured->slug)
                ->where('featuredPrompts.0.spotlight.eyebrow', 'Featured pick')
                ->where('popularPrompts.0.slug', $popular->slug)
                ->where('popularPrompts.0.spotlight.eyebrow', '9 copies this month')
                ->where('academyAccess.signedIn', false)
                ->where('academyAccess.status', 'guest')
                ->where('academyAccess.billingUrl', route('academy.pricing')));
    }

    public function test_prompt_library_index_exposes_current_access_summary_for_grace_period_subscription(): void
    {
        config()->set('academy_billing.plans', [
            'creator_monthly' => [
                'tier' => 'creator',
                'stripe_price_id' => 'price_creator_test',
            ],
        ]);

        $user = User::factory()->create();
        $subscription = Subscription::query()->create([
            'user_id' => $user->id,
            'type' => 'academy',
            'stripe_id' => 'sub_creator_test',
            'stripe_status' => 'active',
            'stripe_price' => 'price_creator_test',
            'quantity' => 1,
            'ends_at' => now()->addDays(12),
        ]);

        SubscriptionItem::query()->create([
            'subscription_id' => $subscription->id,
            'stripe_id' => 'si_creator_test',
            'stripe_product' => 'prod_creator_test',
            'stripe_price' => 'price_creator_test',
            'quantity' => 1,
        ]);

        $this->actingAs($user)
            ->get(route('academy.prompts.index'))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/List')
                ->where('academyAccess.signedIn', true)
                ->where('academyAccess.tier', 'creator')
                ->where('academyAccess.tierLabel', 'Creator')
                ->where('academyAccess.status', 'grace_period')
                ->where('academyAccess.statusLabel', 'Cancels soon')
                ->where('academyAccess.dateLabel', 'Access ends')
                ->where('academyAccess.renewsAutomatically', false)
                ->where('academyAccess.source', 'subscription')
                ->where('academyAccess.billingUrl', route('academy.billing.account'))
                ->where('academyAccess.expiresAt', $subscription->ends_at?->toISOString()));
    }

    public function test_popular_prompts_page_displays_ranked_prompt_payloads(): void
    {
        $featured = AcademyPromptTemplate::query()->create([
            'title' => 'Featured Prompt',
            'slug' => 'featured-popular-page-prompt',
            'excerpt' => 'Featured prompt excerpt.',
            'prompt' => 'Featured prompt body',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'featured' => true,
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $topPrompt = AcademyPromptTemplate::query()->create([
            'title' => 'Top Prompt',
            'slug' => 'top-prompt',
            'excerpt' => 'Top prompt excerpt.',
            'prompt' => 'Top prompt body',
            'difficulty' => 'advanced',
            'access_level' => 'free',
            'active' => true,
            'published_at' => now()->subMinutes(2),
        ]);

        $runnerUpPrompt = AcademyPromptTemplate::query()->create([
            'title' => 'Runner Up Prompt',
            'slug' => 'runner-up-prompt',
            'excerpt' => 'Runner up prompt excerpt.',
            'prompt' => 'Runner up prompt body',
            'difficulty' => 'intermediate',
            'access_level' => 'free',
            'active' => true,
            'published_at' => now()->subMinutes(3),
        ]);

        AcademyContentMetricDaily::query()->create([
            'date' => now()->toDateString(),
            'content_type' => 'academy_prompt',
            'content_id' => $topPrompt->id,
            'views' => 74,
            'prompt_copies' => 11,
            'popularity_score' => 128.7,
        ]);

        AcademyContentMetricDaily::query()->create([
            'date' => now()->toDateString(),
            'content_type' => 'academy_prompt',
            'content_id' => $runnerUpPrompt->id,
            'views' => 48,
            'prompt_copies' => 5,
            'popularity_score' => 91.4,
        ]);

        Cache::flush();

        $this->get(route('academy.prompts.popular'))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/List')
                ->where('promptView', 'popular')
                ->where('popularPeriod.value', '30d')
                ->where('breadcrumbs.2.label', 'Popular Prompts')
                ->where('promptLibraryUrl', route('academy.prompts.index'))
                ->where('items.data.0.slug', $topPrompt->slug)
                ->where('items.data.0.ranking.rank', 1)
                ->where('items.data.0.ranking.prompt_copies', 11)
                ->where('items.data.1.slug', $runnerUpPrompt->slug)
                ->where('items.data.1.ranking.rank', 2)
                ->where('popularPeriods.0.value', '7d')
                ->where('popularPeriods.1.active', true)
                ->where('featuredPrompts.0.slug', $featured->slug));
    }

    public function test_popular_prompts_page_can_filter_to_last_seven_days(): void
    {
        $recentPrompt = AcademyPromptTemplate::query()->create([
            'title' => 'Recent Prompt',
            'slug' => 'recent-prompt',
            'excerpt' => 'Recent prompt excerpt.',
            'prompt' => 'Recent prompt body',
            'difficulty' => 'advanced',
            'access_level' => 'free',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $olderPrompt = AcademyPromptTemplate::query()->create([
            'title' => 'Older Prompt',
            'slug' => 'older-prompt',
            'excerpt' => 'Older prompt excerpt.',
            'prompt' => 'Older prompt body',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'active' => true,
            'published_at' => now()->subMinutes(2),
        ]);

        AcademyContentMetricDaily::query()->create([
            'date' => now()->toDateString(),
            'content_type' => 'academy_prompt',
            'content_id' => $recentPrompt->id,
            'views' => 31,
            'prompt_copies' => 7,
            'popularity_score' => 79.5,
        ]);

        AcademyContentMetricDaily::query()->create([
            'date' => now()->subDays(20)->toDateString(),
            'content_type' => 'academy_prompt',
            'content_id' => $olderPrompt->id,
            'views' => 200,
            'prompt_copies' => 22,
            'popularity_score' => 240.1,
        ]);

        Cache::flush();

        $this->get(route('academy.prompts.popular', ['period' => '7d']))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/List')
                ->where('popularPeriod.value', '7d')
                ->where('popularPeriod.label', '7 days')
                ->where('items.data.0.slug', $recentPrompt->slug)
                ->where('items.data.0.spotlight.eyebrow', '7 copies in the last 7 days')
                ->missing('items.data.1')
                ->where('popularPeriods.0.active', true)
                ->where('popularPeriods.1.active', false));
    }

    public function test_prompt_pack_index_does_not_include_nested_prompts_until_pack_detail(): void
    {
        $prompt = AcademyPromptTemplate::query()->create([
            'title' => 'Pack Prompt',
            'slug' => 'pack-prompt',
            'excerpt' => 'Prompt excerpt.',
            'prompt' => 'Pack prompt body',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $pack = AcademyPromptPack::query()->create([
            'title' => 'Starter Prompt Pack',
            'slug' => 'starter-prompt-pack',
            'excerpt' => 'Pack excerpt.',
            'description' => 'Pack description.',
            'access_level' => 'free',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $pack->prompts()->attach($prompt->id, ['order_num' => 0]);

        $this->get(route('academy.packs.index'))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/List')
                ->where('items.data.0.slug', 'starter-prompt-pack')
                ->where('items.data.0.prompts', [])
                ->where('analytics.contentType', 'academy_prompt_pack_library')
            );
    }

    public function test_logged_in_user_can_submit_artwork_to_active_challenge(): void
    {
        $user = User::factory()->create();
        $artwork = Artwork::factory()->create([
            'user_id' => $user->id,
            'is_public' => true,
            'is_approved' => true,
            'published_at' => now()->subMinute(),
        ]);

        $challenge = AcademyChallenge::query()->create([
            'title' => 'Open Challenge',
            'slug' => 'open-challenge',
            'access_level' => 'free',
            'status' => 'active',
            'active' => true,
            'starts_at' => now()->subHour(),
            'ends_at' => now()->addDay(),
        ]);

        $this->actingAs($user)
            ->post(route('academy.challenges.submit.store', ['slug' => $challenge->slug]), [
                'artwork_id' => $artwork->id,
                'prompt_used' => 'Prompt used',
                'workflow_notes' => 'Workflow notes',
                'ai_tool_used' => 'ComfyUI',
                'is_ai_generated' => true,
                'is_ai_assisted' => true,
            ])
            ->assertRedirect(route('academy.challenges.show', ['slug' => $challenge->slug]));

        $this->assertDatabaseHas('academy_challenge_submissions', [
            'challenge_id' => $challenge->id,
            'user_id' => $user->id,
            'artwork_id' => $artwork->id,
        ]);
    }

    public function test_guest_cannot_view_creator_or_pro_lesson_content(): void
    {
        $creatorLesson = AcademyLesson::query()->create([
            'title' => 'Creator Lesson',
            'slug' => 'guest-creator-lesson',
            'excerpt' => 'Creator preview',
            'content' => 'CREATOR SECRET LESSON BODY',
            'difficulty' => 'advanced',
            'access_level' => 'creator',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $proLesson = AcademyLesson::query()->create([
            'title' => 'Pro Lesson',
            'slug' => 'guest-pro-lesson',
            'excerpt' => 'Pro preview',
            'content' => 'PRO SECRET LESSON BODY',
            'difficulty' => 'pro',
            'access_level' => 'pro',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        foreach ([$creatorLesson, $proLesson] as $lesson) {
            $this->get(route('academy.lessons.show', ['slug' => $lesson->slug]))
                ->assertOk()
                ->assertDontSee($lesson->content)
                ->assertInertia(fn (AssertableInertia $page) => $page
                    ->where('item.locked', true)
                    ->where('item.content', null));
        }
    }

    public function test_logged_in_free_user_cannot_view_creator_or_pro_prompt_content(): void
    {
        $creatorPrompt = AcademyPromptTemplate::query()->create([
            'title' => 'Creator Prompt',
            'slug' => 'free-user-creator-prompt',
            'excerpt' => 'Creator preview.',
            'prompt' => 'CREATOR PREMIUM PROMPT',
            'negative_prompt' => 'CREATOR PREMIUM NEGATIVE',
            'difficulty' => 'beginner',
            'access_level' => 'creator',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $proPrompt = AcademyPromptTemplate::query()->create([
            'title' => 'Pro Prompt',
            'slug' => 'free-user-pro-prompt',
            'excerpt' => 'Pro preview.',
            'prompt' => 'PRO PREMIUM PROMPT',
            'negative_prompt' => 'PRO PREMIUM NEGATIVE',
            'difficulty' => 'pro',
            'access_level' => 'pro',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $user = User::factory()->create();

        foreach ([$creatorPrompt, $proPrompt] as $prompt) {
            $this->actingAs($user)
                ->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
                ->assertOk()
                ->assertDontSee($prompt->prompt)
                ->assertDontSee((string) $prompt->negative_prompt)
                ->assertInertia(fn (AssertableInertia $page) => $page
                    ->where('item.locked', true)
                    ->where('item.access_requirement', $prompt->access_level === 'pro' ? 'Requires Pro access.' : 'Requires Creator or Pro access.')
                    ->where('item.prompt', null)
                    ->where('item.negative_prompt', null));
        }
    }

    public function test_creator_user_can_view_creator_prompt_but_not_pro_prompt(): void
    {
        $creatorPrompt = AcademyPromptTemplate::query()->create([
            'title' => 'Creator Prompt',
            'slug' => 'creator-visible-prompt',
            'excerpt' => 'Creator prompt.',
            'prompt' => 'CREATOR ACCESS PROMPT',
            'negative_prompt' => 'CREATOR ACCESS NEGATIVE',
            'difficulty' => 'intermediate',
            'access_level' => 'creator',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $proPrompt = AcademyPromptTemplate::query()->create([
            'title' => 'Pro Prompt',
            'slug' => 'creator-locked-pro-prompt',
            'excerpt' => 'Pro prompt.',
            'prompt' => 'PRO ONLY ACCESS PROMPT',
            'negative_prompt' => 'PRO ONLY ACCESS NEGATIVE',
            'difficulty' => 'pro',
            'access_level' => 'pro',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $creator = User::factory()->create(['role' => 'academy_creator']);

        $this->actingAs($creator)
            ->get(route('academy.prompts.show', ['slug' => $creatorPrompt->slug]))
            ->assertOk()
            ->assertSee('CREATOR ACCESS PROMPT')
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->where('item.locked', false)
                ->where('item.prompt', 'CREATOR ACCESS PROMPT'));

        $this->actingAs($creator)
            ->get(route('academy.prompts.show', ['slug' => $proPrompt->slug]))
            ->assertOk()
            ->assertDontSee('PRO ONLY ACCESS PROMPT')
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->where('item.locked', true)
                ->where('item.prompt', null));
    }

    public function test_pro_and_admin_users_can_view_pro_prompt(): void
    {
        $prompt = AcademyPromptTemplate::query()->create([
            'title' => 'Pro Prompt',
            'slug' => 'pro-visible-prompt',
            'excerpt' => 'Visible to pro and admin.',
            'prompt' => 'VISIBLE TO PRO AND ADMIN',
            'negative_prompt' => 'VISIBLE NEGATIVE TO PRO AND ADMIN',
            'difficulty' => 'pro',
            'access_level' => 'pro',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        foreach ([
            User::factory()->create(['role' => 'academy_pro']),
            User::factory()->create(['role' => 'admin']),
        ] as $user) {
            $this->actingAs($user)
                ->get(route('academy.prompts.show', ['slug' => $prompt->slug]))
                ->assertOk()
                ->assertSee('VISIBLE TO PRO AND ADMIN')
                ->assertInertia(fn (AssertableInertia $page) => $page
                    ->where('item.locked', false)
                    ->where('item.prompt', 'VISIBLE TO PRO AND ADMIN'));
        }
    }

    public function test_challenge_routes_are_hidden_when_challenges_are_disabled(): void
    {
        config(['academy.challenges_enabled' => false]);

        $this->get('/academy/challenges')->assertNotFound();
    }

    public function test_checkout_returns_payment_disabled_response_when_payments_are_disabled(): void
    {
        config(['academy.payments_enabled' => false]);

        $user = User::factory()->create();

        $this->actingAs($user)
            ->postJson(route('academy.checkout', ['plan' => 'creator-monthly']))
            ->assertStatus(423)
            ->assertJsonPath('code', 'academy_payments_disabled');
    }

    public function test_duplicate_prompt_save_is_idempotent(): void
    {
        $prompt = AcademyPromptTemplate::query()->create([
            'title' => 'Free Prompt',
            'slug' => 'idempotent-save-prompt',
            'prompt' => 'Save me twice',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $user = User::factory()->create();

        $this->actingAs($user)->postJson(route('academy.prompts.save', ['prompt' => $prompt->id]))->assertOk();
        $this->actingAs($user)->postJson(route('academy.prompts.save', ['prompt' => $prompt->id]))->assertOk();

        $this->assertSame(1, DB::table('academy_saved_prompts')->where('user_id', $user->id)->where('prompt_template_id', $prompt->id)->count());
    }

    public function test_duplicate_challenge_submission_updates_existing_record_instead_of_creating_duplicate(): void
    {
        $user = User::factory()->create();
        $artwork = Artwork::factory()->create([
            'user_id' => $user->id,
            'is_public' => true,
            'is_approved' => true,
            'published_at' => now()->subMinute(),
        ]);

        $challenge = AcademyChallenge::query()->create([
            'title' => 'Duplicate Safe Challenge',
            'slug' => 'duplicate-safe-challenge',
            'access_level' => 'free',
            'status' => 'active',
            'active' => true,
            'starts_at' => now()->subHour(),
            'ends_at' => now()->addDay(),
        ]);

        $payload = [
            'artwork_id' => $artwork->id,
            'prompt_used' => 'Prompt one',
            'workflow_notes' => 'Workflow one',
            'ai_tool_used' => 'ComfyUI',
            'is_ai_generated' => true,
            'is_ai_assisted' => true,
        ];

        $this->actingAs($user)->post(route('academy.challenges.submit.store', ['slug' => $challenge->slug]), $payload)->assertRedirect();

        $payload['workflow_notes'] = 'Workflow two';

        $this->actingAs($user)->post(route('academy.challenges.submit.store', ['slug' => $challenge->slug]), $payload)->assertRedirect();

        $this->assertSame(1, DB::table('academy_challenge_submissions')->where('challenge_id', $challenge->id)->where('user_id', $user->id)->where('artwork_id', $artwork->id)->count());
        $this->assertDatabaseHas('academy_challenge_submissions', [
            'challenge_id' => $challenge->id,
            'user_id' => $user->id,
            'artwork_id' => $artwork->id,
            'workflow_notes' => 'Workflow two',
        ]);
    }
}
