<?php

declare(strict_types=1);

namespace Tests\Feature\Academy;

use App\Http\Middleware\ConditionalValidateCsrfToken;
use App\Models\AcademyContentMetricDaily;
use App\Models\AcademyCourse;
use App\Models\AcademyEvent;
use App\Models\AcademyLesson;
use App\Models\AcademyLike;
use App\Models\AcademyPromptPack;
use App\Models\AcademyPromptTemplate;
use App\Models\AcademySave;
use App\Models\AcademySearchLog;
use App\Models\AcademyUserProgress;
use App\Models\User;
use App\Services\Academy\AcademyContentIntelligenceService;
use App\Support\AcademyAnalytics\AcademyAnalyticsContentType;
use App\Support\AcademyAnalytics\AcademyAnalyticsEventType;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
use Inertia\Testing\AssertableInertia;
use Tests\TestCase;

final class AcademyAnalyticsTest extends TestCase
{
    use RefreshDatabase;

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

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

    public function test_event_endpoint_stores_valid_academy_event(): void
    {
        $prompt = $this->createPrompt('tracked-prompt');

        $this->postJson(route('academy.analytics.events.store'), [
            'event_type' => AcademyAnalyticsEventType::CONTENT_VIEW,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
            'visitor_id' => 'visitor-123',
            'metadata' => ['source' => 'feature-test'],
        ])->assertOk()->assertJson(['ok' => true]);

        $this->assertDatabaseHas('academy_events', [
            'event_type' => AcademyAnalyticsEventType::CONTENT_VIEW,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
            'visitor_id' => 'visitor-123',
        ]);
    }

    public function test_event_endpoint_rejects_invalid_event_type(): void
    {
        $prompt = $this->createPrompt('invalid-event-prompt');

        $this->postJson(route('academy.analytics.events.store'), [
            'event_type' => 'academy_fake_event',
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
        ])->assertStatus(422);
    }

    public function test_event_endpoint_rejects_invalid_content_type(): void
    {
        $this->postJson(route('academy.analytics.events.store'), [
            'event_type' => AcademyAnalyticsEventType::CONTENT_VIEW,
            'content_type' => 'academy_fake_content',
            'content_id' => 1,
        ])->assertStatus(422);
    }

    public function test_event_endpoint_marks_admin_and_bot_events_without_ip_storage(): void
    {
        $admin = User::factory()->create(['role' => 'admin']);
        $prompt = $this->createPrompt('admin-bot-prompt');

        $this->withHeader('User-Agent', 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)')
            ->actingAs($admin)
            ->postJson(route('academy.analytics.events.store'), [
                'event_type' => AcademyAnalyticsEventType::CONTENT_VIEW,
                'content_type' => AcademyAnalyticsContentType::PROMPT,
                'content_id' => $prompt->id,
                'visitor_id' => 'admin-bot-visitor',
            ])->assertOk();

        $this->assertDatabaseHas('academy_events', [
            'content_id' => $prompt->id,
            'visitor_id' => 'admin-bot-visitor',
            'is_admin' => true,
            'is_bot' => true,
        ]);

        $this->assertFalse(Schema::hasColumn('academy_events', 'ip_address'));
    }

    public function test_search_result_click_event_is_accepted(): void
    {
        $prompt = $this->createPrompt('clicked-prompt');

        $this->postJson(route('academy.analytics.events.store'), [
            'event_type' => AcademyAnalyticsEventType::SEARCH_RESULT_CLICK,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
            'visitor_id' => 'search-click-visitor',
            'metadata' => [
                'query' => 'robot mascot',
                'normalized_query' => 'robot mascot',
                'results_count' => 12,
                'position' => 3,
                'source' => 'academy_search_results',
            ],
        ])->assertOk()->assertJson(['ok' => true]);

        $this->assertDatabaseHas('academy_events', [
            'event_type' => AcademyAnalyticsEventType::SEARCH_RESULT_CLICK,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
        ]);
    }

    public function test_search_result_click_rejects_invalid_clicked_content_type(): void
    {
        $prompt = $this->createPrompt('invalid-click-content-type-prompt');

        $this->postJson(route('academy.analytics.events.store'), [
            'event_type' => AcademyAnalyticsEventType::SEARCH_RESULT_CLICK,
            'content_type' => AcademyAnalyticsContentType::SEARCH,
            'content_id' => $prompt->id,
            'metadata' => [
                'query' => 'robot mascot',
                'normalized_query' => 'robot mascot',
                'results_count' => 12,
            ],
        ])->assertStatus(422);
    }

    public function test_search_result_click_rejects_invalid_clicked_content_id(): void
    {
        $this->postJson(route('academy.analytics.events.store'), [
            'event_type' => AcademyAnalyticsEventType::SEARCH_RESULT_CLICK,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => 999999,
            'metadata' => [
                'query' => 'robot mascot',
                'normalized_query' => 'robot mascot',
                'results_count' => 12,
            ],
        ])->assertStatus(422);
    }

