<?php

namespace Tests\Feature;

use App\Models\File;
use App\Models\Meeting;
use App\Models\MeetingActionItem;
use App\Models\Permission;
use App\Models\Project;
use App\Models\Role;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
use Tests\TestCase;

class SecurityHardeningTest extends TestCase
{
    use RefreshDatabase;

    public function test_login_is_rate_limited(): void
    {
        for ($i = 0; $i < 5; $i++) {
            $this->postJson('/api/login', [
                'email' => 'missing@example.com',
                'password' => 'wrong-password',
            ])->assertUnauthorized();
        }

        $this->postJson('/api/login', [
            'email' => 'missing@example.com',
            'password' => 'wrong-password',
        ])->assertTooManyRequests();
    }

    public function test_file_upload_rejects_executable_files(): void
    {
        Storage::fake('local');

        $user = User::factory()->create(['status' => 'active']);
        $this->grantAdminRole($user);
        $project = Project::create([
            'title' => 'Security test project',
            'project_manager_id' => $user->id,
            'created_by' => $user->id,
        ]);
        $token = $user->createToken('api-token')->plainTextToken;

        $this->withToken($token)
            ->postJson('/api/files', [
                'project_id' => $project->id,
                'file' => UploadedFile::fake()->create('payload.php', 4, 'application/x-php'),
            ])
            ->assertUnprocessable();
    }

    public function test_file_resource_does_not_expose_storage_path(): void
    {
        $user = User::factory()->create(['status' => 'active']);
        $this->grantAdminRole($user);
        $file = File::create([
            'name' => 'safe.pdf',
            'original_name' => 'safe.pdf',
            'path' => 'files/private-safe.pdf',
            'mime_type' => 'application/pdf',
            'size' => 123,
            'fileable_type' => Project::class,
            'fileable_id' => 1,
            'user_id' => $user->id,
        ]);

        $token = $user->createToken('api-token')->plainTextToken;

        $this->withToken($token)
            ->getJson('/api/files')
            ->assertOk()
            ->assertJsonMissingPath('data.0.path')
            ->assertJsonPath('data.0.download_url', url("/api/files/{$file->id}"));
    }

    public function test_user_without_permission_cannot_access_user_management(): void
    {
        $user = User::factory()->create(['status' => 'active']);
        $token = $user->createToken('api-token')->plainTextToken;

        $this->withToken($token)
            ->getJson('/api/users')
            ->assertForbidden()
            ->assertJsonPath('success', false);
    }

    public function test_project_permission_does_not_expose_unrelated_projects(): void
    {
        $owner = User::factory()->create(['status' => 'active']);
        $viewer = User::factory()->create(['status' => 'active']);
        $permission = Permission::create([
            'name' => 'projects.view',
            'display_name' => 'View projects',
            'guard_name' => 'web',
            'module' => 'projects',
        ]);
        $role = Role::create(['name' => 'project-viewer', 'display_name' => 'Project viewer', 'guard_name' => 'web']);
        $role->permissions()->attach($permission);
        $viewer->roles()->attach($role);

        $foreignProject = Project::create([
            'title' => 'Private project',
            'project_manager_id' => $owner->id,
            'created_by' => $owner->id,
        ]);

        $token = $viewer->createToken('api-token')->plainTextToken;

        $this->withToken($token)
            ->getJson('/api/projects')
            ->assertOk()
            ->assertJsonPath('meta.total', 0);

        $this->withToken($token)
            ->getJson("/api/projects/{$foreignProject->id}")
            ->assertForbidden();
    }

    public function test_authenticated_user_cannot_change_another_user_status_without_permission(): void
    {
        $actor = User::factory()->create(['status' => 'active']);
        $target = User::factory()->create(['status' => 'active']);
        $token = $actor->createToken('api-token')->plainTextToken;

        $this->withToken($token)
            ->patchJson("/api/users/{$target->id}/status", ['status' => 'inactive'])
            ->assertForbidden();

        $this->assertSame('active', $target->fresh()->status);
    }

    public function test_user_cannot_download_file_from_unrelated_project(): void
    {
        $owner = User::factory()->create(['status' => 'active']);
        $viewer = User::factory()->create(['status' => 'active']);
        $permission = Permission::create([
            'name' => 'files.view',
            'display_name' => 'View files',
            'guard_name' => 'web',
            'module' => 'files',
        ]);
        $role = Role::create(['name' => 'file-viewer', 'display_name' => 'File viewer', 'guard_name' => 'web']);
        $role->permissions()->attach($permission);
        $viewer->roles()->attach($role);
        $project = Project::create([
            'title' => 'Private project',
            'project_manager_id' => $owner->id,
            'created_by' => $owner->id,
        ]);
        $file = File::create([
            'name' => 'private.pdf',
            'original_name' => 'private.pdf',
            'path' => 'files/private.pdf',
            'mime_type' => 'application/pdf',
            'size' => 10,
            'fileable_type' => Project::class,
            'fileable_id' => $project->id,
            'user_id' => $owner->id,
        ]);

        $token = $viewer->createToken('api-token')->plainTextToken;
        $this->withToken($token)->getJson("/api/files/{$file->id}")->assertForbidden();
    }

    public function test_meeting_action_item_must_belong_to_meeting_in_route(): void
    {
        $user = User::factory()->create(['status' => 'active']);
        $this->grantAdminRole($user);
        $project = Project::create([
            'title' => 'Meeting security',
            'project_manager_id' => $user->id,
            'created_by' => $user->id,
        ]);
        $first = Meeting::create([
            'title' => 'First',
            'project_id' => $project->id,
            'date' => now()->toDateString(),
            'meeting_type' => 'other',
            'created_by' => $user->id,
        ]);
        $second = Meeting::create([
            'title' => 'Second',
            'project_id' => $project->id,
            'date' => now()->toDateString(),
            'meeting_type' => 'other',
            'created_by' => $user->id,
        ]);
        $item = MeetingActionItem::create([
            'meeting_id' => $second->id,
            'title' => 'Private action',
        ]);

        $token = $user->createToken('api-token')->plainTextToken;
        $this->withToken($token)
            ->putJson("/api/meetings/{$first->id}/action-items/{$item->id}", ['title' => 'Tampered'])
            ->assertForbidden();

        $this->assertSame('Private action', $item->fresh()->title);
    }
}
