<?php

use App\Models\User;
use cPad\Plugins\Forum\Models\ForumBoard;
use cPad\Plugins\Forum\Models\ForumCategory;
use cPad\Plugins\Forum\Models\ForumPost;
use cPad\Plugins\Forum\Models\ForumTopic;
use cPad\Plugins\Forum\Services\ForumModerationService;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Schema;

uses(Tests\TestCase::class);

it('enforces forum write rate limits for thread creation and replies', function () {
    ensureForumRateLimitModelClassesLoaded();

    Queue::fake();

    config()->set('forum_bot_protection.enabled', false);
    config()->set('forum_bot_protection.behavior.new_account_days', 0);
    config()->set('skinbase_ai_moderation.enabled', false);

    $moderationService = \Mockery::mock(ForumModerationService::class);
    $moderationService->shouldReceive('preflight')->andReturnUsing(static function ($user, $content, $sourceIp): array {
        return [
            'spam_score' => 0,
            'quality_score' => 0,
            'ai_spam_score' => 0,
            'ai_toxicity_score' => 0,
            'behavior_score' => 0,
            'link_score' => 0,
            'learning_score' => 0,
            'risk_score' => 0,
            'trust_modifier' => 0,
            'decision' => 'allowed',
            'captcha_required' => false,
            'blocked' => false,
            'requires_review' => false,
            'flagged' => false,
            'reason' => null,
            'content_hash' => hash('sha256', (string) $content),
            'pattern_signature' => null,
            'source_ip_hash' => $sourceIp ? hash('sha256', $sourceIp) : null,
            'moderation_labels' => ['preflight', 'allowed'],
            'provider' => 'none',
            'provider_available' => false,
            'language' => null,
        ];
    });
    $moderationService->shouldReceive('applyPreflightAssessment')->andReturnUsing(static function (ForumPost $post, array $assessment): void {
        $post->forceFill([
            'source_ip_hash' => $assessment['source_ip_hash'] ?? $post->source_ip_hash,
            'content_hash' => $assessment['content_hash'] ?? $post->content_hash,
            'spam_score' => (int) ($assessment['spam_score'] ?? 0),
            'quality_score' => (int) ($assessment['quality_score'] ?? 0),
            'ai_spam_score' => (int) ($assessment['ai_spam_score'] ?? 0),
            'ai_toxicity_score' => (int) ($assessment['ai_toxicity_score'] ?? 0),
            'behavior_score' => (int) ($assessment['behavior_score'] ?? 0),
            'link_score' => (int) ($assessment['link_score'] ?? 0),
            'learning_score' => (int) ($assessment['learning_score'] ?? 0),
            'risk_score' => (int) ($assessment['risk_score'] ?? 0),
            'trust_modifier' => (int) ($assessment['trust_modifier'] ?? 0),
            'flagged' => (bool) ($assessment['flagged'] ?? false),
            'flagged_reason' => $assessment['reason'] ?? null,
            'moderation_checked' => false,
            'moderation_status' => 'pending_ai_scan',
            'moderation_labels' => (array) ($assessment['moderation_labels'] ?? []),
            'moderation_meta' => [
                'provider' => $assessment['provider'] ?? 'none',
                'provider_available' => (bool) ($assessment['provider_available'] ?? false),
                'language' => $assessment['language'] ?? null,
            ],
        ])->save();
    });
    $moderationService->shouldReceive('logRequestSecurity')->andReturnNull();
    $moderationService->shouldReceive('dispatchAsyncScan')->andReturnNull();

    $this->app->instance(ForumModerationService::class, $moderationService);

    createForumRateLimitTestSchema();

    $user = User::query()->create([
        'username' => 'ratelimit-user',
        'username_changed_at' => now()->subDays(120),
        'last_username_change_at' => now()->subDays(120),
        'onboarding_step' => 'complete',
        'name' => 'Rate Limit User',
        'email' => 'ratelimit@example.com',
        'email_verified_at' => now(),
        'password' => 'password',
        'is_active' => true,
    ]);

    markForumRateLimitUserAsEstablished($user);

    $board = makeForumBoard('thread-limit');

    clearForumRateLimiters((string) $user->id);

    for ($attempt = 1; $attempt <= 3; $attempt++) {
        $response = $this->actingAs($user)->post(route('forum.topic.store', ['boardSlug' => $board->slug]), [
            'title' => 'Rate limit topic ' . $attempt,
            'content' => 'Thread body ' . $attempt,
        ]);

        $response->assertRedirect();
    }

    $this->actingAs($user)->post(route('forum.topic.store', ['boardSlug' => $board->slug]), [
        'title' => 'Rate limit topic 4',
        'content' => 'Thread body 4',
    ])->assertStatus(429);

    expect(ForumTopic::query()->count())->toBe(3)
        ->and(ForumPost::query()->count())->toBe(3);

    clearForumRateLimiters((string) $user->id);

    $replyUser = User::query()->create([
        'username' => 'reply-limit-user',
        'username_changed_at' => now()->subDays(120),
        'last_username_change_at' => now()->subDays(120),
        'onboarding_step' => 'complete',
        'name' => 'Reply Limit User',
        'email' => 'replylimit@example.com',
        'email_verified_at' => now(),
        'password' => 'password',
        'is_active' => true,
    ]);

    markForumRateLimitUserAsEstablished($replyUser);

    clearForumRateLimiters((string) $replyUser->id);

    $topic = makeForumTopic($replyUser);

    for ($attempt = 1; $attempt <= 3; $attempt++) {
        $response = $this->actingAs($replyUser)->post(route('forum.topic.reply', ['topic' => $topic->slug]), [
            'content' => 'Reply burst ' . $attempt,
        ]);

        $response->assertRedirect();
    }

    $this->actingAs($replyUser)->post(route('forum.topic.reply', ['topic' => $topic->slug]), [
        'content' => 'Reply burst 4',
    ])->assertStatus(429);

    expect(ForumPost::query()->where('topic_id', $topic->id)->count())->toBe(4);

    clearForumRateLimiters((string) $user->id);
    clearForumRateLimiters((string) $replyUser->id);
});