    public function test_search_result_click_updates_latest_matching_search_log(): void
    {
        $prompt = $this->createPrompt('search-log-click-target');

        $clickedAlready = AcademySearchLog::query()->create([
            'visitor_id' => 'matching-search-visitor',
            'query' => 'Robot mascot',
            'normalized_query' => 'robot mascot',
            'results_count' => 12,
            'clicked_content_type' => AcademyAnalyticsContentType::PROMPT,
            'clicked_content_id' => $prompt->id,
            'filters' => ['difficulty' => 'beginner'],
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_bot' => false,
        ]);
        $clickedAlready->forceFill(['created_at' => now()->subMinutes(5), 'updated_at' => now()->subMinutes(5)])->save();

        $pending = AcademySearchLog::query()->create([
            'visitor_id' => 'matching-search-visitor',
            'query' => 'Robot mascot',
            'normalized_query' => 'robot mascot',
            'results_count' => 12,
            'filters' => ['difficulty' => 'beginner'],
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_bot' => false,
        ]);
        $pending->forceFill(['created_at' => now()->subMinutes(2), 'updated_at' => now()->subMinutes(2)])->save();

        $this->postJson(route('academy.analytics.events.store'), [
            'event_type' => AcademyAnalyticsEventType::SEARCH_RESULT_CLICK,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
            'visitor_id' => 'matching-search-visitor',
            'metadata' => [
                'query' => 'Robot mascot',
                'normalized_query' => 'robot mascot',
                'results_count' => 12,
                'position' => 2,
                'filters' => ['difficulty' => 'beginner'],
            ],
        ])->assertOk();

        $this->assertDatabaseHas('academy_search_logs', [
            'id' => $pending->id,
            'clicked_content_type' => AcademyAnalyticsContentType::PROMPT,
            'clicked_content_id' => $prompt->id,
        ]);
    }

    public function test_search_result_click_creates_fallback_search_log_when_none_exists(): void
    {
        $prompt = $this->createPrompt('fallback-click-target');

        $this->postJson(route('academy.analytics.events.store'), [
            'event_type' => AcademyAnalyticsEventType::SEARCH_RESULT_CLICK,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
            'visitor_id' => 'fallback-search-visitor',
            'metadata' => [
                'query' => 'robot mascot',
                'normalized_query' => 'robot mascot',
                'results_count' => 12,
                'position' => 3,
                'filters' => ['difficulty' => 'beginner'],
            ],
        ])->assertOk();

        $this->assertDatabaseHas('academy_search_logs', [
            'visitor_id' => 'fallback-search-visitor',
            'normalized_query' => 'robot mascot',
            'results_count' => 12,
            'clicked_content_type' => AcademyAnalyticsContentType::PROMPT,
            'clicked_content_id' => $prompt->id,
        ]);
    }

    public function test_authenticated_user_can_toggle_academy_like_and_save(): void
    {
        $user = User::factory()->create();
        $prompt = $this->createPrompt('liked-prompt');
        $course = $this->createCourse('saved-course');

        $this->actingAs($user)
            ->postJson(route('academy.interactions.like'), [
                'content_type' => AcademyAnalyticsContentType::PROMPT,
                'content_id' => $prompt->id,
            ])
            ->assertOk()
            ->assertJson([
                'liked' => true,
                'likes_count' => 1,
            ]);

        $this->actingAs($user)
            ->postJson(route('academy.interactions.save'), [
                'content_type' => AcademyAnalyticsContentType::COURSE,
                'content_id' => $course->id,
            ])
            ->assertOk()
            ->assertJson([
                'saved' => true,
                'saves_count' => 1,
            ]);

        $this->actingAs($user)
            ->postJson(route('academy.interactions.like'), [
                'content_type' => AcademyAnalyticsContentType::PROMPT,
                'content_id' => $prompt->id,
            ])
            ->assertOk()
            ->assertJson([
                'liked' => false,
                'likes_count' => 0,
            ]);

        $this->assertDatabaseMissing('academy_likes', [
            'user_id' => $user->id,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
        ]);

        $this->assertDatabaseHas('academy_saves', [
            'user_id' => $user->id,
            'content_type' => AcademyAnalyticsContentType::COURSE,
            'content_id' => $course->id,
        ]);

        $this->assertSame(1, AcademyEvent::query()
            ->where('event_type', AcademyAnalyticsEventType::PROMPT_LIKE)
            ->where('content_id', $prompt->id)
            ->count());
    }

    public function test_guest_cannot_toggle_academy_like_or_save(): void
    {
        $prompt = $this->createPrompt('guest-toggle-prompt');

        $this->postJson(route('academy.interactions.like'), [
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
        ])->assertStatus(401);

        $this->postJson(route('academy.interactions.save'), [
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
        ])->assertStatus(401);
    }

    public function test_progress_endpoints_track_single_lesson_start_and_completion_event(): void
    {
        $user = User::factory()->create();
        $lesson = $this->createLesson('analytics-lesson');

        $this->actingAs($user)
            ->postJson(route('academy.progress.lesson.start'), [
                'lesson_id' => $lesson->id,
            ])
            ->assertOk()
            ->assertJson([
                'ok' => true,
                'status' => 'started',
            ]);

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

        $this->assertDatabaseHas('academy_user_progress', [
            'user_id' => $user->id,
            'lesson_id' => $lesson->id,
            'status' => 'completed',
            'progress_percent' => 100,
        ]);

        $this->assertSame(1, AcademyEvent::query()
            ->where('event_type', AcademyAnalyticsEventType::LESSON_STARTED)
            ->where('content_id', $lesson->id)
            ->count());

        $this->assertSame(1, AcademyEvent::query()
            ->where('event_type', AcademyAnalyticsEventType::LESSON_COMPLETED)
            ->where('content_id', $lesson->id)
            ->count());
    }

    public function test_progress_complete_endpoint_prevents_duplicate_progress_rows(): void
    {
        $user = User::factory()->create();
        $lesson = $this->createLesson('analytics-lesson-deduped');

        $this->actingAs($user)
            ->postJson(route('academy.progress.lesson.start'), [
                'lesson_id' => $lesson->id,
            ])->assertOk();

        $this->actingAs($user)
            ->postJson(route('academy.progress.lesson.complete'), [
                'lesson_id' => $lesson->id,
            ])->assertOk();

        $this->actingAs($user)
            ->postJson(route('academy.progress.lesson.complete'), [
                'lesson_id' => $lesson->id,
            ])->assertOk();

        $this->assertSame(1, AcademyUserProgress::query()
            ->where('user_id', $user->id)
            ->where('lesson_id', $lesson->id)
            ->count());
    }

