<?php
/** Server-side PDF renderer for immutable WooCommerce fiscal documents. */
if (!defined('ABSPATH')) {
    exit;
}

final class DdvWooPdfService
{
    public function streamInvoice(WC_Order $order, DdvWooFiscalDocument $doc)
    {
        $pdf = $this->invoiceBytes($order, $doc);
        $this->stream($pdf['content'], $pdf['filename']);
    }

    public function streamCredit(WC_Order $order, WC_Order_Refund $refund, DdvWooFiscalDocument $doc)
    {
        $pdf = $this->creditBytes($order, $refund, $doc);
        $this->stream($pdf['content'], $pdf['filename']);
    }

    public function invoiceBytes(WC_Order $order, DdvWooFiscalDocument $doc)
    {
        $doc = VerifactuDocumentIdentity::getInvoice($order, $doc);
        $record = (new VerifactuRepository())->getLatestAltaByInvoice((int) $doc->id);
        if (!$record) {
            throw new Exception('No se puede generar el PDF fiscal con QR antes de generar el registro VeriFactu de alta.');
        }
        $qr = VerifactuQrService::generateInvoiceForRecord($order, $doc, $record);
        return $this->render($order, $doc, $record, $qr, null);
    }

    public function creditBytes(WC_Order $order, WC_Order_Refund $refund, DdvWooFiscalDocument $doc)
    {
        if ((int) $refund->get_parent_id() !== (int) $order->get_id()) {
            throw new Exception('El reembolso no pertenece al pedido solicitado.');
        }
        $fiscalCredit = VerifactuDocumentIdentity::getCreditDocument((int) $refund->get_id());
        if ((int) $fiscalCredit->id !== (int) $doc->id) {
            throw new Exception('El documento rectificativo no coincide con el reembolso solicitado.');
        }
        $record = (new VerifactuRepository())->getLatestAltaBySlip((int) $refund->get_id());
        if (!$record) {
            throw new Exception('No se puede generar el PDF rectificativo con QR antes de generar el registro VeriFactu de alta.');
        }
        $qr = VerifactuQrService::generateCreditSlipForRecord($order, $refund, $record);
        return $this->render($order, $doc, $record, $qr, $refund);
    }

