<?php
/** Local-only AEAT QR generator. No invoice data is sent to third-party services. */
if (!defined('ABSPATH')) {
    exit;
}

use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\ErrorCorrectionLevel;
use Endroid\QrCode\QrCode;
use Endroid\QrCode\RoundBlockSizeMode;
use Endroid\QrCode\Writer\PngWriter;

class VerifactuQrService
{
    public static function buildUrl($issuerNif, array $identity, $total, $environment = null)
    {
        if (empty($identity['number']) || empty($identity['issue_date'])) {
            throw new Exception('No se puede generar el QR sin numero y fecha de factura.');
        }
        $issuerNif = trim((string) $issuerNif);
        if ($issuerNif === '') {
            throw new Exception('No se puede generar el QR sin NIF del emisor.');
        }

        $query = http_build_query(array(
            'nif' => $issuerNif,
            'numserie' => (string) $identity['number'],
            'fecha' => (string) $identity['issue_date'],
            'importe' => VerifactuInvoiceData::formatAmount($total),
        ), '', '&', PHP_QUERY_RFC3986);

        return self::endpointForEnvironment($environment) . '?' . $query;
    }

    public static function generateInvoice($order, $invoice)
    {
        $invoice = VerifactuDocumentIdentity::getInvoice($order, $invoice);
        $identity = VerifactuDocumentIdentity::getInvoiceIdentity($order, $invoice);
        return self::generate($identity, (float) $invoice->total_paid_tax_incl, 'I' . (int) $invoice->id);
    }

    public static function generateCreditSlip($order, $creditSlip)
    {
        $order = is_numeric($order) ? wc_get_order((int) $order) : $order;
        if (!($order instanceof WC_Order)) {
            throw new Exception('No se ha podido cargar el pedido del abono.');
        }
        $refund = $creditSlip instanceof WC_Order_Refund ? $creditSlip : VerifactuDocumentIdentity::getCreditSlip($order, (int) $creditSlip);
        $doc = VerifactuDocumentIdentity::getCreditDocument((int) $refund->get_id());
        $identity = VerifactuDocumentIdentity::getCreditSlipIdentity($order, (int) $refund->get_id());
        return self::generate($identity, -abs((float) $doc->total_paid_tax_incl), 'R' . (int) $refund->get_id());
    }

    /**
     * Generates the QR using the fiscal identity stored in the exact ALTA record.
     * This prevents a later configuration/environment change from changing the
     * QR printed on an already-issued fiscal document.
     */
    public static function generateInvoiceForRecord($order, $invoice, array $record)
    {
        $invoice = VerifactuDocumentIdentity::getInvoice($order, $invoice);
        $identity = VerifactuDocumentIdentity::getInvoiceIdentity($order, $invoice);
        self::assertRecordMatches($record, $identity, 'id_order_invoice', (int) $invoice->id);
        return self::generate(
            $identity,
            (float) $invoice->total_paid_tax_incl,
            'I' . (int) $invoice->id,
            (string) $record['issuer_nif'],
            (string) $record['environment']
        );
    }

    public static function generateCreditSlipForRecord($order, $creditSlip, array $record)
    {
        $order = is_numeric($order) ? wc_get_order((int) $order) : $order;
        if (!($order instanceof WC_Order)) {
            throw new Exception('No se ha podido cargar el pedido del abono.');
        }
        $refund = $creditSlip instanceof WC_Order_Refund ? $creditSlip : VerifactuDocumentIdentity::getCreditSlip($order, (int) $creditSlip);
        $doc = VerifactuDocumentIdentity::getCreditDocument((int) $refund->get_id());
        $identity = VerifactuDocumentIdentity::getCreditSlipIdentity($order, (int) $refund->get_id());
        self::assertRecordMatches($record, $identity, 'id_order_slip', (int) $refund->get_id());
        return self::generate(
            $identity,
            -abs((float) $doc->total_paid_tax_incl),
            'R' . (int) $refund->get_id(),
            (string) $record['issuer_nif'],
            (string) $record['environment']
        );
    }

