<?php

namespace Tests\Feature\Api\V1\FullSurface;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Routing\Route as LaravelRoute;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Route;
use Tests\Concerns\AssertsE2EApiResponses;
use Tests\Concerns\SeedsE2ETestFixtures;
use Tests\TestCase;

/**
 * Full API surface executable contract probes.
 *
 * These tests intentionally walk the registered Laravel routes instead of only
 * the endpoints humans remembered to write by hand. Humans forget things. Routes
 * do not. This does not replace the named workflow scenarios; it makes the
 * whole API surface prove that each endpoint has a controlled response shape,
 * auth boundary, and validation boundary under realistic seeded context.
 */
class ApiFullSurfaceEndToEndContractTest extends TestCase
{
    use RefreshDatabase;
    use SeedsE2ETestFixtures;
    use AssertsE2EApiResponses;

    /**
     * @var array<string, int|string>
     */
    private array $ids = [];

    private User $admin;
    private User $teacher;
    private User $parent;
    private User $studentUser;

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

        $this->seedE2EConfiguration();
        $world = $this->seedTeacherClassWithStudent(9101);

        $this->teacher = $world['teacher'];
        $this->parent = $world['parent'];
        $this->studentUser = $this->createApiUserWithRole('student');
        $this->admin = $this->createApiUserWithRole('administrator');

        $invoiceId = $this->seedInvoiceForParent((int) $world['parent_id']);
        $voucherId = $this->seedDiscountVoucher();

