<?php

declare(strict_types=1);

use Tests\Support\Api\ApiServer;

usesApiSuite();

it('rejects scanner LPR requests without an image before contacting Plate Recognizer', function (): void {
    api_test_covers('POST /modules/scanner/lpr', 'failure');

    $response = api_client()->post('/modules/scanner/lpr', []);

    $response->assertStatus(400)->assertSuccess(false)->assertMessage('Image is required.');
});

it('accepts multipart scanner images and forwards them to Plate Recognizer as a temp-file upload', function (): void {
    api_test_covers('POST /modules/scanner/lpr', 'happy');

    if (trim((string)getenv('API_TEST_BASE_URL')) !== '') {
        $this->markTestSkipped('This scanner LPR test requires the self-started API server so PLATE_RECOGNIZER_API_URL can be isolated.');
    }

    $fakePlateRecognizer = ScannerLprFakePlateRecognizerServer::start();
    $previousPlateRecognizerUrl = scanner_lpr_api_get_env('PLATE_RECOGNIZER_API_URL');
    scanner_lpr_api_set_env('PLATE_RECOGNIZER_API_URL', $fakePlateRecognizer->url());
    api_test_runtime()->restartServer();

    $imagePath = tempnam(sys_get_temp_dir(), 'scanner-lpr-api-');
    expect($imagePath)->not->toBeFalse();
    file_put_contents($imagePath, 'jpeg-camera-bytes');

    try {
        api_fixtures()->setModuleConfig('licenseplaterecognizer', 'enabled', 'true', 'bool');
        api_fixtures()->setModuleConfig('licenseplaterecognizer', 'api_key', 'scanner-api-test-key', 'string');
        api_test_runtime()->redis()?->del(['licenseplaterecognizer:runtime_config:v1']);

        $response = api_client()->postMultipart('/modules/scanner/lpr', [
            'client_capture_ms' => '12.345',
            'client_frame_width' => '300',
            'client_frame_height' => '225',
            'client_frame_bytes' => (string)strlen('jpeg-camera-bytes'),
        ], [
            'image' => [
                'path' => $imagePath,
                'mime' => 'image/jpeg',
                'name' => 'camera-frame.jpg',
            ],
        ]);

        $response->assertStatus(200)->assertSuccess();
        expect($response->data())->toMatchArray([
            'success' => true,
            'license_plate_number' => 'AB12345',
        ]);
        expect($response->headers['server-timing'] ?? '')
            ->toContain('lpr_client_capture')
            ->toContain('lpr_client_frame_width')
            ->toContain('lpr_client_frame_height')
            ->toContain('lpr_client_frame_bytes')
            ->toContain('lpr_local')
            ->toContain('lpr_payload')
            ->toContain('lpr_upstream_dns')
            ->toContain('lpr_upstream_connect')
            ->toContain('lpr_upstream')
            ->toContain('lpr_upstream_total')
            ->toContain('lpr_upstream_processing')
            ->toContain('lpr_total')
            ->toContain('lpr_route_total')
            ->toContain('lpr_request_total');

        $capture = $fakePlateRecognizer->capture();
        expect($capture['method'] ?? null)->toBe('POST');
        expect($capture['authorization'] ?? null)->toBe('Token scanner-api-test-key');
        expect($capture['expect'] ?? null)->toBeNull();
        expect(json_decode((string)($capture['post']['config'] ?? ''), true))->toBe([
            'mode' => 'fast',
            'plates_per_vehicle' => 1,
            'zoom_in_vehicles' => 0,
        ]);
        expect($capture['post']['regions'] ?? null)->toBe('dk,de,se,no');
        expect($capture['post']['client_capture_ms'] ?? null)->toBeNull();
        expect($capture['post']['client_frame_width'] ?? null)->toBeNull();
        expect($capture['post']['client_frame_height'] ?? null)->toBeNull();
        expect($capture['post']['client_frame_bytes'] ?? null)->toBeNull();
        expect($capture['files']['upload'] ?? [])->toMatchArray([
            'name' => 'license-plate.jpg',
            'type' => 'image/jpeg',
            'size' => strlen('jpeg-camera-bytes'),
            'error' => UPLOAD_ERR_OK,
            'contents' => base64_encode('jpeg-camera-bytes'),
        ]);
    } finally {
        @unlink($imagePath);
        $fakePlateRecognizer->stop();
        scanner_lpr_api_set_env('PLATE_RECOGNIZER_API_URL', $previousPlateRecognizerUrl);
        api_test_runtime()->restartServer();
    }
});

final class ScannerLprFakePlateRecognizerServer
{
    private mixed $process = null;

    private function __construct(
        private readonly string $directory,
        private readonly string $capturePath,
        private readonly int $port,
    ) {
    }