    private function render(WC_Order $order, DdvWooFiscalDocument $doc, array $record, array $qr, $refund)
    {
        $this->loadDompdf();
        $this->assertQr($qr);

        $isCredit = $refund instanceof WC_Order_Refund || $doc->document_type === DdvWooDocumentRepository::TYPE_CREDIT;
        $snapshot = is_array($doc->snapshot) ? $doc->snapshot : array();
        $billing = isset($snapshot['billing_address']) && is_array($snapshot['billing_address']) ? $snapshot['billing_address'] : array();
        $recipient = isset($snapshot['recipient']) && is_array($snapshot['recipient']) ? $snapshot['recipient'] : array();
        $issuerAddress = isset($snapshot['issuer_address']) && is_array($snapshot['issuer_address'])
            ? $snapshot['issuer_address']
            : $this->currentIssuerAddress();
        $issuerName = $this->issuerNameFromRecord($record);
        $issuerNif = trim((string) ($record['issuer_nif'] ?? ''));
        if ($issuerNif === '') {
            throw new Exception('El registro VeriFactu no conserva el NIF del emisor necesario para el PDF.');
        }

        $qrRaw = file_get_contents((string) $qr['path']);
        if (!is_string($qrRaw) || $qrRaw === '') {
            throw new Exception('No se ha podido leer el QR VeriFactu local para incorporarlo al PDF.');
        }
        $qrData = 'data:image/png;base64,' . base64_encode($qrRaw);
        $qrSize = (float) $qr['size_mm'];
        if ($qrSize < 30.0 || $qrSize > 40.0) {
            throw new Exception('El QR del PDF debe medir entre 30 y 40 mm.');
        }

        $currency = !empty($doc->currency) ? strtoupper((string) $doc->currency) : 'EUR';
        $sign = $isCredit ? -1.0 : 1.0;
        $title = $isCredit ? 'FACTURA RECTIFICATIVA' : 'FACTURA';
        $sourceNumber = '';
        if ($isCredit && (int) $doc->source_invoice_id > 0) {
            try {
                $source = VerifactuDocumentIdentity::getInvoiceById((int) $doc->source_invoice_id);
                $sourceNumber = (string) $source->number;
            } catch (Throwable $ignored) {
                $sourceNumber = '';
            }
        }

        $logo = $this->localLogoDataUri();
        $cssPath = dirname(__DIR__) . '/assets/invoice-pdf.css';
        $css = is_file($cssPath) ? file_get_contents($cssPath) : false;
        if (!is_string($css) || $css === '') {
            throw new Exception('No se encuentra la plantilla CSS local de la factura PDF.');
        }

        $html = '<!doctype html><html><head><meta charset="UTF-8"><style>' . $css . '</style></head><body>';

        // AEAT: QR at the beginning of the visible invoice, once and before invoice content.
        $html .= '<div class="aeat-qr-block">'
            . '<div class="aeat-qr-image">'
            . '<div class="aeat-qr-label">' . $this->e((string) ($qr['label_top'] ?? 'QR tributario:')) . '</div>'
            . '<img alt="QR tributario VeriFactu" src="' . $this->e($qrData) . '" style="width:' . $this->decimalCss($qrSize) . 'mm;height:' . $this->decimalCss($qrSize) . 'mm">'
            . '</div>'
            . '<div class="aeat-qr-copy">'
            . '<div class="aeat-verifactu">' . $this->e((string) ($qr['label_verifactu'] ?? 'VERI*FACTU')) . '</div>'
            . '<div class="aeat-verify-label">' . $this->e((string) ($qr['label_bottom'] ?? 'Factura verificable en la sede electronica de la AEAT')) . '</div>'
            . '</div><div class="clear-both"></div></div>';

        $html .= '<div class="header"><div class="company-data">';
        if ($logo !== '') {
            $html .= '<img class="logo" alt="Logotipo" src="' . $this->e($logo) . '">';
        }
        $html .= '<div class="company-name">' . $this->e($issuerName) . '</div>'
            . '<div class="company-details">NIF: ' . $this->e($issuerNif) . $this->issuerAddressHtml($issuerAddress) . '</div>'
            . '</div>'
            . '<div class="invoice-data"><h1>' . $this->e($title) . '</h1>'
            . '<div class="document-number">' . $this->e($doc->number) . '</div>'
            . '<div><strong>Fecha de expedicion:</strong> ' . $this->e($doc->issue_date) . '</div>'
            . '<div><strong>Pedido WooCommerce:</strong> #' . $this->e((string) ($snapshot['order_number'] ?? $order->get_order_number())) . '</div>';
        if ($sourceNumber !== '') {
            $html .= '<div><strong>Rectifica:</strong> ' . $this->e($sourceNumber) . '</div>';
        }
        $html .= '</div><div class="clear-both"></div></div>';

        $html .= '<div class="addresses"><div class="address-box"><h3>EMISOR</h3><strong>' . $this->e($issuerName) . '</strong><br>NIF: ' . $this->e($issuerNif)
            . $this->issuerAddressHtml($issuerAddress) . '</div>'
            . '<div class="address-box right"><h3>DESTINATARIO</h3><strong>' . $this->e((string) ($recipient['name'] ?? '')) . '</strong>';
        if (!empty($recipient['tax_id'])) {
            $html .= '<br>NIF/DNI/VAT: ' . $this->e((string) $recipient['tax_id']);
        }
        $html .= $this->billingAddressHtml($billing) . '</div><div class="clear-both"></div></div>';

        $html .= '<table class="items-table"><thead><tr><th>Concepto</th><th class="center">Cant.</th><th class="num">P. unitario base</th><th class="num">Base</th><th class="num">Impuestos</th><th class="num">Total</th></tr></thead><tbody>';
        foreach ((array) ($snapshot['items'] ?? array()) as $item) {
            $qty = (float) ($item['quantity'] ?? 1);
            if (abs($qty) < 0.000001) {
                $qty = 1.0;
            }
            $base = (float) ($item['base'] ?? 0);
            $tax = (float) ($item['tax'] ?? 0);
            $total = (float) ($item['total'] ?? ($base + $tax));
            $unit = $base / $qty;
            $html .= '<tr><td>' . $this->e((string) ($item['name'] ?? 'Concepto')) . '</td>'
                . '<td class="center">' . $this->e($this->formatQuantity($qty)) . '</td>'
                . '<td class="num">' . $this->e($this->amount($sign * $unit, $currency)) . '</td>'
                . '<td class="num">' . $this->e($this->amount($sign * $base, $currency)) . '</td>'
                . '<td class="num">' . $this->e($this->amount($sign * $tax, $currency)) . '</td>'
                . '<td class="num">' . $this->e($this->amount($sign * $total, $currency)) . '</td></tr>';
        }
        $html .= '</tbody></table>';

        $taxRows = $this->aggregateTaxRows((array) ($snapshot['tax_rows'] ?? array()), $sign);
        $html .= '<div class="tax-summary"><h3>Desglose de impuestos</h3><table class="tax-table"><thead><tr><th>Tipo</th><th class="num">Base</th><th class="num">Cuota</th></tr></thead><tbody>';
        if (!$taxRows) {
            $html .= '<tr><td colspan="3">Sin desglose de impuestos disponible en la instantanea fiscal.</td></tr>';
        } else {
            foreach ($taxRows as $row) {
                $html .= '<tr><td>' . $this->e($this->formatRate($row['rate'])) . '</td>'
                    . '<td class="num">' . $this->e($this->amount($row['base'], $currency)) . '</td>'
                    . '<td class="num">' . $this->e($this->amount($row['tax'], $currency)) . '</td></tr>';
            }
        }
        $html .= '</tbody></table></div>';

        $baseTotal = $sign * (float) ($snapshot['total_excl'] ?? $doc->total_paid_tax_excl);
        $taxTotal = $sign * (float) ($snapshot['total_tax'] ?? ((float) $doc->total_paid_tax_incl - (float) $doc->total_paid_tax_excl));
        $grandTotal = $sign * (float) ($snapshot['total_incl'] ?? $doc->total_paid_tax_incl);
        $html .= '<div class="totals-section"><table class="totals-table">'
            . '<tr><td class="label">Base imponible</td><td class="num">' . $this->e($this->amount($baseTotal, $currency)) . '</td></tr>'
            . '<tr><td class="label">Impuestos</td><td class="num">' . $this->e($this->amount($taxTotal, $currency)) . '</td></tr>'
            . '<tr class="total-row"><td class="label">TOTAL FACTURA</td><td class="num">' . $this->e($this->amount($grandTotal, $currency)) . '</td></tr>'
            . '</table></div><div class="clear-both"></div>';

        $notes = array();
        if ($sourceNumber !== '') {
            $notes[] = '<strong>Factura rectificada:</strong> ' . $this->e($sourceNumber);
        }
        if (!empty($snapshot['refund_reason'])) {
            $notes[] = '<strong>Motivo de rectificacion:</strong> ' . $this->e((string) $snapshot['refund_reason']);
        }
        if (!empty($snapshot['fiscal_note'])) {
            $notes[] = '<strong>Nota fiscal:</strong> ' . $this->e((string) $snapshot['fiscal_note']);
        }
        if ($notes) {
            $html .= '<div class="notes"><p>' . implode('</p><p>', $notes) . '</p></div>';
        }

        $html .= '<div class="footer-note">Documento fiscal generado por digitalDot VeriFactu para WooCommerce a partir de la instantanea economica inmutable del documento. '
            . 'QR asociado al registro VeriFactu de alta #' . $this->e((string) ($record['id_record'] ?? '')) . ' (' . $this->e((string) ($record['environment'] ?? '')) . ').</div>';
        $html .= '</body></html>';

        $options = new \Dompdf\Options();
        $options->set('isRemoteEnabled', false);
        $options->set('isHtml5ParserEnabled', true);
        $options->set('isPhpEnabled', false);
        $options->set('isJavascriptEnabled', false);
        $options->set('defaultFont', 'DejaVu Sans');
        $options->set('chroot', array(dirname(__DIR__)));

        $dompdf = new \Dompdf\Dompdf($options);
        $dompdf->loadHtml($html, 'UTF-8');
        $dompdf->setPaper('A4', 'portrait');
        $dompdf->render();
        $content = $dompdf->output();
        if (!is_string($content) || substr($content, 0, 4) !== '%PDF') {
            throw new Exception('Dompdf no ha generado un PDF valido.');
        }

        $prefix = $isCredit ? 'rectificativa-' : 'factura-';
        $safeNumber = function_exists('sanitize_file_name')
            ? sanitize_file_name((string) $doc->number)
            : preg_replace('/[^A-Za-z0-9._-]+/', '_', (string) $doc->number);
        if ($safeNumber === '') {
            $safeNumber = (string) $doc->id;
        }

        return array(
            'content' => $content,
            'filename' => $prefix . $safeNumber . '.pdf',
        );
    }

