<?php

use App\Models\Branch;
use App\Models\User;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

test('API responses carry baseline security headers', function () {
    $branch = Branch::factory()->create();
    $admin = User::factory()->branchAdmin()->create(['branch_id' => $branch->id]);

    $response = $this->actingAs($admin)->getJson('/api/v1/members');

    $response->assertOk();
    $response->assertHeader('X-Content-Type-Options', 'nosniff');
    $response->assertHeader('X-Frame-Options', 'DENY');
    $response->assertHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
});

test('the global API rate limit returns 429 once exceeded', function () {
    RateLimiter::for('api', fn () => Limit::perMinute(2)->by('test-fixed-key'));

    $branch = Branch::factory()->create();
    $admin = User::factory()->branchAdmin()->create(['branch_id' => $branch->id]);

    $this->actingAs($admin)->getJson('/api/v1/members')->assertOk();
    $this->actingAs($admin)->getJson('/api/v1/members')->assertOk();
    $this->actingAs($admin)->getJson('/api/v1/members')->assertStatus(429);
});

test('the search endpoint has its own stricter rate limit', function () {
    RateLimiter::for('search', fn () => Limit::perMinute(1)->by('test-fixed-search-key'));

    $branch = Branch::factory()->create();
    $admin = User::factory()->branchAdmin()->create(['branch_id' => $branch->id]);

    $this->actingAs($admin)->getJson('/api/v1/members/search?q=an')->assertOk();
    $this->actingAs($admin)->getJson('/api/v1/members/search?q=an')->assertStatus(429);
});

test('a server exception never leaks a stack trace when APP_DEBUG is off', function () {
    config(['app.debug' => false]);

    $branch = Branch::factory()->create();
    $admin = User::factory()->branchAdmin()->create(['branch_id' => $branch->id]);

    // membership_status is required at the DB level; forcing a raw invalid
    // enum value through validated() is out of reach from HTTP, so instead
    // assert the documented shape of the *would-be* 500 body directly via
    // the exception renderer registered in bootstrap/app.php.
    $request = Request::create('/api/v1/members', 'GET');
    $handler = app(ExceptionHandler::class);
    $response = $handler->render($request, new RuntimeException('should never reach the client'));

    expect($response->getStatusCode())->toBe(500);
    expect($response->getContent())->toBe('{"message":"Server error."}');
    expect($response->getContent())->not->toContain('should never reach the client');
});

test('an unauthenticated cross-site POST without a CSRF token is rejected', function () {
    // No GET /sanctum/csrf-cookie call precedes this, so no XSRF-TOKEN
    // cookie/header pair exists — Sanctum's stateful guard must still 401
    // rather than treating the request as authenticated.
    $this->postJson('/api/v1/members', [])->assertStatus(401);
});

test('unauthenticated request to a protected endpoint is rejected before CSRF matters', function () {
    $this->postJson('/api/v1/households', [])->assertStatus(401);
});
