<?php

namespace Tests\Feature\Api\V1\FullSurface\Support;

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 Illuminate\Testing\TestResponse;
use Tests\Concerns\AssertsE2EApiResponses;
use Tests\Concerns\SeedsE2ETestFixtures;
use Tests\TestCase;

abstract class FullSurfaceE2EContractCase extends TestCase
{
    use RefreshDatabase;
    use SeedsE2ETestFixtures;
    use AssertsE2EApiResponses;

    protected User $admin;
    protected User $teacher;
    protected User $parent;
    protected User $studentUser;

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

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

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

        $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,
            'user' => $this->admin->id,
            'userId' => $this->admin->id,
            'role' => (int) DB::table('roles')->where('name', 'parent')->value('id'),
            'roleId' => (int) DB::table('roles')->where('name', 'parent')->value('id'),
            'permission' => 1,
            '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,
            'voucherId' => $voucherId,
            'schoolYear' => (int) DB::table('school_years')->where('name', self::E2E_SCHOOL_YEAR)->value('id'),
            'schoolYearId' => (int) DB::table('school_years')->where('name', self::E2E_SCHOOL_YEAR)->value('id'),
            'promotion' => 1,
            'promotionId' => 1,
            'batch' => 1,
            'batchId' => 1,
            'contact' => 1,
            'contactId' => 1,
            'family' => 1,
            'familyId' => 1,
            'authorizedUser' => 1,
            'authorizedUserId' => 1,
            'guardian' => 1,
            'guardianId' => 1,
            'eventCharge' => 1,
            'eventChargeId' => 1,
            'plan' => 1,
            'planId' => 1,
            'carryforward' => 1,
            'carryforwardId' => 1,
            'installment' => 1,
            'installmentId' => 1,
            'category' => 1,
            'categoryId' => 1,
            'item' => 1,
            'itemId' => 1,
            'supplier' => 1,
            'supplierId' => 1,
            'request' => 1,
            'requestId' => 1,
            'message' => 1,
            'messageId' => 1,
            'ticket' => 1,
            'ticketId' => 1,
            'certificateNumber' => 'CERT-E2E-001',
            'token' => 'invalid-token-for-contract-probe',
            'filename' => 'placeholder.txt',
            'name' => 'placeholder.txt',
            'mode' => 'view',
        ];
    }

    /** @return list<LaravelRoute> */
    protected function apiRoutes(?string $contains = null): array
    {
        return array_values(array_filter(Route::getRoutes()->getRoutes(), function (LaravelRoute $route) use ($contains): bool {
            $uri = $route->uri();

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

            if ($contains !== null && ! str_contains($uri, $contains)) {
                return false;
            }

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

    /** @param list<string> $needles @return list<LaravelRoute> */
    protected function apiRoutesContainingAny(array $needles): array
    {
        return array_values(array_filter($this->apiRoutes(), function (LaravelRoute $route) use ($needles): bool {
            foreach ($needles as $needle) {
                if (str_contains($route->uri(), $needle)) {
                    return true;
                }
            }

            return false;
        }));
    }

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

        return 'GET';
    }

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

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

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

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

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

        return $this->admin;
    }

    protected function requestAs(User $actor, string $method, string $uri, array $payload = []): TestResponse
    {
        $this->actingAs($actor, 'api');

        return $this->json($method, $this->materializePath($uri), $payload);
    }

    protected function probeRoute(LaravelRoute $route, ?array $payload = null): TestResponse
    {
        $method = $this->primaryMethod($route);
        $uri = $route->uri();

        return $this->requestAs($this->actorFor($uri), $method, $uri, $payload ?? $this->payloadFor($method, $uri));
    }

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

        $base = [
            'school_year' => self::E2E_SCHOOL_YEAR,
            'semester' => self::E2E_SEMESTER,
            'date' => '2025-10-05',
            'notes' => 'E2E deep 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' => 'Deep',
                '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') || str_contains($uri, 'homework') || str_contains($uri, 'quiz')) {
            return $base + [
                'student_id' => $this->ids['studentId'],
                'class_section_id' => $this->ids['classSectionId'],
                'score' => 95,
                'points' => 10,
                'column' => 'Deep 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 deep contract probe',
            ];
        }

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

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

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

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

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

    /** @return list<int> */
    protected function controlledStatuses(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],
        };
    }

    protected function assertControlled(TestResponse $response, string $method, string $uri): void
    {
        $this->assertStatusIn($response, $this->controlledStatuses($method), "$method $uri");
    }

    protected function assertJsonResponseWhenSuccessful(TestResponse $response, string $context): void
    {
        if ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300 && $response->getContent() !== '') {
            $this->assertTrue(
                str_contains((string) $response->headers->get('Content-Type'), 'json') || $this->isJsonString($response->getContent()),
                $context . ' should return JSON for successful API responses. Body: ' . $response->getContent()
            );
        }
    }

    protected function isJsonString(string $content): bool
    {
        json_decode($content, true);

        return json_last_error() === JSON_ERROR_NONE;
    }
}
