<?php

namespace Tests\Feature\Api;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Routing\Route as LaravelRoute;
use Illuminate\Support\Facades\Route;
use Tests\TestCase;

/**
 * Route-to-use-case coverage guard.
 *
 * This does not replace workflow tests. It prevents the slower rot where a new
 * endpoint is added with no explicit business workflow owner, because apparently
 * endpoints reproduce when nobody is watching them.
 */
class ApiUseCaseCoverageMatrixTest extends TestCase
{
    use RefreshDatabase;

    public function test_every_api_route_is_mapped_to_an_explicit_use_case_domain(): void
    {
        $unmapped = [];

        foreach ($this->apiRoutes() as $route) {
            $uri = $route->uri();

            if ($this->domainFor($uri) === null) {
                $unmapped[] = implode('|', array_diff($route->methods(), ['HEAD'])) . ' ' . $uri;
            }
        }

        $this->assertSame(
            [],
            $unmapped,
            "API routes without an explicit use-case domain:\n" . implode("\n", $unmapped) .
            "\n\nAdd the route to the matrix and then add/extend an E2E workflow test for that domain."
        );
    }

    public function test_required_use_case_domains_have_route_coverage(): void
    {
        $domains = collect($this->apiRoutes())
            ->map(fn (LaravelRoute $route): ?string => $this->domainFor($route->uri()))
            ->filter()
            ->unique()
            ->sort()
            ->values()
            ->all();

        $missing = array_values(array_diff(array_keys($this->useCaseDomains()), $domains));

        $this->assertSame(
            [],
            $missing,
            "Required use-case domains with no matching API route coverage:\n" . implode("\n", $missing)
        );
    }

    public function test_every_protected_api_route_rejects_anonymous_requests(): void
    {
        $failures = [];

        foreach ($this->protectedRoutes() as $route) {
            $method = $this->primaryMethod($route);
            $uri = '/' . $this->uriWithSafePlaceholders($route->uri());

            $response = $this->json($method, $uri);
            $status = $response->getStatusCode();

            if (! in_array($status, [401, 403, 404, 405, 419, 422], true)) {
                $failures[] = "$method $uri returned $status";
            }
        }

        $this->assertSame(
            [],
            $failures,
            "Anonymous requests reached protected API routes unexpectedly:\n" . implode("\n", $failures)
        );
    }

    /**
     * @return array<string, string>
     */
    private function useCaseDomains(): array
    {
        return [
            'public_content_and_support' => '#^api/(?:v1/)?(?:login|register|auth|documentation|docs|docs/public|policies|pages|contact|frontend|health|system/db-check|access_denied|certificates/verify|timeoff/notify|winners/competitions|confirm_authorized_user|set_authorized_user_password|badge_scan|scanner)(?:/|$)#',
            'legacy_compatibility' => '#^api/(?:proofread|emails|compare|attendance-templates|attendance-comment-templates|administrator/attendance-templates)(?:/|$)#',
            'preferences_navigation_and_ui' => '#^api/v1/(?:preferences|nav-builder|landing|info-icon|dashboard|utilities|ui|settings|configuration|system|stats|role-switcher)(?:/|$)#',
            'administration_enrollment_and_school_years' => '#^api/v1/(?:administrator|school-years|class-sections|users|role-permissions|ip-bans|notifications)(?:/|$)#',
            'students_parents_and_families' => '#^api/v1/(?:students|parents|families|family-admin|emergency-contacts)(?:/|$)#',
            'teachers_staff_and_class_operations' => '#^api/v1/(?:teacher|teachers|staff|assignments|class-prep|class-progress)(?:/|$)#',
            'attendance_and_safety' => '#^api/v1/(?:attendance|attendance-tracking|attendance-comment-templates|attendance-templates|incidents)(?:/|$)#',
            'grading_scores_and_reporting' => '#^api/v1/(?:scores|grading|reports|badges|certificates|print-requests)(?:/|$)#',
            'finance_billing_and_inventory' => '#^api/v1/(?:finance|discounts|expenses|extra-charges|inventory|files)(?:/|$)#',
            'communications_and_messaging' => '#^api/v1/(?:messages|broadcast-email|email|email-extractor|communications|support|whatsapp)(?:/|$)#',
            'competition_and_public_results' => '#^api/v1/(?:competition-scores)(?:/|$)#',
        ];
    }

    private function domainFor(string $uri): ?string
    {
        foreach ($this->useCaseDomains() as $domain => $pattern) {
            if (preg_match($pattern, $uri) === 1) {
                return $domain;
            }
        }

        return null;
    }

    /**
     * @return list<LaravelRoute>
     */
    private function apiRoutes(): array
    {
        return array_values(array_filter(Route::getRoutes()->getRoutes(), function (LaravelRoute $route): bool {
            return str_starts_with($route->uri(), 'api/');
        }));
    }

    /**
     * @return list<LaravelRoute>
     */
    private function protectedRoutes(): array
    {
        return array_values(array_filter($this->apiRoutes(), function (LaravelRoute $route): bool {
            if ($this->isStreamOrGeneratedFileRoute($route->uri())) {
                return false;
            }

            return collect($route->gatherMiddleware())->contains(function (string $middleware): bool {
                return str_contains($middleware, 'auth:api')
                    || str_contains($middleware, 'jwt.auth')
                    || str_contains($middleware, 'admin.access')
                    || str_contains($middleware, 'parent.access')
                    || str_contains($middleware, 'teacher.portal.auth');
            });
        }));
    }

    private function isStreamOrGeneratedFileRoute(string $uri): bool
    {
        return str_contains($uri, 'files/')
            || str_contains($uri, 'receipts/')
            || str_contains($uri, 'reimbursements/')
            || str_contains($uri, 'attachments/')
            || str_contains($uri, '/pdf')
            || str_contains($uri, '/csv');
    }

    private function primaryMethod(LaravelRoute $route): string
    {
        foreach ($route->methods() as $method) {
            if (! in_array($method, ['HEAD', 'OPTIONS'], true)) {
                return $method;
            }
        }

        return 'GET';
    }

    private function uriWithSafePlaceholders(string $uri): string
    {
        return preg_replace('/\{[^}]+\}/', '1', $uri) ?? $uri;
    }
}