    private function loadDompdf()
    {
        if (!extension_loaded('mbstring') || !extension_loaded('dom')) {
            throw new Exception('Dompdf requiere las extensiones PHP mbstring y DOM para generar facturas PDF.');
        }
        if (class_exists('Dompdf\\Dompdf') && class_exists('Dompdf\\Options')) {
            return;
        }
        $autoload = dirname(__DIR__) . '/vendor-pdf/autoload.php';
        if (!is_file($autoload)) {
            throw new Exception('No se encuentra la libreria Dompdf incluida con el modulo de facturas PDF.');
        }
        require_once $autoload;
        if (!class_exists('Dompdf\\Dompdf') || !class_exists('Dompdf\\Options')) {
            throw new Exception('La libreria Dompdf no se ha podido cargar correctamente.');
        }
    }

    private function assertQr(array $qr)
    {
        if (empty($qr['path']) || !is_file((string) $qr['path'])) {
            throw new Exception('No existe un QR VeriFactu local valido para el documento. No se generara un PDF fiscal sin QR.');
        }
        if (empty($qr['url'])) {
            throw new Exception('El QR VeriFactu no contiene la URL tributaria de cotejo.');
        }
    }

    private function stream($content, $filename)
    {
        if (!is_string($content) || $content === '') {
            throw new Exception('El PDF esta vacio.');
        }
        if (headers_sent()) {
            throw new Exception('No se puede descargar el PDF porque ya se han enviado cabeceras HTTP.');
        }
        while (ob_get_level() > 0) {
            @ob_end_clean();
        }
        nocache_headers();
        header('Content-Type: application/pdf');
        header('Content-Disposition: attachment; filename="' . str_replace(array('"', "\r", "\n"), '', (string) $filename) . '"');
        header('Content-Length: ' . strlen($content));
        header('X-Content-Type-Options: nosniff');
        header('Referrer-Policy: no-referrer');
        header('X-Robots-Tag: noindex, nofollow, noarchive', true);
        echo $content;
        exit;
    }

