<?php

declare(strict_types=1);

namespace CloudXM\NFe\Rest;

use CloudXM\NFe\Services\ApiClient;
use CloudXM\NFe\Services\NFeFileStorageService;
use CloudXM\SDK\Support\HasLogger;

/**
 * Invoice Document Controller
 *
 * Handles PDF and XML downloads with local storage fallback
 */
class InvoiceDocumentController
{
    use HasLogger;

    /**
     * API Client instance
     */
    private ApiClient $apiClient;

    /**
     * File Storage Service instance
     */
    private NFeFileStorageService $fileStorage;

    /**
     * Constructor
     */
    public function __construct(ApiClient $apiClient, NFeFileStorageService $fileStorage)
    {
        $this->apiClient = $apiClient;
        $this->fileStorage = $fileStorage;
    }

    /**
     * Download invoice PDF
     *
     * First tries to serve from local storage, falls back to API if not found
     *
     * @param \WP_REST_Request $request
     * @return void
     */
    public function downloadPdf(\WP_REST_Request $request): void
    {
        $invoiceId = $request->get_param('id');

        $this->log()->info('nfe.pdf_download_requested', [
            'invoice_id' => $invoiceId,
            'request_params' => $request->get_params(),
        ]);

        try {
            $content = $this->getPdfContent($invoiceId);

            if (empty($content)) {
                $this->log()->warning('nfe.pdf_not_available', [
                    'invoice_id' => $invoiceId,
                ]);

                $this->showErrorPage(
                    __('PDF Not Available', 'woocommerce-cloudxm-nfe'),
                    __('The PDF file could not be downloaded. This may be because:', 'woocommerce-cloudxm-nfe') .
                    '<ul style="list-style: disc; margin-left: 20px;">' .
                    '<li>' . __('The NFE server is temporarily unavailable (500 error)', 'woocommerce-cloudxm-nfe') . '</li>' .
                    '<li>' . __('The files were not generated at issuance time', 'woocommerce-cloudxm-nfe') . '</li>' .
                    '<li>' . __('The invoice has been cancelled or is in an invalid state', 'woocommerce-cloudxm-nfe') . '</li>' .
                    '</ul>' .
                    '<p>' . __('You can try again later or re-issue the invoice.', 'woocommerce-cloudxm-nfe') . '</p>' .
                    '<p><a href="' . esc_url(admin_url('admin.php?page=cloudxm-nfe-invoices')) . '">' . __('Return to Invoices', 'woocommerce-cloudxm-nfe') . '</a></p>'
                );
                return;
            }

            $this->streamFile($content, 'application/pdf', "nfe-{$invoiceId}.pdf");
        } catch (\Exception $e) {
            $this->log()->error('nfe.pdf_download_failed', [
                'invoice_id' => $invoiceId,
                'error' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
            ]);

            $this->showErrorPage(
                __('Download Failed', 'woocommerce-cloudxm-nfe'),
                sprintf(__('An error occurred while downloading the PDF: %s', 'woocommerce-cloudxm-nfe'), esc_html($e->getMessage())) .
                '<p><a href="' . esc_url(admin_url('admin.php?page=cloudxm-nfe-invoices')) . '">' . __('Return to Invoices', 'woocommerce-cloudxm-nfe') . '</a></p>'
            );
        }
    }