    public static function generate(array $identity, $total, $documentToken, $issuerNif = null, $environment = null)
    {
        if (!extension_loaded('gd')) {
            throw new Exception('La extension GD de PHP es obligatoria para generar el QR local.');
        }
        if (!class_exists(QrCode::class)) {
            $autoload = dirname(__DIR__) . '/vendor/autoload.php';
            if (!is_file($autoload)) {
                throw new Exception('No se encuentra la libreria local de codigos QR.');
            }
            require_once $autoload;
        }

        $resolvedIssuer = $issuerNif === null ? VerifactuConfig::getIssuerNif() : trim((string) $issuerNif);
        $resolvedEnvironment = $environment === null ? VerifactuConfig::getEnvironment() : self::normaliseEnvironment($environment);
        $url = self::buildUrl($resolvedIssuer, $identity, $total, $resolvedEnvironment);

        $fileToken = isset($identity['file_token']) && $identity['file_token'] !== ''
            ? (string) $identity['file_token']
            : VerifactuDocumentIdentity::safeFileToken((string) $identity['number'] . '_' . (string) $identity['issue_date']);
        $file = DdvWpStorage::path('qr', $resolvedEnvironment . '_'
            . preg_replace('/[^A-Za-z0-9_-]/', '_', (string) $documentToken) . '_'
            . $fileToken . '.png');

        $qr = QrCode::create($url)
            ->setEncoding(new Encoding('UTF-8'))
            ->setErrorCorrectionLevel(ErrorCorrectionLevel::Medium)
            ->setSize(400)
            ->setMargin(32)
            ->setRoundBlockSizeMode(RoundBlockSizeMode::Margin);
        $writer = new PngWriter();
        $result = $writer->write($qr);
        $result->saveToFile($file);
        if (!is_file($file)) {
            throw new Exception('No se ha podido guardar el codigo QR local.');
        }
        @chmod($file, 0640);

        return array(
            'path' => $file,
            'url' => $url,
            'size_mm' => VerifactuConfig::getQrSizeMm(),
            'label_top' => 'QR tributario:',
            'label_bottom' => 'Factura verificable en la sede electronica de la AEAT',
            'label_verifactu' => 'VERI*FACTU',
            'issuer_nif' => $resolvedIssuer,
            'environment' => $resolvedEnvironment,
        );
    }

    private static function assertRecordMatches(array $record, array $identity, $documentColumn, $documentId)
    {
        if (($record['operation'] ?? '') !== 'ALTA') {
            throw new Exception('El QR solo puede vincularse a un registro VeriFactu de alta.');
        }
        if (empty($record['issuer_nif']) || empty($record['environment'])) {
            throw new Exception('El registro VeriFactu no conserva emisor o entorno suficiente para reconstruir el QR.');
        }
        if ((string) ($record['document_number'] ?? '') !== (string) $identity['number']
            || (string) ($record['issue_date'] ?? '') !== (string) $identity['issue_date']) {
            throw new Exception('La identidad del registro VeriFactu no coincide con la del documento fiscal.');
        }
        if (!empty($record[$documentColumn]) && (int) $record[$documentColumn] !== (int) $documentId) {
            throw new Exception('El registro VeriFactu no pertenece al documento fiscal solicitado.');
        }
    }

    private static function endpointForEnvironment($environment)
    {
        if ($environment === null || trim((string) $environment) === '') {
            return VerifactuConfig::getQrEndpoint();
        }
        $environment = self::normaliseEnvironment($environment);
        return $environment === VerifactuConfig::ENV_PRODUCTION
            ? VerifactuConfig::QR_PRODUCTION
            : VerifactuConfig::QR_PREPRODUCTION;
    }

    private static function normaliseEnvironment($environment)
    {
        $environment = strtolower(trim((string) $environment));
        if (!in_array($environment, array(VerifactuConfig::ENV_PRODUCTION, VerifactuConfig::ENV_PREPRODUCTION), true)) {
            throw new Exception('El entorno del registro VeriFactu no es valido para generar el QR.');
        }
        return $environment;
    }
}