function createForumRateLimitTestSchema(): void
{
    foreach ([
        'forum_security_logs',
        'forum_firewall_logs',
        'forum_bot_ip_blacklist',
        'forum_spam_signatures',
        'forum_spam_learning',
        'forum_spam_domains',
        'forum_spam_keywords',
        'forum_topic_tags',
        'forum_tags',
        'forum_posts',
        'forum_topics',
        'forum_threads',
        'forum_boards',
        'forum_categories',
        'users',
    ] as $table) {
        Schema::dropIfExists($table);
    }

    Schema::create('forum_bot_ip_blacklist', function (Blueprint $table): void {
        $table->id();
        $table->string('ip_address', 45)->unique();
        $table->string('reason', 255)->nullable();
        $table->unsignedTinyInteger('risk_score')->default(100);
        $table->timestamp('expires_at')->nullable();
        $table->timestamp('created_at')->nullable();
    });

    Schema::create('forum_firewall_logs', function (Blueprint $table): void {
        $table->id();
        $table->unsignedBigInteger('user_id')->nullable();
        $table->string('ip_address', 45)->nullable();
        $table->string('action', 80);
        $table->unsignedTinyInteger('risk_score')->default(0);
        $table->string('decision', 20)->default('allow');
        $table->string('threat_type', 80)->nullable();
        $table->string('reason', 255)->nullable();
        $table->json('metadata')->nullable();
        $table->timestamps();
    });

    Schema::create('forum_security_logs', function (Blueprint $table): void {
        $table->id();
        $table->unsignedBigInteger('user_id')->nullable();
        $table->unsignedBigInteger('post_id')->nullable();
        $table->string('ip_address', 45)->nullable();
        $table->string('action', 80);
        $table->unsignedTinyInteger('risk_score')->default(0);
        $table->string('decision', 20)->default('allow');
        $table->string('reason', 255)->nullable();
        $table->json('layer_scores')->nullable();
        $table->json('metadata')->nullable();
        $table->timestamps();
    });

    Schema::create('forum_spam_signatures', function (Blueprint $table): void {
        $table->id();
        $table->string('content_hash', 64)->nullable()->index();
        $table->string('pattern_signature', 191)->nullable()->index();
        $table->string('source', 32)->nullable();
        $table->string('reason', 255)->nullable();
        $table->unsignedInteger('confidence')->default(0);
        $table->unsignedBigInteger('reviewed_by')->nullable();
        $table->json('metadata')->nullable();
        $table->timestamp('created_at')->nullable();
    });

    Schema::create('users', function (Blueprint $table): void {
        $table->id();
        $table->string('username')->nullable();
        $table->timestamp('username_changed_at')->nullable();
        $table->timestamp('last_username_change_at')->nullable();
        $table->string('onboarding_step')->nullable();
        $table->string('name')->nullable();
        $table->string('email')->nullable();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password')->nullable();
        $table->boolean('is_active')->default(true);
        $table->unsignedInteger('trust_score')->default(0);
        $table->unsignedInteger('approved_posts')->default(0);
        $table->unsignedInteger('flagged_posts')->default(0);
        $table->string('role')->nullable();
        $table->rememberToken();
        $table->timestamps();
        $table->softDeletes();
    });

    Schema::create('forum_categories', function (Blueprint $table): void {
        $table->id();
        $table->string('name')->nullable();
        $table->string('title')->nullable();
        $table->string('slug')->unique();
        $table->text('description')->nullable();
        $table->unsignedBigInteger('parent_id')->nullable();
        $table->integer('position')->default(0);
        $table->boolean('is_active')->default(true);
        $table->timestamps();
    });

    Schema::create('forum_boards', function (Blueprint $table): void {
        $table->id();
        $table->unsignedBigInteger('category_id');
        $table->unsignedBigInteger('legacy_category_id')->nullable();
        $table->string('title');
        $table->string('slug')->unique();
        $table->text('description')->nullable();
        $table->string('icon')->nullable();
        $table->string('image')->nullable();
        $table->integer('position')->default(0);
        $table->boolean('is_active')->default(true);
        $table->boolean('is_read_only')->default(false);
        $table->timestamps();
    });

    Schema::create('forum_threads', function (Blueprint $table): void {
        $table->id();
        $table->unsignedBigInteger('category_id');
        $table->unsignedBigInteger('user_id');
        $table->string('title');
        $table->string('slug')->unique();
        $table->longText('content');
        $table->unsignedInteger('views')->default(0);
        $table->boolean('is_locked')->default(false);
        $table->boolean('is_pinned')->default(false);
        $table->string('visibility')->default('public');
        $table->timestamp('last_post_at')->nullable();
        $table->timestamps();
        $table->softDeletes();
    });

    Schema::create('forum_topics', function (Blueprint $table): void {
        $table->id();
        $table->unsignedBigInteger('board_id');
        $table->unsignedBigInteger('user_id');
        $table->unsignedBigInteger('artwork_id')->nullable();
        $table->unsignedBigInteger('legacy_thread_id')->nullable();
        $table->string('title');
        $table->string('slug')->unique();
        $table->unsignedInteger('views')->default(0);
        $table->unsignedInteger('replies_count')->default(0);
        $table->boolean('is_pinned')->default(false);
        $table->boolean('is_locked')->default(false);
        $table->boolean('is_deleted')->default(false);
        $table->unsignedBigInteger('last_post_id')->nullable();
        $table->timestamp('last_post_at')->nullable();
        $table->timestamps();
    });

    Schema::create('forum_posts', function (Blueprint $table): void {
        $table->id();
        $table->unsignedBigInteger('thread_id');
        $table->unsignedBigInteger('topic_id')->nullable();
        $table->string('source_ip_hash', 64)->nullable();
        $table->unsignedBigInteger('user_id');
        $table->longText('content');
        $table->string('content_hash', 64)->nullable();
        $table->boolean('is_edited')->default(false);
        $table->timestamp('edited_at')->nullable();
        $table->unsignedInteger('spam_score')->default(0);
        $table->unsignedInteger('quality_score')->default(0);
        $table->unsignedInteger('ai_spam_score')->default(0);
        $table->unsignedInteger('ai_toxicity_score')->default(0);
        $table->unsignedInteger('behavior_score')->default(0);
        $table->unsignedInteger('link_score')->default(0);
        $table->integer('learning_score')->default(0);
        $table->unsignedInteger('risk_score')->default(0);
        $table->integer('trust_modifier')->default(0);
        $table->boolean('flagged')->default(false);
        $table->string('flagged_reason')->nullable();
        $table->boolean('moderation_checked')->default(false);
        $table->string('moderation_status')->nullable();
        $table->json('moderation_labels')->nullable();
        $table->json('moderation_meta')->nullable();
        $table->timestamp('last_ai_scan_at')->nullable();
        $table->timestamps();
        $table->softDeletes();
    });

    Schema::create('forum_tags', function (Blueprint $table): void {
        $table->id();
        $table->string('name')->unique();
        $table->string('slug')->unique();
        $table->timestamps();
    });

    Schema::create('forum_topic_tags', function (Blueprint $table): void {
        $table->id();
        $table->unsignedBigInteger('topic_id');
        $table->unsignedBigInteger('tag_id');
        $table->timestamps();

        $table->unique(['topic_id', 'tag_id']);
    });

    Schema::create('forum_spam_domains', function (Blueprint $table): void {
        $table->id();
        $table->string('domain')->unique();
        $table->timestamps();
    });

    Schema::create('forum_spam_keywords', function (Blueprint $table): void {
        $table->id();
        $table->string('keyword', 120)->unique();
        $table->timestamp('created_at')->nullable();
    });

    Schema::create('forum_spam_learning', function (Blueprint $table): void {
        $table->id();
        $table->string('content_hash', 64)->index();
        $table->string('decision', 32);
        $table->string('pattern_signature', 191)->nullable();
        $table->unsignedBigInteger('reviewed_by')->nullable();
        $table->json('metadata')->nullable();
        $table->timestamps();
    });
}