    /**
     * Download invoice XML
     *
     * First tries to serve from local storage, falls back to API if not found
     *
     * @param \WP_REST_Request $request
     * @return void
     */
    public function downloadXml(\WP_REST_Request $request): void
    {
        $invoiceId = $request->get_param('id');

        try {
            $result = $this->getXmlContent($invoiceId);

            if ($result === null) {
                $this->log()->warning('nfe.xml_not_available', [
                    'invoice_id' => $invoiceId,
                ]);

                $this->showErrorPage(
                    __('XML Not Available', 'woocommerce-cloudxm-nfe'),
                    __('The XML file could not be downloaded. This may be because:', 'woocommerce-cloudxm-nfe') .
                    '<ul style="list-style: disc; margin-left: 20px;">' .
                    '<li>' . __('The NFE server is temporarily unavailable (500 error)', 'woocommerce-cloudxm-nfe') . '</li>' .
                    '<li>' . __('The files were not generated at issuance time', 'woocommerce-cloudxm-nfe') . '</li>' .
                    '<li>' . __('The invoice has been cancelled or is in an invalid state', 'woocommerce-cloudxm-nfe') . '</li>' .
                    '</ul>' .
                    '<p>' . __('You can try again later or re-issue the invoice.', 'woocommerce-cloudxm-nfe') . '</p>' .
                    '<p><a href="' . esc_url(admin_url('admin.php?page=cloudxm-nfe-invoices')) . '">' . __('Return to Invoices', 'woocommerce-cloudxm-nfe') . '</a></p>'
                );
                return;
            }

            // Detect ZIP by magic bytes (PK\x03\x04) or explicit content-type.
            // Cancelled invoices return a ZIP archive from the NFe server.
            $isZip = ($result['content_type'] === 'application/zip')
                || ($result['content_type'] === 'application/octet-stream')
                || (str_starts_with($result['content'], "PK\x03\x04"));

            if ($isZip) {
                $this->streamFile($result['content'], 'application/zip', "nfe-{$invoiceId}.zip");
            } else {
                $this->streamFile($result['content'], 'application/xml', "nfe-{$invoiceId}.xml");
            }
        } catch (\Exception $e) {
            $this->log()->error('nfe.xml_download_failed', [
                'invoice_id' => $invoiceId,
                'error' => $e->getMessage(),
            ]);

            $this->showErrorPage(
                __('Download Failed', 'woocommerce-cloudxm-nfe'),
                sprintf(__('An error occurred while downloading the XML: %s', 'woocommerce-cloudxm-nfe'), esc_html($e->getMessage())) .
                '<p><a href="' . esc_url(admin_url('admin.php?page=cloudxm-nfe-invoices')) . '">' . __('Return to Invoices', 'woocommerce-cloudxm-nfe') . '</a></p>'
            );
        }
    }

