<?php

declare(strict_types=1);

namespace App\Shared\Support;

use App\Core\Env;

final class RemotePdfService
{
    public function render(string $html, string $filename = 'document.pdf'): ?string
    {
        $url = trim((string) Env::get('PDF_SERVICE_URL', ''));
        $token = trim((string) Env::get('PDF_SERVICE_TOKEN', ''));
        if ($url === '' || $token === '') {
            return null;
        }

        $payload = json_encode([
            'html' => $this->prepareHtml($html),
            'base_url' => $this->baseUrl(),
            'filename' => $filename,
        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($payload) || $payload === '') {
            return null;
        }

        $timeout = max(10, (int) Env::get('PDF_SERVICE_TIMEOUT', '60'));

        if (function_exists('curl_init')) {
            $ch = curl_init($url);
            if ($ch === false) {
                return null;
            }
            curl_setopt_array($ch, [
                CURLOPT_POST => true,
                CURLOPT_HTTPHEADER => [
                    'Content-Type: application/json; charset=UTF-8',
                    'Accept: application/pdf',
                    'X-PDF-Token: ' . $token,
                ],
                CURLOPT_POSTFIELDS => $payload,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_CONNECTTIMEOUT => 10,
                CURLOPT_TIMEOUT => $timeout,
                CURLOPT_FOLLOWLOCATION => true,
                CURLOPT_MAXREDIRS => 3,
            ]);
            $body = curl_exec($ch);
            $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
            $type = (string) curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
            curl_close($ch);

            if ($status >= 200 && $status < 300 && is_string($body) && $body !== '' && (str_contains($type, 'application/pdf') || str_starts_with($body, '%PDF'))) {
                return $body;
            }
            return null;
        }

        $context = stream_context_create([
            'http' => [
                'method' => 'POST',
                'header' => "Content-Type: application/json; charset=UTF-8\r\nAccept: application/pdf\r\nX-PDF-Token: {$token}\r\n",
                'content' => $payload,
                'timeout' => $timeout,
                'ignore_errors' => true,
            ],
        ]);
        $body = @file_get_contents($url, false, $context);
        $statusOk = false;
        foreach (($http_response_header ?? []) as $line) {
            if (preg_match('~^HTTP/\S+\s+2\d\d~', (string) $line)) {
                $statusOk = true;
                break;
            }
        }

        return $statusOk && is_string($body) && $body !== '' && str_starts_with($body, '%PDF') ? $body : null;
    }

    private function prepareHtml(string $html): string
    {
        $baseUrl = $this->baseUrl();
        if ($baseUrl === '') {
            return $html;
        }

        // Let WeasyPrint resolve relative links even if the HTML does not have a base tag.
        if (stripos($html, '<base ') === false) {
            $html = preg_replace('~<head([^>]*)>~i', '<head$1><base href="' . htmlspecialchars($baseUrl, ENT_QUOTES, 'UTF-8') . '/">', $html, 1) ?: $html;
        }

        return (string) preg_replace_callback('~(src|href)=("|\')([^"\']+)(\2)~i', function (array $m) use ($baseUrl): string {
            $url = html_entity_decode((string) $m[3], ENT_QUOTES | ENT_HTML5, 'UTF-8');
            if ($url === '' || preg_match('~^(data:|https?://|file:|mailto:|tel:)~i', $url)) {
                return $m[0];
            }
            $absolute = str_starts_with($url, '/') ? ($baseUrl . $url) : ($baseUrl . '/' . ltrim($url, '/'));
            return $m[1] . '=' . $m[2] . htmlspecialchars($absolute, ENT_QUOTES, 'UTF-8') . $m[2];
        }, $html);
    }

    private function baseUrl(): string
    {
        $configured = trim((string) Env::get('APP_URL', ''));
        if ($configured !== '') {
            return rtrim($configured, '/');
        }

        $https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
            || (($_SERVER['SERVER_PORT'] ?? null) === '443')
            || (strtolower((string) ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https');
        $scheme = $https ? 'https' : 'http';
        $host = (string) ($_SERVER['HTTP_HOST'] ?? '');
        if ($host === '') {
            return '';
        }

        return $scheme . '://' . $host . rtrim(Path::baseUri(), '/');
    }
}
