<?php

declare(strict_types=1);

namespace App\Controllers;

use App\Core\DB;
use App\Modules\Reporting\Application\Services\DocumentPrintService;
use App\Shared\Support\QuotationSupport;
use App\Shared\Support\RemotePdfService;
use App\Services\PrintSettingsService;
use RuntimeException;

final class PublicQuotationPdfController
{
    public function __construct(private ?DocumentPrintService $service = null)
    {
        $this->service ??= new DocumentPrintService();
    }

    public function handle(): void
    {
        $pdo = DB::pdo();
        $id = (int)($_GET['id'] ?? 0);
        $token = (string)($_GET['token'] ?? '');
        if ($id <= 0 || $token === '' || !QuotationSupport::validatePdfToken($pdo, $id, $token)) {
            http_response_code(403);
            exit('Forbidden');
        }

        try {
            $result = $this->buildPdfForQuotation($pdo, $id);
        } catch (RuntimeException $e) {
            $code = (int)$e->getCode();
            http_response_code($code >= 400 && $code < 600 ? $code : 404);
            exit($e->getMessage());
        }

        if ($result['bytes'] === '') {
            $this->renderHtmlFallback($pdo, $id);
            return;
        }

        header('Content-Type: application/pdf');
        header('Content-Disposition: inline; filename="' . $result['filename'] . '"');
        header('Content-Length: ' . strlen($result['bytes']));
        echo $result['bytes'];
        exit;
    }

    public function buildPdfForQuotation(\PDO $pdo, int $id): array
    {
        $data = $this->service->quotation($pdo, $id);
        $html = $this->renderQuotationHtml($pdo, $data);
        $pdf = $this->generatePdfBytes($html);
        $filename = preg_replace('/[^A-Za-z0-9._-]/', '_', (string)($data['meta']['no'] ?? ('quotation_' . $id))) . '.pdf';

        return [
            'bytes' => $pdf,
            'filename' => $filename,
        ];
    }


    private function renderHtmlFallback(\PDO $pdo, int $id): void
    {
        $data = $this->service->quotation($pdo, $id);
        $quotation = $data['payload']['quotation'] ?? [];
        $validUntil = (string)($quotation['valid_until'] ?? '');
        $isExpired = $validUntil !== '' && strtotime(substr($validUntil, 0, 10)) < strtotime(date('Y-m-d'));
        $message = $isExpired
            ? 'هذا عرض السعر منتهي الصلاحية. يمكنكم الاطلاع عليه كمرجع فقط، ويرجى التواصل مع الشركة للحصول على عرض محدث.'
            : 'تعذر إنشاء ملف PDF على هذا السيرفر حاليًا، لذلك تم عرض نسخة HTML قابلة للقراءة والطباعة من عرض السعر.';

        $html = $this->renderQuotationHtml($pdo, $data);
        $bannerClass = $isExpired ? '#fff3cd;border:1px solid #f0ad4e;color:#7a4b00;' : '#fff3cd;border:1px solid #f0ad4e;color:#7a4b00;';
        $banner = '<div style="max-width:1100px;margin:16px auto 12px;padding:14px 18px;border-radius:12px;background:' . $bannerClass . '">' . e($message) . '</div>';
        $html = preg_replace('/<body>\s*<div class="page a4">/u', '<body>' . $banner . '<div class="page a4">', $html, 1) ?: $html;

        header('Content-Type: text/html; charset=UTF-8');
        echo $html;
        exit;
    }