    /**
     * Get PDF content from local storage or API
     *
     * @param string $invoiceId
     * @return string|null
     */
    private function getPdfContent(string $invoiceId): ?string
    {
        // Get order ID from invoice ID
        $orders = wc_get_orders([
            'limit' => 1,
            'meta_key' => '_cloudxm_nfe_invoice_id',
            'meta_value' => $invoiceId,
        ]);

        if (empty($orders)) {
            $this->log()->warning('nfe.order_not_found', ['invoice_id' => $invoiceId]);
            return null;
        }

        $order = $orders[0];
        $accessKey = $order->get_meta('_cloudxm_nfe_access_key');
        $emissionDate = $order->get_meta('_cloudxm_nfe_issuance_date');

        if (empty($accessKey)) {
            // Current meta may point to a different invoice (e.g. scheduled then cancelled).
            // Check cancelled invoice history for a matching invoice_id.
            $historyJson = $order->get_meta('_cloudxm_nfe_cancelled_invoice_history');
            if (!empty($historyJson)) {
                $history = json_decode($historyJson, true);
                if (is_array($history) && isset($history['invoice_id']) && (string) $history['invoice_id'] === $invoiceId) {
                    $accessKey = $history['access_key'] ?? '';
                    $emissionDate = $history['issuance_date'] ?? '';
                }
            }

            if (empty($accessKey)) {
                $this->log()->warning('nfe.access_key_missing', ['invoice_id' => $invoiceId]);
                return null;
            }
        }

        try {
            // Handle empty or invalid emission date
            if (empty($emissionDate)) {
                $this->log()->warning('nfe.emission_date_missing', [
                    'invoice_id' => $invoiceId,
                ]);
                // Use current time as fallback
                $emissionDateTime = new \DateTime();
            } else {
                $emissionDateTime = new \DateTime($emissionDate);
            }
            
            // Check if file exists locally
            $relativePath = $this->fileStorage->getFilePathFromAccessKey($accessKey, 'pdf', $emissionDateTime);
            
            if ($this->fileStorage->fileExists($relativePath)) {
                $this->log()->debug('nfe.pdf_served_from_storage', [
                    'invoice_id' => $invoiceId,
                    'access_key' => $accessKey,
                    'path' => $relativePath,
                ]);
                
                return $this->fileStorage->getFileContents($relativePath);
            }

            // Fallback: Download from API
            $this->log()->info('nfe.pdf_fallback_to_api', [
                'invoice_id' => $invoiceId,
                'access_key' => $accessKey,
            ]);

            $content = $this->apiClient->downloadPdfV2($invoiceId);
            
            // Store file locally for next time
            if (!empty($content)) {
                try {
                    $this->fileStorage->storePdf($invoiceId, $content, $accessKey, $emissionDateTime);
                    $this->log()->info('nfe.pdf_stored_after_download', [
                        'invoice_id' => $invoiceId,
                        'access_key' => $accessKey,
                    ]);
                } catch (\Exception $e) {
                    // Log but don't fail - we already have the content
                    $this->log()->warning('nfe.pdf_storage_failed', [
                        'invoice_id' => $invoiceId,
                        'error' => $e->getMessage(),
                    ]);
                }
            }

            return $content;
        } catch (\Exception $e) {
            $this->log()->error('nfe.pdf_content_error', [
                'invoice_id' => $invoiceId,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);
            
            return null;
        }
    }

    /**
     * Get XML content from local storage or API
     *
     * Returns array with 'content' and 'content_type', or null if unavailable.
     * For cancelled invoices the server returns a ZIP archive.
     *
     * @param string $invoiceId
     * @return array{content: string, content_type: string}|null
     */
    private function getXmlContent(string $invoiceId): ?array
    {
        // Get order ID from invoice ID
        $orders = wc_get_orders([
            'limit' => 1,
            'meta_key' => '_cloudxm_nfe_invoice_id',
            'meta_value' => $invoiceId,
        ]);

        if (empty($orders)) {
            $this->log()->warning('nfe.order_not_found', ['invoice_id' => $invoiceId]);
            return null;
        }

        $order = $orders[0];
        $accessKey = $order->get_meta('_cloudxm_nfe_access_key');
        $emissionDate = $order->get_meta('_cloudxm_nfe_issuance_date');
        $nfeStatus = (string) $order->get_meta('_cloudxm_nfe_status');

        if (empty($accessKey)) {
            $historyJson = $order->get_meta('_cloudxm_nfe_cancelled_invoice_history');
            if (!empty($historyJson)) {
                $history = json_decode($historyJson, true);
                if (is_array($history) && isset($history['invoice_id']) && (string) $history['invoice_id'] === $invoiceId) {
                    $accessKey = $history['access_key'] ?? '';
                    $emissionDate = $history['issuance_date'] ?? '';
                    $nfeStatus = 'cancelled';
                }
            }

            if (empty($accessKey)) {
                $this->log()->warning('nfe.access_key_missing', ['invoice_id' => $invoiceId]);
                return null;
            }
        }

        try {
            $emissionDateTime = empty($emissionDate) ? new \DateTime() : new \DateTime($emissionDate);
            
            // Check if file exists locally
            $relativePath = $this->fileStorage->getFilePathFromAccessKey($accessKey, 'xml', $emissionDateTime);

            if ($this->fileStorage->fileExists($relativePath)) {
                $this->log()->debug('nfe.xml_served_from_storage', [
                    'invoice_id' => $invoiceId,
                    'access_key' => $accessKey,
                    'path' => $relativePath,
                ]);

                $content = $this->fileStorage->getFileContents($relativePath);
                if ($content === null) {
                    return null;
                }
                // Detect ZIP by magic bytes — cached files have no content-type header
                $contentType = str_starts_with($content, "PK\x03\x04") ? 'application/zip' : 'application/xml';
                return ['content' => $content, 'content_type' => $contentType];
            }

            // Fallback: Download from API (returns content + content-type)
            $this->log()->info('nfe.xml_fallback_to_api', [
                'invoice_id' => $invoiceId,
                'access_key' => $accessKey,
            ]);

            $result = $this->apiClient->downloadXmlV2WithMeta($invoiceId);

            if (!empty($result['content'])) {
                try {
                    // Store using the raw content (may be ZIP for cancelled invoices)
                    $this->fileStorage->storeXml($invoiceId, $result['content'], $accessKey, $emissionDateTime);
                    $this->log()->info('nfe.xml_stored_after_download', [
                        'invoice_id' => $invoiceId,
                        'access_key' => $accessKey,
                        'content_type' => $result['content_type'],
                    ]);
                } catch (\Exception $e) {
                    $this->log()->warning('nfe.xml_storage_failed', [
                        'invoice_id' => $invoiceId,
                        'error' => $e->getMessage(),
                    ]);
                }
            }

            return !empty($result['content']) ? $result : null;
        } catch (\Exception $e) {
            $this->log()->error('nfe.xml_content_error', [
                'invoice_id' => $invoiceId,
                'error' => $e->getMessage(),
            ]);
            return null;
        }
    }

    /**
     * Stream file to browser
     *
     * @param string $content File content
     * @param string $mimeType MIME type
     * @param string $filename Filename
     * @return void
     */
    private function streamFile(string $content, string $mimeType, string $filename): void
    {
        // Clean all output buffers
        while (ob_get_level()) {
            ob_end_clean();
        }

        // Disable WordPress output buffering
        if (function_exists('apache_setenv')) {
            @apache_setenv('no-gzip', '1');
        }
        @ini_set('zlib.output_compression', 'Off');
        @ini_set('output_buffering', 'Off');
        @ini_set('implicit_flush', '1');

        // Set headers
        header('Content-Type: ' . $mimeType);
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Content-Length: ' . strlen($content));
        header('Cache-Control: private, max-age=0, must-revalidate');
        header('Pragma: public');
        header('Connection: close');

        // Send content
        echo $content;

        // Flush output
        if (function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        } else {
            flush();
        }

        exit;
    }

    /**
     * Show a user-friendly error page instead of crashing with 500
     *
     * @param string $title Error title
     * @param string $message Error message (can contain HTML)
     * @return void
     */
    private function showErrorPage(string $title, string $message): void
    {
        // Clean all output buffers
        while (ob_get_level()) {
            ob_end_clean();
        }

        // Set status code to 200 so browser doesn't show a generic error page
        status_header(200);
        header('Content-Type: text/html; charset=utf-8');

        // Output a user-friendly error page
        echo '<!DOCTYPE html>
<html lang="' . get_bloginfo('language') . '">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>' . esc_html($title) . '</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
            background: #f0f0f1;
            padding: 40px 20px;
            line-height: 1.6;
        }
        .error-container {
            max-width: 600px;
            margin: 40px auto;
            background: #fff;
            border-radius: 8px;
            padding: 40px;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
        }
        h1 {
            color: #d63638;
            margin-top: 0;
            font-size: 24px;
        }
        p {
            margin-bottom: 16px;
            color: #1e1e1e;
        }
        a {
            color: #2271b1;
            text-decoration: none;
        }
        a:hover {
            text-decoration: underline;
        }
        .btn {
            display: inline-block;
            padding: 10px 20px;
            background: #2271b1;
            color: #fff;
            border-radius: 4px;
            margin-top: 10px;
        }
        .btn:hover {
            background: #135e96;
            color: #fff;
            text-decoration: none;
        }
    </style>
</head>
<body>
    <div class="error-container">
        <h1>' . esc_html($title) . '</h1>
        ' . $message . '
    </div>
</body>
</html>';

        exit;
    }
}