    public function test_prompt_search_logs_are_recorded_from_index_queries(): void
    {
        $this->createPrompt('searchable-prompt', 'Lighting Prompt');

        $this->get(route('academy.prompts.index', ['q' => 'Lighting']))
            ->assertOk();

        $this->assertDatabaseHas('academy_search_logs', [
            'normalized_query' => 'lighting',
            'results_count' => 1,
            'is_bot' => false,
        ]);
    }

    public function test_zero_result_search_logs_and_tracks_filters(): void
    {
        $this->get(route('academy.prompts.index', ['q' => 'No Match Here', 'difficulty' => 'beginner']))
            ->assertOk();

        $this->assertDatabaseHas('academy_search_logs', [
            'normalized_query' => 'no match here',
            'results_count' => 0,
        ]);

        $searchLog = AcademySearchLog::query()->where('normalized_query', 'no match here')->latest('id')->first();
        $this->assertSame('beginner', $searchLog?->filters['difficulty'] ?? null);

        $this->assertDatabaseHas('academy_events', [
            'event_type' => AcademyAnalyticsEventType::ZERO_SEARCH_RESULTS,
            'content_type' => AcademyAnalyticsContentType::SEARCH,
        ]);
    }

    public function test_prompt_library_index_exposes_dedicated_page_analytics_content_type(): void
    {
        $this->createPrompt('library-analytics-prompt');

        $this->get(route('academy.prompts.index'))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/List')
                ->where('analytics.contentType', AcademyAnalyticsContentType::PROMPT_LIBRARY)
                ->where('analytics.contentId', null)
                ->where('analytics.pageName', 'academy_prompts_index')
            );
    }

    public function test_popular_prompts_page_exposes_dedicated_page_analytics_content_type(): void
    {
        $prompt = $this->createPrompt('popular-page-prompt');
        $this->createMetric(AcademyAnalyticsContentType::PROMPT, $prompt->id, [
            'views' => 18,
            'prompt_copies' => 4,
            'popularity_score' => 51.2,
        ]);

        $this->get(route('academy.prompts.popular'))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/List')
                ->where('analytics.contentType', AcademyAnalyticsContentType::PROMPT_POPULAR)
                ->where('analytics.contentId', null)
                ->where('analytics.pageName', 'academy_prompts_popular')
            );
    }

    public function test_popular_prompts_page_exposes_selected_period_in_analytics_metadata(): void
    {
        $prompt = $this->createPrompt('popular-period-analytics-prompt');
        $this->createMetric(AcademyAnalyticsContentType::PROMPT, $prompt->id, [
            'views' => 28,
            'prompt_copies' => 6,
            'popularity_score' => 64.4,
        ]);

        $this->get(route('academy.prompts.popular', ['period' => '7d']))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/List')
                ->where('analytics.metadata.period', '7d')
                ->where('analytics.metadata.period_days', 7)
                ->where('analytics.trackingKey', 'period:7d')
            );
    }

    public function test_prompt_pack_library_index_exposes_dedicated_page_analytics_content_type(): void
    {
        AcademyPromptPack::query()->create([
            'title' => 'Starter Pack',
            'slug' => 'starter-pack',
            'excerpt' => 'A free pack.',
            'description' => 'Pack description.',
            'access_level' => 'free',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);

        $this->get(route('academy.packs.index'))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Academy/List')
                ->where('analytics.contentType', AcademyAnalyticsContentType::PROMPT_PACK_LIBRARY)
                ->where('analytics.contentId', null)
                ->where('analytics.pageName', 'academy_packs_index')
            );
    }

    public function test_rollup_counts_search_result_clicks_for_clicked_content(): void
    {
        $prompt = $this->createPrompt('rollup-search-click-prompt');
        $date = Carbon::parse('2026-05-12 11:15:00');

        AcademyEvent::query()->create([
            'event_type' => AcademyAnalyticsEventType::SEARCH_RESULT_CLICK,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
            'visitor_id' => 'rollup-search-click-visitor',
            'url' => route('academy.prompts.show', ['slug' => $prompt->slug]),
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_admin' => false,
            'is_bot' => false,
            'is_crawler' => false,
            'is_suspicious' => false,
            'metadata' => [
                'query' => 'robot mascot',
                'normalized_query' => 'robot mascot',
                'results_count' => 12,
                'position' => 3,
            ],
            'occurred_at' => $date,
        ]);

        $this->artisan('academy:analytics-rollup', ['--date' => $date->toDateString()])->assertExitCode(0);

        $metric = AcademyContentMetricDaily::query()
            ->whereDate('date', $date->toDateString())
            ->where('content_type', AcademyAnalyticsContentType::PROMPT)
            ->where('content_id', $prompt->id)
            ->first();

        $this->assertNotNull($metric);
        $this->assertSame(1, (int) $metric->search_clicks);
    }

    public function test_rollup_excludes_bot_admin_search_result_clicks(): void
    {
        $prompt = $this->createPrompt('ignored-search-click-prompt');
        $date = Carbon::parse('2026-05-13 09:00:00');

        AcademyEvent::query()->create([
            'event_type' => AcademyAnalyticsEventType::SEARCH_RESULT_CLICK,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
            'user_id' => User::factory()->create(['role' => 'admin'])->id,
            'visitor_id' => 'ignored-search-click-visitor',
            'url' => route('academy.prompts.show', ['slug' => $prompt->slug]),
            'is_logged_in' => true,
            'is_subscriber' => false,
            'is_admin' => true,
            'is_bot' => true,
            'is_crawler' => true,
            'is_suspicious' => true,
            'metadata' => [
                'query' => 'robot mascot',
                'normalized_query' => 'robot mascot',
                'results_count' => 12,
            ],
            'occurred_at' => $date,
        ]);

        $this->artisan('academy:analytics-rollup', ['--date' => $date->toDateString()])->assertExitCode(0);

        $metric = AcademyContentMetricDaily::query()
            ->whereDate('date', $date->toDateString())
            ->where('content_type', AcademyAnalyticsContentType::PROMPT)
            ->where('content_id', $prompt->id)
            ->first();

        $this->assertNull($metric);
    }

    public function test_rollup_command_aggregates_daily_metrics_idempotently(): void
    {
        $prompt = $this->createPrompt('rollup-prompt');
        $user = User::factory()->create();
        $date = Carbon::parse('2026-05-11 10:30:00');

        AcademyEvent::query()->create([
            'event_type' => AcademyAnalyticsEventType::CONTENT_VIEW,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
            'visitor_id' => 'guest-rollup',
            'url' => route('academy.prompts.show', ['slug' => $prompt->slug]),
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_admin' => false,
            'is_bot' => false,
            'is_crawler' => false,
            'is_suspicious' => false,
            'metadata' => [],
            'occurred_at' => $date,
        ]);

        AcademyEvent::query()->create([
            'event_type' => AcademyAnalyticsEventType::ENGAGED_VIEW,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
            'user_id' => $user->id,
            'visitor_id' => 'member-rollup',
            'url' => route('academy.prompts.show', ['slug' => $prompt->slug]),
            'is_logged_in' => true,
            'is_subscriber' => false,
            'is_admin' => false,
            'is_bot' => false,
            'is_crawler' => false,
            'is_suspicious' => false,
            'metadata' => ['engaged_seconds' => 32],
            'occurred_at' => $date->copy()->addMinute(),
        ]);

        AcademyEvent::query()->create([
            'event_type' => AcademyAnalyticsEventType::PROMPT_COPY,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
            'user_id' => $user->id,
            'visitor_id' => 'member-rollup',
            'url' => route('academy.prompts.show', ['slug' => $prompt->slug]),
            'is_logged_in' => true,
            'is_subscriber' => false,
            'is_admin' => false,
            'is_bot' => false,
            'is_crawler' => false,
            'is_suspicious' => false,
            'metadata' => ['copy_type' => 'main_prompt'],
            'occurred_at' => $date->copy()->addMinutes(2),
        ]);

        $like = AcademyLike::query()->create([
            'user_id' => $user->id,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
        ]);
        $like->forceFill(['created_at' => $date, 'updated_at' => $date])->save();

        $save = AcademySave::query()->create([
            'user_id' => $user->id,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
        ]);
        $save->forceFill(['created_at' => $date, 'updated_at' => $date])->save();

        $searchLog = AcademySearchLog::query()->create([
            'user_id' => $user->id,
            'visitor_id' => 'member-rollup',
            'query' => 'lighting',
            'normalized_query' => 'lighting',
            'results_count' => 0,
            'filters' => [],
            'is_logged_in' => true,
            'is_subscriber' => false,
            'is_bot' => false,
        ]);
        $searchLog->forceFill(['created_at' => $date, 'updated_at' => $date])->save();

        $this->artisan('academy:analytics-rollup', ['--date' => $date->toDateString()])->assertExitCode(0);
        $this->artisan('academy:analytics-rollup', ['--date' => $date->toDateString()])->assertExitCode(0);

        $metric = AcademyContentMetricDaily::query()
            ->whereDate('date', $date->toDateString())
            ->where('content_type', AcademyAnalyticsContentType::PROMPT)
            ->where('content_id', $prompt->id)
            ->first();

        $this->assertNotNull($metric);
        $this->assertSame(1, (int) $metric->views);
        $this->assertSame(2, (int) $metric->unique_visitors);
        $this->assertSame(1, (int) $metric->engaged_views);
        $this->assertSame(1, (int) $metric->likes);
        $this->assertSame(1, (int) $metric->saves);
        $this->assertSame(1, (int) $metric->prompt_copies);
        $this->assertSame(32, (int) $metric->avg_engaged_seconds);
        $this->assertSame(0, (int) $metric->bounce_count);
        $this->assertGreaterThan(0, (float) $metric->popularity_score);

        $this->assertSame(1, AcademyContentMetricDaily::query()
            ->whereDate('date', $date->toDateString())
            ->where('content_type', AcademyAnalyticsContentType::PROMPT)
            ->where('content_id', $prompt->id)
            ->count());

        $searchMetric = AcademyContentMetricDaily::query()
            ->whereDate('date', $date->toDateString())
            ->where('content_type', AcademyAnalyticsContentType::SEARCH)
            ->whereNull('content_id')
            ->first();

        $this->assertNotNull($searchMetric);
        $this->assertSame(1, (int) $searchMetric->search_impressions);
        $this->assertSame(1, (int) $searchMetric->bounce_count);
    }

    public function test_health_command_runs_and_warns_when_no_events_exist(): void
    {
        $exitCode = Artisan::call('academy:analytics-health');
        $output = Artisan::output();

        $this->assertSame(0, $exitCode);
        $this->assertStringContainsString('Academy Analytics Health Check', $output);
        $this->assertStringContainsString('WARNING: No events received in last 24 hours.', $output);
        $this->assertStringContainsString('Status: WARNING', $output);
    }

    public function test_health_command_reports_latest_event_timestamp_and_supports_json(): void
    {
        $prompt = $this->createPrompt('health-command-prompt');
        $eventTime = Carbon::parse('2026-05-10 14:42:00');

        AcademyEvent::query()->create([
            'event_type' => AcademyAnalyticsEventType::CONTENT_VIEW,
            'content_type' => AcademyAnalyticsContentType::PROMPT,
            'content_id' => $prompt->id,
            'visitor_id' => 'health-check-visitor',
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_admin' => false,
            'is_bot' => false,
            'is_crawler' => false,
            'is_suspicious' => false,
            'metadata' => [],
            'occurred_at' => $eventTime,
        ]);

        AcademySearchLog::query()->create([
            'visitor_id' => 'health-check-visitor',
            'query' => 'robot mascot',
            'normalized_query' => 'robot mascot',
            'results_count' => 4,
            'clicked_content_type' => AcademyAnalyticsContentType::PROMPT,
            'clicked_content_id' => $prompt->id,
            'filters' => [],
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_bot' => false,
        ]);

        $this->createMetric(AcademyAnalyticsContentType::PROMPT, $prompt->id, [
            'date' => now()->toDateString(),
            'views' => 1,
            'unique_visitors' => 1,
        ]);

        $exitCode = Artisan::call('academy:analytics-health', ['--json' => true]);
        $report = json_decode(trim(Artisan::output()), true, 512, JSON_THROW_ON_ERROR);

        $this->assertSame(0, $exitCode);
        $this->assertSame('2026-05-10 14:42:00', $report['latest_event_at']);
        $this->assertSame(now()->toDateString(), $report['latest_rollup_date']);
        $this->assertSame(1, $report['search_logs']);
        $this->assertSame(1, $report['search_clicks']);
    }

    public function test_content_intelligence_finds_zero_result_searches_and_results_without_clicks_and_calculates_ctr(): void
    {
        $prompt = $this->createPrompt('search-gap-prompt');

        AcademySearchLog::query()->create([
            'visitor_id' => 'search-gap-a',
            'query' => 'Amiga pixel art prompt',
            'normalized_query' => 'amiga pixel art prompt',
            'results_count' => 0,
            'filters' => [],
            'is_logged_in' => true,
            'is_subscriber' => false,
            'is_bot' => false,
        ]);
        AcademySearchLog::query()->create([
            'visitor_id' => 'search-gap-b',
            'query' => 'Prompt anatomy',
            'normalized_query' => 'prompt anatomy',
            'results_count' => 6,
            'filters' => [],
            'is_logged_in' => true,
            'is_subscriber' => false,
            'is_bot' => false,
        ]);
        AcademySearchLog::query()->create([
            'visitor_id' => 'search-gap-c',
            'query' => 'Prompt anatomy',
            'normalized_query' => 'prompt anatomy',
            'results_count' => 5,
            'filters' => [],
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_bot' => false,
        ]);
        AcademySearchLog::query()->create([
            'visitor_id' => 'search-gap-d',
            'query' => 'Robot mascot',
            'normalized_query' => 'robot mascot',
            'results_count' => 4,
            'clicked_content_type' => AcademyAnalyticsContentType::PROMPT,
            'clicked_content_id' => $prompt->id,
            'filters' => [],
            'is_logged_in' => true,
            'is_subscriber' => true,
            'is_bot' => false,
        ]);
        AcademySearchLog::query()->create([
            'visitor_id' => 'search-gap-e',
            'query' => 'Robot mascot',
            'normalized_query' => 'robot mascot',
            'results_count' => 4,
            'filters' => [],
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_bot' => false,
        ]);

        $report = app(AcademyContentIntelligenceService::class)->getSearchGaps([
            'from' => now()->subDay(),
            'to' => now()->addDay(),
            'limit' => 25,
        ]);

        $rows = collect($report['rows']);

        $this->assertSame('Zero-result demand', $rows->firstWhere('normalized_query', 'amiga pixel art prompt')['issue']);
        $this->assertSame('Results with no clicks', $rows->firstWhere('normalized_query', 'prompt anatomy')['issue']);
        $this->assertSame(50.0, $rows->firstWhere('normalized_query', 'robot mascot')['ctr']);
    }

    public function test_content_intelligence_detects_prompt_opportunity_signals(): void
    {
        $needsWork = $this->createPrompt('needs-work-prompt', 'Needs Work Prompt');
        $hiddenWinner = $this->createPrompt('hidden-winner-prompt', 'Hidden Winner Prompt');
        $premiumPrompt = $this->createPrompt('premium-prompt', 'Premium Prompt');

        $this->createMetric(AcademyAnalyticsContentType::PROMPT, $needsWork->id, [
            'views' => 180,
            'unique_visitors' => 120,
            'prompt_copies' => 4,
        ]);
        $this->createMetric(AcademyAnalyticsContentType::PROMPT, $hiddenWinner->id, [
            'views' => 20,
            'unique_visitors' => 20,
            'prompt_copies' => 10,
        ]);
        $this->createMetric(AcademyAnalyticsContentType::PROMPT, $premiumPrompt->id, [
            'views' => 40,
            'unique_visitors' => 30,
            'premium_preview_views' => 12,
            'upgrade_clicks' => 4,
        ]);

        $rows = collect(app(AcademyContentIntelligenceService::class)->getPromptInsights([
            'from' => now()->subDay(),
            'to' => now()->addDay(),
        ])['rows']);

        $this->assertSame('High views, low copies', $rows->firstWhere('title', 'Needs Work Prompt')['issue']);
        $this->assertSame('Low views, high copy rate', $rows->firstWhere('title', 'Hidden Winner Prompt')['issue']);
        $this->assertSame('High upgrade interest', $rows->firstWhere('title', 'Premium Prompt')['issue']);
    }

    public function test_content_intelligence_detects_lesson_dropoff_signals(): void
    {
        $lowStartLesson = $this->createLesson('low-start-lesson');
        $dropoffLesson = $this->createLesson('dropoff-lesson');
        $winnerLesson = $this->createLesson('winner-lesson');

        $this->createMetric(AcademyAnalyticsContentType::LESSON, $lowStartLesson->id, [
            'views' => 120,
            'unique_visitors' => 100,
            'starts' => 10,
            'completions' => 5,
        ]);
        $this->createMetric(AcademyAnalyticsContentType::LESSON, $dropoffLesson->id, [
            'views' => 90,
            'unique_visitors' => 70,
            'starts' => 20,
            'completions' => 4,
        ]);
        $this->createMetric(AcademyAnalyticsContentType::LESSON, $winnerLesson->id, [
            'views' => 28,
            'unique_visitors' => 24,
            'starts' => 12,
            'completions' => 9,
        ]);

        $rows = collect(app(AcademyContentIntelligenceService::class)->getLessonDropoffs([
            'from' => now()->subDay(),
            'to' => now()->addDay(),
        ])['rows']);

        $this->assertSame('High views, low starts', $rows->firstWhere('content_id', $lowStartLesson->id)['issue']);
        $this->assertSame('High starts, low completions', $rows->firstWhere('content_id', $dropoffLesson->id)['issue']);
        $this->assertSame('High completions, low views', $rows->firstWhere('content_id', $winnerLesson->id)['issue']);
    }

    public function test_content_intelligence_detects_course_health_signals(): void
    {
        $stalledCourse = $this->createCourse('stalled-course');
        $expandableCourse = $this->createCourse('expandable-course');

        $this->createMetric(AcademyAnalyticsContentType::COURSE, $stalledCourse->id, [
            'views' => 140,
            'unique_visitors' => 100,
            'starts' => 20,
            'completions' => 4,
        ]);
        $this->createMetric(AcademyAnalyticsContentType::COURSE, $expandableCourse->id, [
            'views' => 45,
            'unique_visitors' => 35,
            'starts' => 12,
            'completions' => 10,
        ]);

        AcademyUserProgress::query()->create([
            'user_id' => User::factory()->create()->id,
            'course_id' => $stalledCourse->id,
            'status' => 'in_progress',
            'progress_percent' => 35,
            'started_at' => now(),
            'last_seen_at' => now(),
        ]);
        AcademyUserProgress::query()->create([
            'user_id' => User::factory()->create()->id,
            'course_id' => $expandableCourse->id,
            'status' => 'completed',
            'progress_percent' => 100,
            'started_at' => now(),
            'completed_at' => now(),
            'last_seen_at' => now(),
        ]);

        $rows = collect(app(AcademyContentIntelligenceService::class)->getCourseHealth([
            'from' => now()->subDay(),
            'to' => now()->addDay(),
        ])['rows']);

        $this->assertSame('Low course completion rate', $rows->firstWhere('content_id', $stalledCourse->id)['issue']);
        $this->assertSame('Expansion candidate', $rows->firstWhere('content_id', $expandableCourse->id)['issue']);
        $this->assertSame(35.0, $rows->firstWhere('content_id', $stalledCourse->id)['avg_progress']);
    }

    public function test_content_intelligence_detects_premium_interest_signals(): void
    {
        $strongPrompt = $this->createPrompt('strong-premium-prompt', 'Strong Premium Prompt');
        $weakTeaserLesson = $this->createLesson('weak-teaser-lesson');

        $this->createMetric(AcademyAnalyticsContentType::PROMPT, $strongPrompt->id, [
            'premium_preview_views' => 10,
            'upgrade_clicks' => 4,
        ]);
        $this->createMetric(AcademyAnalyticsContentType::LESSON, $weakTeaserLesson->id, [
            'premium_preview_views' => 20,
            'upgrade_clicks' => 1,
        ]);

        $rows = collect(app(AcademyContentIntelligenceService::class)->getPremiumInterest([
            'from' => now()->subDay(),
            'to' => now()->addDay(),
        ])['rows']);

        $strongPromptRow = $rows->first(fn (array $row): bool => $row['content_type'] === AcademyAnalyticsContentType::PROMPT && $row['content_id'] === $strongPrompt->id);
        $weakLessonRow = $rows->first(fn (array $row): bool => $row['content_type'] === AcademyAnalyticsContentType::LESSON && $row['content_id'] === $weakTeaserLesson->id);

        $this->assertSame('Strong premium candidate', $strongPromptRow['issue']);
        $this->assertSame('Weak premium teaser', $weakLessonRow['issue']);
    }

    public function test_content_intelligence_generates_editorial_recommendations(): void
    {
        $prompt = $this->createPrompt('recommendation-prompt', 'Recommendation Prompt');
        $lesson = $this->createLesson('recommendation-lesson');

        AcademySearchLog::query()->create([
            'visitor_id' => 'recommendation-search-visitor',
            'query' => 'amiga pixel art prompt',
            'normalized_query' => 'amiga pixel art prompt',
            'results_count' => 0,
            'filters' => [],
            'is_logged_in' => true,
            'is_subscriber' => false,
            'is_bot' => false,
        ]);

        $this->createMetric(AcademyAnalyticsContentType::PROMPT, $prompt->id, [
            'views' => 180,
            'unique_visitors' => 120,
            'prompt_copies' => 3,
        ]);
        $this->createMetric(AcademyAnalyticsContentType::LESSON, $lesson->id, [
            'views' => 90,
            'unique_visitors' => 60,
            'starts' => 20,
            'completions' => 4,
        ]);

        $titles = collect(app(AcademyContentIntelligenceService::class)->getEditorialRecommendations([
            'from' => now()->subDay(),
            'to' => now()->addDay(),
        ])['rows'])->pluck('title');

        $this->assertTrue($titles->contains('Create content for "amiga pixel art prompt"'));
        $this->assertTrue($titles->contains('Review prompt "Recommendation Prompt"'));
        $this->assertTrue($titles->contains('Improve lesson "Tracked Lesson"'));
    }

    public function test_recalculate_popularity_command_updates_existing_scores(): void
    {
        $metric = AcademyContentMetricDaily::query()->create([
            'date' => now()->toDateString(),
            'content_type' => AcademyAnalyticsContentType::COURSE,
            'content_id' => $this->createCourse('popularity-course')->id,
            'views' => 10,
            'unique_visitors' => 5,
            'guest_views' => 2,
            'user_views' => 8,
            'subscriber_views' => 1,
            'engaged_views' => 3,
            'scroll_50' => 2,
            'scroll_75' => 1,
            'scroll_100' => 1,
            'likes' => 2,
            'saves' => 1,
            'prompt_copies' => 0,
            'negative_prompt_copies' => 0,
            'starts' => 2,
            'completions' => 1,
            'upgrade_clicks' => 1,
            'premium_preview_views' => 0,
            'search_impressions' => 0,
            'search_clicks' => 0,
            'bounce_count' => 1,
            'avg_engaged_seconds' => 24,
            'popularity_score' => 0,
            'conversion_score' => 0,
        ]);

        $this->artisan('academy:analytics-recalculate-popularity', ['--days' => 1])->assertExitCode(0);

        $metric->refresh();

        $this->assertGreaterThan(0, (float) $metric->popularity_score);
        $this->assertSame(20.0, (float) $metric->conversion_score);
    }

    public function test_admin_prompt_library_analytics_page_uses_dedicated_prompt_library_filter(): void
    {
        $admin = User::factory()->create(['role' => 'admin']);

        $this->actingAs($admin)
            ->get(route('admin.academy.analytics.prompt-library'))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Admin/Academy/AnalyticsContent')
                ->where('title', 'Prompt library analytics')
                ->where('filters.content_type', AcademyAnalyticsContentType::PROMPT_LIBRARY)
                ->where('contentTypeOptions', fn ($options): bool => collect($options)->contains(
                    fn (array $option): bool => ($option['value'] ?? null) === AcademyAnalyticsContentType::PROMPT_LIBRARY
                        && ($option['label'] ?? null) === 'Prompt library'
                ))
            );
    }

    public function test_admin_prompt_library_analytics_page_includes_prompt_library_summary(): void
    {
        $admin = User::factory()->create(['role' => 'admin']);

        $this->createMetric(AcademyAnalyticsContentType::PROMPT_LIBRARY, null, [
            'views' => 42,
            'unique_visitors' => 18,
            'engaged_views' => 9,
            'scroll_50' => 12,
            'scroll_100' => 4,
            'avg_engaged_seconds' => 31,
        ]);

        $this->actingAs($admin)
            ->get(route('admin.academy.analytics.prompt-library'))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Admin/Academy/AnalyticsContent')
                ->where('summary.views', 42)
                ->where('summary.uniqueVisitors', 18)
                ->where('summary.engagedViews', 9)
                ->where('summary.avgEngagedSeconds', 31)
                ->where('summary.engagementRate', 50)
                ->where('summary.deepScrollRate', 22.2)
            );
    }

    public function test_admin_analytics_overview_includes_prompt_library_trend(): void
    {
        $admin = User::factory()->create(['role' => 'admin']);
        $today = now()->toDateString();
        $yesterday = now()->subDay()->toDateString();

        $this->createMetric(AcademyAnalyticsContentType::PROMPT_LIBRARY, null, [
            'date' => $today,
            'views' => 40,
            'unique_visitors' => 20,
            'engaged_views' => 10,
            'popularity_score' => 55,
        ]);

        $this->createMetric(AcademyAnalyticsContentType::PROMPT_LIBRARY, null, [
            'date' => $yesterday,
            'views' => 20,
            'unique_visitors' => 10,
            'engaged_views' => 5,
            'popularity_score' => 25,
        ]);

        $this->actingAs($admin)
            ->get(route('admin.academy.analytics.overview', [
                'range' => 'custom',
                'from' => $today,
                'to' => $today,
            ]))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Admin/Academy/AnalyticsOverview')
                ->where('promptLibraryTrend.current.views', 40)
                ->where('promptLibraryTrend.current.uniqueVisitors', 20)
                ->where('promptLibraryTrend.current.engagementRate', 50)
                ->where('promptLibraryTrend.previous.views', 20)
                ->where('promptLibraryTrend.deltas.views', 100)
                ->where('promptLibraryTrend.deltas.engagementRate', 0)
            );
    }

    public function test_admin_analytics_overview_includes_popular_prompt_period_usage(): void
    {
        $admin = User::factory()->create(['role' => 'admin']);

        AcademyEvent::query()->create([
            'event_type' => AcademyAnalyticsEventType::PAGE_VIEW,
            'content_type' => AcademyAnalyticsContentType::PROMPT_POPULAR,
            'visitor_id' => 'visitor-30-a',
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_admin' => false,
            'is_bot' => false,
            'is_crawler' => false,
            'is_suspicious' => false,
            'metadata' => ['period' => '30d', 'period_days' => 30],
            'occurred_at' => now()->subHour(),
        ]);

        AcademyEvent::query()->create([
            'event_type' => AcademyAnalyticsEventType::PAGE_VIEW,
            'content_type' => AcademyAnalyticsContentType::PROMPT_POPULAR,
            'visitor_id' => 'visitor-30-b',
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_admin' => false,
            'is_bot' => false,
            'is_crawler' => false,
            'is_suspicious' => false,
            'metadata' => ['period' => '30d', 'period_days' => 30],
            'occurred_at' => now()->subMinutes(30),
        ]);

        AcademyEvent::query()->create([
            'event_type' => AcademyAnalyticsEventType::PAGE_VIEW,
            'content_type' => AcademyAnalyticsContentType::PROMPT_POPULAR,
            'visitor_id' => 'visitor-7-a',
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_admin' => false,
            'is_bot' => false,
            'is_crawler' => false,
            'is_suspicious' => false,
            'metadata' => ['period' => '7d', 'period_days' => 7],
            'occurred_at' => now()->subMinutes(10),
        ]);

        $this->actingAs($admin)
            ->get(route('admin.academy.analytics.overview'))
            ->assertOk()
            ->assertInertia(fn (AssertableInertia $page) => $page
                ->component('Admin/Academy/AnalyticsOverview')
                ->where('popularPromptPeriodUsage.totalViews', 3)
                ->where('popularPromptPeriodUsage.totalVisitors', 3)
                ->where('popularPromptPeriodUsage.periods.0.period', '30d')
                ->where('popularPromptPeriodUsage.periods.0.views', 2)
                ->where('popularPromptPeriodUsage.periods.0.share', 66.7)
                ->where('popularPromptPeriodUsage.periods.1.period', '7d')
                ->where('popularPromptPeriodUsage.periods.1.views', 1));
    }

    public function test_prune_events_command_removes_only_old_raw_events(): void
    {
        $oldEvent = AcademyEvent::query()->create([
            'event_type' => AcademyAnalyticsEventType::PAGE_VIEW,
            'content_type' => AcademyAnalyticsContentType::HOME,
            'visitor_id' => 'old-visitor',
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_admin' => false,
            'is_bot' => false,
            'is_crawler' => false,
            'is_suspicious' => false,
            'metadata' => [],
            'occurred_at' => now()->subDays(200),
        ]);

        $freshEvent = AcademyEvent::query()->create([
            'event_type' => AcademyAnalyticsEventType::PAGE_VIEW,
            'content_type' => AcademyAnalyticsContentType::HOME,
            'visitor_id' => 'fresh-visitor',
            'is_logged_in' => false,
            'is_subscriber' => false,
            'is_admin' => false,
            'is_bot' => false,
            'is_crawler' => false,
            'is_suspicious' => false,
            'metadata' => [],
            'occurred_at' => now()->subDays(10),
        ]);

        $this->artisan('academy:analytics-prune-events', ['--days' => 180])->assertExitCode(0);

        $this->assertDatabaseMissing('academy_events', ['id' => $oldEvent->id]);
        $this->assertDatabaseHas('academy_events', ['id' => $freshEvent->id]);
    }

    private function createPrompt(string $slug, string $title = 'Tracked Prompt'): AcademyPromptTemplate
    {
        return AcademyPromptTemplate::query()->create([
            'title' => $title,
            'slug' => $slug,
            'excerpt' => 'Prompt excerpt.',
            'prompt' => 'Prompt body.',
            'negative_prompt' => 'Negative prompt body.',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);
    }

    private function createLesson(string $slug): AcademyLesson
    {
        return AcademyLesson::query()->create([
            'title' => 'Tracked Lesson',
            'slug' => $slug,
            'excerpt' => 'Lesson excerpt.',
            'content' => 'Lesson body.',
            'difficulty' => 'beginner',
            'access_level' => 'free',
            'lesson_type' => 'article',
            'active' => true,
            'published_at' => now()->subMinute(),
        ]);
    }

    private function createCourse(string $slug): AcademyCourse
    {
        return AcademyCourse::query()->create([
            'title' => 'Tracked Course',
            'slug' => $slug,
            'excerpt' => 'Course excerpt.',
            'access_level' => 'free',
            'difficulty' => 'beginner',
            'status' => 'published',
            'published_at' => now()->subMinute(),
        ]);
    }

    /**
     * @param  array<string, mixed>  $attributes
     */
    private function createMetric(string $contentType, ?int $contentId, array $attributes = []): AcademyContentMetricDaily
    {
        return AcademyContentMetricDaily::query()->create(array_merge([
            'date' => now()->toDateString(),
            'content_type' => $contentType,
            'content_id' => $contentId,
            'views' => 0,
            'unique_visitors' => 0,
            'guest_views' => 0,
            'user_views' => 0,
            'subscriber_views' => 0,
            'engaged_views' => 0,
            'scroll_50' => 0,
            'scroll_75' => 0,
            'scroll_100' => 0,
            'likes' => 0,
            'saves' => 0,
            'prompt_copies' => 0,
            'negative_prompt_copies' => 0,
            'starts' => 0,
            'completions' => 0,
            'upgrade_clicks' => 0,
            'premium_preview_views' => 0,
            'search_impressions' => 0,
            'search_clicks' => 0,
            'bounce_count' => 0,
            'avg_engaged_seconds' => null,
            'popularity_score' => 0,
            'conversion_score' => 0,
        ], $attributes));
    }
}