    private function renderQuotationHtml(\PDO $pdo, array $data): string
    {
        $title = (string)($data['title'] ?? 'عرض سعر');
        $meta = is_array($data['meta'] ?? null) ? $data['meta'] : [];
        $company = is_array($data['company'] ?? null) ? $data['company'] : [];
        $footer = is_array($data['footer'] ?? null) ? $data['footer'] : [];
        $print_template = (string)($data['print_template'] ?? 'quotation_v2');
        $print_format = 'a4';

        $payload = array_merge([
            'title' => $title,
            'meta' => $meta,
            'company' => $company,
            'footer' => $footer,
            'print_template' => $print_template,
            'print_format' => $print_format,
        ], is_array($data['payload'] ?? null) ? $data['payload'] : []);

        $payload = (new PrintSettingsService($pdo))->preparePrintPayload('quotation', $payload);

        // Inline logo for public PDF generation because the renderer works from a temporary file.
        $payload['company'] = $this->inlineLogoForPdf(is_array($payload['company'] ?? null) ? $payload['company'] : []);

        extract($payload, EXTR_SKIP);

        ob_start();
        require APP_PATH . '/views/print/bodies/quotation.php';
        $body = (string)ob_get_clean();

        $css = $this->inlinePrintCss([
            APP_BASE_PATH . '/public/assets/print/print_a4.css',
            APP_BASE_PATH . '/public/assets/print/quotation_a4.css',
            APP_BASE_PATH . '/public/assets/print/purchase_invoice_a4.css',
        ]);

        ob_start();
        require APP_PATH . '/views/print/partials/header.php';
        $header = (string)ob_get_clean();

        ob_start();
        require APP_PATH . '/views/print/partials/footer.php';
        $footerHtml = (string)ob_get_clean();

        return '<!doctype html><html lang="ar" dir="rtl"><head><meta charset="utf-8"><title>' . e($title) . '</title>'
            . '<style>' . $css . '\n.print-toolbar,.no-print{display:none!important}body{font-family:DejaVu Sans, sans-serif;background:#fff!important}</style>'
            . '</head><body><div class="page-shell"><div class="page a4"><div class="page-content">'
            . $header
            . '<div class="print-body">' . $body . '</div>'
            . $footerHtml
            . '</div></div></div></body></html>';
    }

    private function inlinePrintCss(array $files): string
    {
        $css = '';
        foreach ($files as $file) {
            if (is_file($file)) {
                $css .= "\n" . (string)file_get_contents($file);
            }
        }
        return $css;
    }

    private function inlineLogoForPdf(array $company): array
    {
        $logoPath = trim((string)($company['logo_path'] ?? ''));
        if ($logoPath === '' || preg_match('~^data:~i', $logoPath)) {
            return $company;
        }

        $fullLogo = APP_BASE_PATH . '/public/' . ltrim($logoPath, '/');
        if (!is_file($fullLogo)) {
            return $company;
        }

        $ext = strtolower(pathinfo($fullLogo, PATHINFO_EXTENSION));
        $mime = match ($ext) {
            'png' => 'image/png',
            'jpg', 'jpeg' => 'image/jpeg',
            'webp' => 'image/webp',
            default => '',
        };
        if ($mime === '') {
            return $company;
        }

        $company['logo_path'] = 'data:' . $mime . ';base64,' . base64_encode((string)file_get_contents($fullLogo));
        return $company;
    }

    private function generatePdfBytes(string $html): string
    {
        $remoteBytes = (new RemotePdfService())->render($html, 'quotation.pdf');
        if ($remoteBytes !== null) {
            return $remoteBytes;
        }

        $tmpDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'quotation_pdf_' . bin2hex(random_bytes(8));
        if (!@mkdir($tmpDir, 0777, true) && !is_dir($tmpDir)) {
            return '';
        }

        $htmlFile = $tmpDir . DIRECTORY_SEPARATOR . 'quotation.html';
        $pdfFile = $tmpDir . DIRECTORY_SEPARATOR . 'quotation.pdf';
        $scriptFile = $tmpDir . DIRECTORY_SEPARATOR . 'render_pdf.py';
        file_put_contents($htmlFile, $html);

        $bytes = '';

        try {
            file_put_contents($scriptFile, <<<'PY'
from pathlib import Path
from weasyprint import HTML
import sys
html_path = Path(sys.argv[1])
pdf_path = Path(sys.argv[2])
HTML(filename=str(html_path), base_url=str(html_path.parent)).write_pdf(str(pdf_path))
PY);

            if ($this->runCommand($this->buildPythonWeasyCommand($scriptFile, $htmlFile, $pdfFile)) && is_file($pdfFile)) {
                $bytes = (string) file_get_contents($pdfFile);
            }

            if ($bytes === '') {
                @unlink($pdfFile);
                if ($this->runCommand($this->buildHeadlessBrowserCommand($htmlFile, $pdfFile)) && is_file($pdfFile)) {
                    $bytes = (string) file_get_contents($pdfFile);
                }
            }
        } finally {
            @unlink($htmlFile);
            @unlink($pdfFile);
            @unlink($scriptFile);
            @rmdir($tmpDir);
        }

        return $bytes;
    }