    public static function start(): self
    {
        $directory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'scanner-lpr-fake-' . bin2hex(random_bytes(6));
        if (!mkdir($directory, 0777, true) && !is_dir($directory)) {
            throw new RuntimeException('Unable to create fake Plate Recognizer directory.');
        }

        $capturePath = $directory . DIRECTORY_SEPARATOR . 'capture.json';
        $routerPath = $directory . DIRECTORY_SEPARATOR . 'router.php';
        file_put_contents($routerPath, <<<'PHP'
<?php

$path = parse_url((string)($_SERVER['REQUEST_URI'] ?? '/'), PHP_URL_PATH) ?: '/';
if ($path === '/health') {
    header('Content-Type: text/plain');
    echo 'ok';
    return;
}

if ($path !== '/v1/plate-reader/') {
    http_response_code(404);
    header('Content-Type: application/json');
    echo json_encode(['error' => 'not found']);
    return;
}

$upload = $_FILES['upload'] ?? [];
$tmpName = is_array($upload) ? (string)($upload['tmp_name'] ?? '') : '';
$capture = [
    'method' => $_SERVER['REQUEST_METHOD'] ?? null,
    'authorization' => $_SERVER['HTTP_AUTHORIZATION'] ?? null,
    'expect' => $_SERVER['HTTP_EXPECT'] ?? null,
    'post' => $_POST,
    'files' => [
        'upload' => [
            'name' => is_array($upload) ? ($upload['name'] ?? null) : null,
            'type' => is_array($upload) ? ($upload['type'] ?? null) : null,
            'size' => is_array($upload) ? ($upload['size'] ?? null) : null,
            'error' => is_array($upload) ? ($upload['error'] ?? null) : null,
            'contents' => $tmpName !== '' && is_file($tmpName) ? base64_encode((string)file_get_contents($tmpName)) : null,
        ],
    ],
];

file_put_contents((string)getenv('SCANNER_LPR_FAKE_CAPTURE_PATH'), json_encode($capture, JSON_UNESCAPED_SLASHES));
header('Content-Type: application/json');
echo json_encode([
    'processing_time' => 58.184,
    'results' => [['plate' => 'ab12345', 'score' => 0.98]],
], JSON_UNESCAPED_SLASHES);
PHP);

        $port = ApiServer::findAvailablePort('127.0.0.1');
        $server = new self($directory, $capturePath, $port);
        $server->startProcess($routerPath);
        $server->waitUntilReady();

        return $server;
    }

    public function url(): string
    {
        return sprintf('http://127.0.0.1:%d', $this->port);
    }

    public function capture(): array
    {
        $contents = is_file($this->capturePath) ? file_get_contents($this->capturePath) : false;
        expect($contents)->not->toBeFalse();

        $decoded = json_decode((string)$contents, true);
        expect($decoded)->toBeArray();

        return $decoded;
    }

    public function stop(): void
    {
        if (is_resource($this->process)) {
            proc_terminate($this->process);
            usleep(250000);
            $status = proc_get_status($this->process);
            if (($status['running'] ?? false) && function_exists('posix_kill')) {
                @posix_kill((int)$status['pid'], 9);
            }
            proc_close($this->process);
            $this->process = null;
        }

        $this->removeDirectory($this->directory);
    }

    private function startProcess(string $routerPath): void
    {
        $command = sprintf(
            '%s -S %s %s',
            escapeshellarg((string)(PHP_BINARY ?: 'php')),
            escapeshellarg('127.0.0.1:' . $this->port),
            escapeshellarg($routerPath),
        );
        $environment = array_merge(getenv() ?: [], [
            'SCANNER_LPR_FAKE_CAPTURE_PATH' => $this->capturePath,
        ]);
        $descriptorSpec = [
            0 => ['pipe', 'r'],
            1 => ['file', $this->directory . DIRECTORY_SEPARATOR . 'stdout.log', 'a'],
            2 => ['file', $this->directory . DIRECTORY_SEPARATOR . 'stderr.log', 'a'],
        ];

        $this->process = proc_open($command, $descriptorSpec, $pipes, $this->directory, $environment);
        if (!is_resource($this->process)) {
            throw new RuntimeException('Unable to start fake Plate Recognizer server.');
        }

        if (isset($pipes[0]) && is_resource($pipes[0])) {
            fclose($pipes[0]);
        }
    }

    private function waitUntilReady(): void
    {
        $deadline = microtime(true) + 5;
        $lastError = 'Timed out waiting for fake Plate Recognizer.';

        while (microtime(true) < $deadline) {
            $curl = curl_init($this->url() . '/health');
            if ($curl === false) {
                throw new RuntimeException('Unable to initialize fake Plate Recognizer health check.');
            }

            curl_setopt_array($curl, [
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_CONNECTTIMEOUT_MS => 250,
                CURLOPT_TIMEOUT_MS => 500,
            ]);
            $body = curl_exec($curl);
            $status = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
            if ($body === false) {
                $lastError = curl_error($curl);
            }
            curl_close($curl);

            if ($status === 200 && $body === 'ok') {
                return;
            }

            usleep(100000);
        }

        throw new RuntimeException($lastError);
    }

    private function removeDirectory(string $directory): void
    {
        if (!is_dir($directory)) {
            return;
        }

        foreach (glob($directory . DIRECTORY_SEPARATOR . '*') ?: [] as $path) {
            if (is_file($path)) {
                @unlink($path);
            }
        }

        @rmdir($directory);
    }
}

function scanner_lpr_api_get_env(string $key): ?string
{
    $value = getenv($key);

    return $value === false ? null : (string)$value;
}

function scanner_lpr_api_set_env(string $key, ?string $value): void
{
    if ($value === null) {
        putenv($key);
        unset($_ENV[$key], $_SERVER[$key]);

        return;
    }

    putenv($key . '=' . $value);
    $_ENV[$key] = $value;
    $_SERVER[$key] = $value;
}