    private function issuerNameFromRecord(array $record)
    {
        $xml = isset($record['request_xml']) ? (string) $record['request_xml'] : '';
        if ($xml !== '' && class_exists('DOMDocument')) {
            $old = libxml_use_internal_errors(true);
            $dom = new DOMDocument();
            if (@$dom->loadXML($xml, LIBXML_NONET | LIBXML_NOBLANKS)) {
                $xpath = new DOMXPath($dom);
                $nodes = $xpath->query('//*[local-name()="NombreRazonEmisor"]');
                if ($nodes && $nodes->length > 0) {
                    $value = trim((string) $nodes->item(0)->textContent);
                    libxml_clear_errors();
                    libxml_use_internal_errors($old);
                    if ($value !== '') {
                        return $value;
                    }
                }
            }
            libxml_clear_errors();
            libxml_use_internal_errors($old);
        }
        try {
            return VerifactuConfig::getIssuerName();
        } catch (Throwable $ignored) {
            return (string) get_bloginfo('name');
        }
    }

    private function currentIssuerAddress()
    {
        $address = array();
        if (function_exists('WC') && WC() && isset(WC()->countries) && WC()->countries) {
            $address = array(
                'address_1' => (string) WC()->countries->get_base_address(),
                'address_2' => method_exists(WC()->countries, 'get_base_address_2') ? (string) WC()->countries->get_base_address_2() : '',
                'postcode' => (string) WC()->countries->get_base_postcode(),
                'city' => (string) WC()->countries->get_base_city(),
                'state' => (string) WC()->countries->get_base_state(),
                'country' => (string) WC()->countries->get_base_country(),
            );
        }
        $address['email'] = (string) get_option('admin_email', '');
        return $address;
    }

