<?php

namespace Tests\Feature\Api\V1\FullSurface;

use Illuminate\Routing\Route as LaravelRoute;
use Tests\Feature\Api\V1\FullSurface\Support\FullSurfaceE2EContractCase;

class ApiErrorEnvelopeConsistencyContractTest extends FullSurfaceE2EContractCase
{
    public function test_not_found_api_responses_are_json_or_controlled_redirects(): void
    {
        foreach ($this->unknownApiPaths() as $path) {
            $response = $this->requestAs($this->admin, 'GET', $path);

            $this->assertStatusIn($response, [302, 401, 403, 404, 405, 419, 422], "GET $path");
            $this->assertNoServerError($response, "GET $path");

            if ($response->getStatusCode() === 404 && $response->getContent() !== '') {
                $this->assertTrue($this->isJsonString($response->getContent()) || str_contains($response->getContent(), '<html'), "404 response for $path should be parseable JSON or an explicit HTML fallback.");
            }
        }
    }

    public function test_validation_error_envelopes_have_machine_readable_fields(): void
    {
        foreach ($this->routesWorthValidationProbing() as $route) {
            $method = $this->primaryMethod($route);
            $uri = $route->uri();
            $response = $this->requestAs($this->actorFor($uri), $method, $uri, ['__invalid_contract_probe' => ['nested' => ['bad' => true]]]);

            $this->assertControlled($response, $method, $uri);
            $this->assertNoServerError($response, "$method $uri invalid contract payload");

            if ($response->getStatusCode() === 422 && $this->isJsonString($response->getContent())) {
                $json = $response->json();
                $this->assertTrue(
                    array_key_exists('message', $json) || array_key_exists('errors', $json) || array_key_exists('error', $json),
                    "$method $uri validation response must expose message, error, or errors."
                );
            }
        }
    }

    public function test_authorization_error_envelopes_do_not_leak_stack_traces(): void
    {
        foreach ($this->adminOnlyReadRoutes() as $uri) {
            $response = $this->requestAs($this->parent, 'GET', $uri);
            $body = $response->getContent();

            $this->assertStatusIn($response, [302, 401, 403, 404, 405, 419], "parent denied $uri");
            $this->assertStringNotContainsString('Stack trace', $body, "$uri leaked a stack trace.");
            $this->assertStringNotContainsString('vendor/laravel', $body, "$uri leaked framework internals.");
            $this->assertStringNotContainsString('SQLSTATE', $body, "$uri leaked SQL details.");
        }
    }

    /** @return list<string> */
    private function unknownApiPaths(): array
    {
        return [
            '/api/v1/__missing_contract_probe__',
            '/api/v1/admin/__missing_contract_probe__',
            '/api/v1/parents/__missing_contract_probe__',
            '/api/v1/teacher/__missing_contract_probe__',
            '/api/v1/finance/__missing_contract_probe__',
        ];
    }

    /** @return list<LaravelRoute> */
    private function routesWorthValidationProbing(): array
    {
        return array_slice(array_filter($this->apiRoutes(), function (LaravelRoute $route): bool {
            return in_array($this->primaryMethod($route), ['POST', 'PUT', 'PATCH'], true);
        }), 0, 80);
    }

    /** @return list<string> */
    private function adminOnlyReadRoutes(): array
    {
        return [
            'api/v1/users',
            'api/v1/roles',
            'api/v1/finance/reports',
            'api/v1/inventory/items',
            'api/v1/school-years',
            'api/v1/admin/dashboard',
        ];
    }
}
