<?php

namespace Tests\Feature\Api;

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

class ApiRouteContractTest extends TestCase
{
    use RefreshDatabase;

    public function test_every_api_route_points_to_an_existing_controller_method(): void
    {
        $missing = [];

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

            if ($action === 'Closure' || ! str_contains($action, '@')) {
                continue;
            }

            [$class, $method] = explode('@', $action, 2);
            if (! class_exists($class) || ! method_exists($class, $method)) {
                $missing[] = implode('|', $route->methods()) . ' ' . $route->uri() . ' -> ' . $action;
            }
        }

        $this->assertSame([], $missing, "These API routes reference missing controllers or methods:\n" . implode("\n", $missing));
    }

    public function test_api_routes_do_not_duplicate_method_and_uri_pairs(): void
    {
        $seen = [];
        $duplicates = [];

        foreach ($this->apiRoutes() as $route) {
            foreach ($route->methods() as $method) {
                if ($method === 'HEAD') {
                    continue;
                }

                $key = $method . ' ' . $route->uri();
                if (isset($seen[$key])) {
                    $duplicates[] = $key;
                }
                $seen[$key] = true;
            }
        }

        $this->assertSame([], array_values(array_unique($duplicates)), "Duplicate API route method/URI pairs found. Ambiguous routing is not a feature, despite humanity's best efforts.");
    }

    public function test_mutating_api_routes_are_protected_unless_explicitly_public(): void
    {
        $publicAllowList = [
            'api/login',
            'api/register',
            'api/v1/login',
            'api/v1/register',
            'api/v1/auth/login',
            'api/v1/auth/register',
            'api/v1/pages/contact',
            'api/v1/contact',
            'api/set_authorized_user_password/{authorizedUserId}',
            'api/v1/badge_scan/scan',
            'api/v1/scanner/process',
        ];

        $unprotected = [];

        foreach ($this->apiRoutes() as $route) {
            $methods = array_diff($route->methods(), ['HEAD', 'OPTIONS']);
            if (array_intersect($methods, ['POST', 'PUT', 'PATCH', 'DELETE']) === []) {
                continue;
            }

            if (in_array($route->uri(), $publicAllowList, true)) {
                continue;
            }

            $middleware = $route->gatherMiddleware();
            $hasProtection = collect($middleware)->contains(function (string $middleware): bool {
                return str_contains($middleware, 'auth')
                    || str_contains($middleware, 'throttle')
                    || str_contains($middleware, 'teacher.portal.auth')
                    || str_contains($middleware, 'badge_scan');
            });

            if (! $hasProtection) {
                $unprotected[] = implode('|', $methods) . ' ' . $route->uri();
            }
        }

        $this->assertSame([], $unprotected, "Mutating API routes without an obvious auth/throttle middleware:\n" . implode("\n", $unprotected));
    }

    /**
     * @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/');
        }));
    }
}