        $this->ids = [
            'id' => 1,
            'userId' => $this->admin->id,
            'roleId' => (int) DB::table('roles')->where('name', 'parent')->value('id'),
            'permissionId' => 1,
            'student' => (int) $world['student_id'],
            'studentId' => (int) $world['student_id'],
            'parent' => (int) $world['parent_id'],
            'parentId' => (int) $world['parent_id'],
            'teacher' => $this->teacher->id,
            'teacherId' => $this->teacher->id,
            'classSection' => (int) $world['class_section_id'],
            'classSectionId' => (int) $world['class_section_id'],
            'class_section_id' => (int) $world['class_section_id'],
            'invoice' => $invoiceId,
            'invoiceId' => $invoiceId,
            'payment' => 1,
            'paymentId' => 1,
            'refund' => 1,
            'refundId' => 1,
            'voucher' => $voucherId,
            'schoolYear' => (int) DB::table('school_years')->where('name', self::E2E_SCHOOL_YEAR)->value('id'),
            'promotionId' => 1,
            'batchId' => 1,
            'contactId' => 1,
            'familyId' => 1,
            'authorizedUserId' => 1,
            'eventCharge' => 1,
            'plan' => 1,
            'carryforward' => 1,
            'installment' => 1,
            'certificateNumber' => 'CERT-E2E-001',
            'token' => 'invalid-token-for-contract-probe',
            'name' => 'placeholder.txt',
            'mode' => 'view',
        ];
    }

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

        foreach ($this->probeableApiRoutes() as $route) {
            $method = $this->primaryMethod($route);
            $uri = '/' . $this->materializeUri($route->uri());
            $actor = $this->actorFor($route->uri());
            $payload = $this->payloadFor($method, $route->uri());

            $this->actingAs($actor, 'api');
            $response = $this->json($method, $uri, $payload);
            $status = $response->getStatusCode();

            if ($status >= 500) {
                $failures[] = "$method $uri crashed with $status: " . $response->getContent();
                continue;
            }

            if (! in_array($status, $this->allowedContractStatuses($method), true)) {
                $failures[] = "$method $uri returned unexpected $status: " . $response->getContent();
            }
        }

        $this->assertSame([], $failures, "Full-surface API contract failures:\n" . implode("\n", $failures));
    }

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

        foreach ($this->probeableApiRoutes() as $route) {
            $method = $this->primaryMethod($route);
            if (! in_array($method, ['POST', 'PUT', 'PATCH', 'DELETE'], true)) {
                continue;
            }

            $uri = '/' . $this->materializeUri($route->uri());
            $this->actingAs($this->actorFor($route->uri()), 'api');
            $response = $this->json($method, $uri, []);
            $status = $response->getStatusCode();

            if ($status >= 500) {
                $failures[] = "$method $uri malformed payload crashed with $status: " . $response->getContent();
            }
        }

        $this->assertSame([], $failures, "Malformed-payload probes must fail cleanly, not explode:\n" . implode("\n", $failures));
    }

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

        foreach ($this->portalBoundaryProbes() as [$actor, $method, $uri]) {
            $this->actingAs($actor, 'api');
            $response = $this->json($method, $this->materializePath($uri), $this->payloadFor($method, ltrim($uri, '/')));
            $status = $response->getStatusCode();

            if (! in_array($status, [401, 403, 404, 405, 419, 422], true)) {
                $failures[] = "$method $uri allowed wrong actor with $status: " . $response->getContent();
            }
        }

        $this->assertSame([], $failures, "Wrong-role portal boundary failures:\n" . implode("\n", $failures));
    }

    /**
     * @return list<LaravelRoute>
     */
    private function probeableApiRoutes(): array
    {
        return array_values(array_filter(Route::getRoutes()->getRoutes(), function (LaravelRoute $route): bool {
            $uri = $route->uri();

            if (! str_starts_with($uri, 'api/')) {
                return false;
            }

            if (str_contains($uri, 'documentation/swagger.json')) {
                return false;
            }

            if (str_contains($uri, 'files/') || str_contains($uri, '/pdf') || str_contains($uri, '/csv')) {
                return false;
            }

            return ! in_array($this->primaryMethod($route), ['HEAD', 'OPTIONS'], true);
        }));
    }

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

        return 'GET';
    }

    private function actorFor(string $uri): User
    {
        if (str_contains($uri, '/parents') || str_contains($uri, '/parent') || str_contains($uri, 'confirm_authorized_user')) {
            return $this->parent;
        }

        if (str_contains($uri, '/teacher') || str_contains($uri, '/teachers') || str_contains($uri, 'class-progress')) {
            return $this->teacher;
        }

        if (str_contains($uri, '/students/dashboard')) {
            return $this->studentUser;
        }

        return $this->admin;
    }

    /**
     * @return list<array{0: User, 1: string, 2: string}>
     */
    private function portalBoundaryProbes(): array
    {
        return [
            [$this->parent, 'GET', '/api/v1/administrator/dashboard/metrics'],
            [$this->parent, 'GET', '/api/v1/administrator/enrollment-withdrawal'],
            [$this->teacher, 'GET', '/api/v1/parents/profile'],
            [$this->admin, 'PATCH', '/api/v1/parents/students/{studentId}'],
            [$this->parent, 'GET', '/api/v1/finance/financial-report'],
            [$this->parent, 'GET', '/api/v1/inventory/items'],
            [$this->teacher, 'POST', '/api/v1/school-years/{schoolYear}/close'],
            [$this->studentUser, 'GET', '/api/v1/teachers/classes'],
        ];
    }

    private function materializeUri(string $uri): string
    {
        return preg_replace_callback('/\{([^}:]+)(?:\?)?[^}]*\}/', function (array $matches): string {
            $key = $matches[1];
            return (string) ($this->ids[$key] ?? $this->ids['id']);
        }, $uri) ?? $uri;
    }

    private function materializePath(string $path): string
    {
        return '/' . ltrim($this->materializeUri(ltrim($path, '/')), '/');
    }

    /**
     * @return array<string, mixed>
     */
    private function payloadFor(string $method, string $uri): array
    {
        if ($method === 'GET' || $method === 'DELETE') {
            return [];
        }

        $base = [
            'school_year' => self::E2E_SCHOOL_YEAR,
            'semester' => self::E2E_SEMESTER,
            'date' => '2025-10-05',
            'notes' => 'E2E full-surface contract probe',
        ];

        if (str_contains($uri, 'auth/login') || preg_match('#api/(?:v1/)?login$#', $uri)) {
            return ['email' => $this->admin->email, 'password' => 'password'];
        }

        if (str_contains($uri, 'users')) {
            return $this->validUserPayload((int) $this->ids['roleId']);
        }

        if (str_contains($uri, 'students')) {
            return $base + [
                'firstname' => 'Contract',
                'lastname' => 'Student',
                'dob' => '2015-01-01',
                'gender' => 'Male',
                'parent_id' => $this->ids['parentId'],
                'student_id' => $this->ids['studentId'],
                'class_section_id' => [$this->ids['classSectionId']],
            ];
        }

        if (str_contains($uri, 'attendance')) {
            return $base + [
                'student_id' => $this->ids['studentId'],
                'class_section_id' => $this->ids['classSectionId'],
                'status' => 'present',
                'attendance' => [$this->ids['studentId'] => 'present'],
                'arrival_time' => '10:05',
            ];
        }

        if (str_contains($uri, 'scores') || str_contains($uri, 'grading')) {
            return $base + [
                'student_id' => $this->ids['studentId'],
                'class_section_id' => $this->ids['classSectionId'],
                'score' => 95,
                'points' => 10,
                'column' => 'Contract Probe',
                'comment' => 'Solid work.',
            ];
        }

        if (str_contains($uri, 'finance') || str_contains($uri, 'invoice') || str_contains($uri, 'payment') || str_contains($uri, 'refund')) {
            return $base + [
                'parent_id' => $this->ids['parentId'],
                'student_id' => $this->ids['studentId'],
                'invoice_id' => $this->ids['invoiceId'],
                'amount' => 25.00,
                'payment_method' => 'cash',
                'reason' => 'E2E contract probe',
            ];
        }

        if (str_contains($uri, 'inventory') || str_contains($uri, 'suppliers') || str_contains($uri, 'supply')) {
            return $base + [
                'name' => 'Contract Probe Item',
                'sku' => 'E2E-' . uniqid(),
                'quantity' => 3,
                'unit_cost' => 5.25,
            ];
        }

        if (str_contains($uri, 'messages') || str_contains($uri, 'support') || str_contains($uri, 'contact') || str_contains($uri, 'email')) {
            return $base + [
                'subject' => 'Contract Probe',
                'message' => 'This is an automated E2E contract probe.',
                'body' => 'This is an automated E2E contract probe.',
                'email' => 'contract.probe@example.test',
                'recipient_id' => $this->teacher->id,
            ];
        }

        if (str_contains($uri, 'preferences') || str_contains($uri, 'settings') || str_contains($uri, 'configuration')) {
            return $base + [
                'key' => 'contract_probe',
                'value' => 'enabled',
                'config_key' => 'contract_probe',
                'config_value' => 'enabled',
            ];
        }

        if (str_contains($uri, 'families') || str_contains($uri, 'authorized')) {
            return $base + [
                'parent_id' => $this->ids['parentId'],
                'student_id' => $this->ids['studentId'],
                'email' => 'guardian.contract@example.test',
                'firstname' => 'Guardian',
                'lastname' => 'Probe',
            ];
        }

        return $base + [
            'name' => 'Contract Probe',
            'title' => 'Contract Probe',
            'description' => 'E2E full-surface contract probe',
            'status' => 'active',
        ];
    }

    /**
     * @return list<int>
     */
    private function allowedContractStatuses(string $method): array
    {
        return match ($method) {
            'POST' => [200, 201, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422],
            'PUT', 'PATCH' => [200, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422],
            'DELETE' => [200, 202, 204, 302, 400, 401, 403, 404, 405, 409, 419, 422],
            default => [200, 204, 302, 304, 400, 401, 403, 404, 405, 409, 419, 422],
        };
    }
}