    private function buildPythonWeasyCommand(string $scriptFile, string $htmlFile, string $pdfFile): ?string
    {
        $python = $this->findExecutable(['python3', 'python']);
        if ($python === null) {
            return null;
        }

        return $this->quoteExecutable($python)
            . ' ' . escapeshellarg($scriptFile)
            . ' ' . escapeshellarg($htmlFile)
            . ' ' . escapeshellarg($pdfFile)
            . ' 2>&1';
    }

    private function buildHeadlessBrowserCommand(string $htmlFile, string $pdfFile): ?string
    {
        $browser = $this->findExecutable($this->headlessBrowserCandidates());
        if ($browser === null) {
            return null;
        }

        $htmlUrl = 'file:///' . str_replace(DIRECTORY_SEPARATOR, '/', ltrim($htmlFile, DIRECTORY_SEPARATOR));
        if (DIRECTORY_SEPARATOR === '\\') {
            $htmlUrl = 'file:///' . str_replace('\\', '/', $htmlFile);
        }

        return $this->quoteExecutable($browser)
            . ' --headless=new --disable-gpu --allow-file-access-from-files --print-to-pdf=' . escapeshellarg($pdfFile)
            . ' ' . escapeshellarg($htmlUrl)
            . ' 2>&1';
    }

    private function runCommand(?string $command): bool
    {
        if ($command === null || trim($command) === '') {
            return false;
        }

        $output = [];
        $code = 1;
        @exec($command, $output, $code);
        return $code === 0;
    }

    private function findExecutable(array $candidates): ?string
    {
        foreach ($candidates as $candidate) {
            if ($candidate === '') {
                continue;
            }

            if ($this->isAbsoluteExecutable($candidate)) {
                return $candidate;
            }

            $resolved = $this->resolveFromPath($candidate);
            if ($resolved !== null) {
                return $resolved;
            }
        }

        return null;
    }

    private function resolveFromPath(string $binary): ?string
    {
        $finder = strtoupper(substr(PHP_OS_FAMILY, 0, 3)) === 'WIN' ? 'where' : 'command -v';
        $cmd = strtoupper(substr(PHP_OS_FAMILY, 0, 3)) === 'WIN'
            ? 'where ' . escapeshellarg($binary) . ' 2>NUL'
            : 'command -v ' . escapeshellarg($binary) . ' 2>/dev/null';
        $result = @shell_exec($cmd);
        if (!is_string($result) || trim($result) == '') {
            return null;
        }

        $line = trim(strtok($result, "
"));
        return $line !== '' ? $line : null;
    }

    private function isAbsoluteExecutable(string $path): bool
    {
        if ($path === '') {
            return false;
        }

        if (is_file($path)) {
            return true;
        }

        return false;
    }

    private function quoteExecutable(string $path): string
    {
        return '"' . str_replace('"', '\"', $path) . '"';
    }

    private function headlessBrowserCandidates(): array
    {
        return [
            'chrome',
            'google-chrome',
            'chromium',
            'chromium-browser',
            'msedge',
            'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
            'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
            'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe',
            'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe',
        ];
    }
}