    private function issuerAddressHtml(array $address)
    {
        $lines = array();
        $street = trim((string) ($address['address_1'] ?? '') . ' ' . (string) ($address['address_2'] ?? ''));
        if ($street !== '') {
            $lines[] = $street;
        }
        $city = trim((string) ($address['postcode'] ?? '') . ' ' . (string) ($address['city'] ?? ''));
        if ($city !== '') {
            $lines[] = $city;
        }
        $region = trim((string) ($address['state'] ?? '') . ' ' . (string) ($address['country'] ?? ''));
        if ($region !== '') {
            $lines[] = $region;
        }
        if (!empty($address['email'])) {
            $lines[] = (string) $address['email'];
        }
        return $lines ? '<br>' . implode('<br>', array_map(array($this, 'e'), $lines)) : '';
    }

    private function billingAddressHtml(array $billing)
    {
        $lines = array();
        $street = trim((string) ($billing['address_1'] ?? '') . ' ' . (string) ($billing['address_2'] ?? ''));
        if ($street !== '') {
            $lines[] = $street;
        }
        $city = trim((string) ($billing['postcode'] ?? '') . ' ' . (string) ($billing['city'] ?? ''));
        if ($city !== '') {
            $lines[] = $city;
        }
        $region = trim((string) ($billing['state'] ?? '') . ' ' . (string) ($billing['country'] ?? ''));
        if ($region !== '') {
            $lines[] = $region;
        }
        if (!empty($billing['email'])) {
            $lines[] = (string) $billing['email'];
        }
        return $lines ? '<br>' . implode('<br>', array_map(array($this, 'e'), $lines)) : '';
    }

    private function localLogoDataUri()
    {
        $logoId = function_exists('get_theme_mod') ? (int) get_theme_mod('custom_logo') : 0;
        if (!$logoId && function_exists('get_option')) {
            $logoId = (int) get_option('site_icon', 0);
        }
        if (!$logoId || !function_exists('get_attached_file')) {
            return '';
        }
        $path = get_attached_file($logoId);
        if (!$path || !is_file($path) || !is_readable($path)) {
            return '';
        }
        $raw = file_get_contents($path);
        if (!is_string($raw) || $raw === '') {
            return '';
        }
        $mime = 'image/png';
        if (function_exists('getimagesize')) {
            $info = @getimagesize($path);
            if (is_array($info) && !empty($info['mime']) && strpos((string) $info['mime'], 'image/') === 0) {
                $mime = (string) $info['mime'];
            }
        }
        return 'data:' . $mime . ';base64,' . base64_encode($raw);
    }

    private function aggregateTaxRows(array $rows, $sign)
    {
        $aggregated = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $rate = round((float) ($row['rate'] ?? 0), 4);
            $key = number_format($rate, 4, '.', '');
            if (!isset($aggregated[$key])) {
                $aggregated[$key] = array('rate' => $rate, 'base' => 0.0, 'tax' => 0.0);
            }
            $aggregated[$key]['base'] += (float) $sign * (float) ($row['base'] ?? 0);
            $aggregated[$key]['tax'] += (float) $sign * (float) ($row['tax'] ?? 0);
        }
        ksort($aggregated, SORT_NATURAL);
        return array_values($aggregated);
    }

    private function amount($value, $currency)
    {
        return number_format((float) $value, 2, ',', '.') . ' ' . strtoupper((string) $currency);
    }

    private function formatQuantity($value)
    {
        $formatted = number_format((float) $value, 3, ',', '.');
        return rtrim(rtrim($formatted, '0'), ',');
    }

    private function formatRate($rate)
    {
        $formatted = number_format((float) $rate, 2, ',', '.');
        $formatted = rtrim(rtrim($formatted, '0'), ',');
        return ($formatted === '' ? '0' : $formatted) . ' %';
    }

    private function decimalCss($value)
    {
        return rtrim(rtrim(number_format((float) $value, 2, '.', ''), '0'), '.');
    }

    private function e($value)
    {
        return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }
}