function ensureForumRateLimitModelClassesLoaded(): void
{
    foreach ([
        'packages/klevze/Plugins/Forum/Models/ForumPost.php',
        'packages/klevze/Plugins/Forum/Models/ForumSpamLearning.php',
        'packages/klevze/Plugins/Forum/Models/ForumAiLog.php',
        'packages/klevze/Plugins/Forum/Models/ForumModerationQueue.php',
    ] as $relativePath) {
        require_once base_path($relativePath);
    }
}

function makeForumBoard(string $suffix): ForumBoard
{
    $category = ForumCategory::query()->create([
        'name' => 'Rate Limit Category ' . $suffix,
        'title' => 'Rate Limit Category ' . $suffix,
        'slug' => 'rate-limit-category-' . $suffix,
        'description' => 'Test category',
        'position' => 1,
        'is_active' => true,
        'parent_id' => null,
    ]);

    return ForumBoard::query()->create([
        'category_id' => $category->id,
        'title' => 'Rate Limit Board ' . $suffix,
        'slug' => 'rate-limit-board-' . $suffix,
        'description' => 'Test board',
        'position' => 1,
        'is_active' => true,
        'is_read_only' => false,
    ]);
}

function makeForumTopic(User $user): ForumTopic
{
    $board = makeForumBoard('reply-limit');

    $legacyThreadId = DB::table('forum_threads')->insertGetId([
        'category_id' => $board->category_id,
        'user_id' => $user->id,
        'title' => 'Existing topic',
        'slug' => 'existing-topic-reply-limit',
        'content' => 'Opening post',
        'views' => 0,
        'is_locked' => false,
        'is_pinned' => false,
        'visibility' => 'public',
        'last_post_at' => now(),
        'created_at' => now(),
        'updated_at' => now(),
    ]);

    $topic = ForumTopic::query()->create([
        'board_id' => $board->id,
        'user_id' => $user->id,
        'legacy_thread_id' => $legacyThreadId,
        'title' => 'Existing topic',
        'slug' => 'existing-topic-reply-limit',
        'views' => 0,
        'replies_count' => 0,
        'is_pinned' => false,
        'is_locked' => false,
        'is_deleted' => false,
        'last_post_at' => now(),
    ]);

    $post = ForumPost::query()->create([
        'thread_id' => $legacyThreadId,
        'topic_id' => $topic->id,
        'user_id' => $user->id,
        'content' => 'Opening post',
        'is_edited' => false,
    ]);

    $topic->forceFill([
        'last_post_id' => $post->id,
        'last_post_at' => $post->created_at,
    ])->save();

    return $topic;
}

function clearForumRateLimiters(string $key): void
{
    foreach ([
        'forum-thread-minute:' . $key,
        'forum-thread-hour:' . $key,
        'forum-post-minute:' . $key,
        'forum-post-hour:' . $key,
    ] as $limiterKey) {
        RateLimiter::clear($limiterKey);
    }
}

function markForumRateLimitUserAsEstablished(User $user): void
{
    $timestamp = now()->subDays(30);

    $user->forceFill([
        'created_at' => $timestamp,
        'updated_at' => $timestamp,
    ])->save();
}