<?php

namespace Tests\Feature;

use Illuminate\Support\Facades\Route;
use Tests\TestCase;

/**
 * The 500 handler in bootstrap/app.php builds a rich diagnostic payload — stack trace,
 * request input, headers, session and the last SQL queries — and errors/500.blade.php
 * renders it into the page. That payload was not gated on APP_DEBUG, so any 500 on a
 * production tenant disclosed the signed-in user's session (which carries
 * `password_hash_web`, their bcrypt hash) to whoever triggered the error.
 *
 * These tests pin the gate. If someone removes it, this file fails.
 */
class ErrorPageDisclosureTest extends TestCase
{
    private const SECRET_HASH = '$2y$10$ThisIsTheUsersBcryptHashDoNotLeakIt';

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

        Route::get('/__boom', function () {
            session()->put('password_hash_web', self::SECRET_HASH);
            // Deliberately not the real recaller key (which would make the guard hit the
            // DB); this only has to prove the `login_` prefix is stripped.
            session()->put('login_web_deadbeefdeadbeef', 1);
            session()->put('harmless_key', 'harmless_value');

            throw new \RuntimeException('SQLSTATE[42P01]: select * from "secret_table" where "national_id" = 29001011234567');
        })->middleware('web');
    }

    public function test_production_error_page_discloses_nothing_but_the_error_id(): void
    {
        config(['app.debug' => false]);

        $response = $this->get('/__boom');

        $response->assertStatus(500);

        $body = $response->getContent();

        // The whole point: the auth session must never reach the browser.
        $this->assertStringNotContainsString(self::SECRET_HASH, $body);
        $this->assertStringNotContainsString('password_hash_web', $body);
        $this->assertStringNotContainsString('login_web_', $body);

        // Nor the rest of the diagnostic payload.
        $this->assertStringNotContainsString('harmless_value', $body, 'session contents leaked');
        $this->assertStringNotContainsString('secret_table', $body, 'exception message leaked');
        $this->assertStringNotContainsString('29001011234567', $body, 'query bindings leaked');
        $this->assertStringNotContainsString('RuntimeException', $body, 'exception class leaked');
        $this->assertStringNotContainsString('bootstrap/app.php', $body, 'stack trace leaked');
        $this->assertStringNotContainsString('cdn.tailwindcss.com', $body, 'error page must not call out to a CDN');

        // The user still gets the one thing that lets support find the log line.
        $response->assertSee('حدث خطأ غير متوقع');
    }

    public function test_debug_error_page_still_shows_diagnostics_to_the_developer(): void
    {
        config(['app.debug' => true]);

        $response = $this->get('/__boom');

        $response->assertStatus(500);

        $body = $response->getContent();

        $this->assertStringContainsString('RuntimeException', $body);
        $this->assertStringContainsString('secret_table', $body);

        // Even in debug the auth keys are stripped — a shared screen or a pasted
        // bug report should never carry a password hash.
        $this->assertStringNotContainsString(self::SECRET_HASH, $body);
    }
}
