<?php
/**
 * PDF Generator — confirmation PDF via bundled FPDF + URL helpers for static model forms.
 *
 * @package WPNest_Returns_For_WooCommerce
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class WPNest_RFW_PDF_Generator {

    const SUPPORTED_LOCALES = array( 'en', 'pl' );

    /**
     * Generates the confirmation PDF for a return request.
     * Idempotent: returns the existing path when the row's pdf_path already exists on disk.
     *
     * Filename pattern: `return-conf-{request_id}-{32hex_token}.pdf`. The token is generated
     * once and persisted to wpnest_rfw_requests.pdf_token. Direct URL access to the file is
     * blocked by the .htaccess written by ensure_upload_dir(); see get_confirmation_url() for
     * the authenticated stream endpoint.
     *
     * @param int $request_id
     * @return string|false  Filesystem path or false on failure / missing FPDF.
     */
    public static function generate_confirmation( $request_id ) {
        $request_id = (int) $request_id;
        if ( $request_id <= 0 ) {
            return false;
        }

        $request = WPNest_RFW_Request_Model::get( $request_id );
        if ( ! $request ) {
            return false;
        }

        if ( $request->pdf_path && file_exists( $request->pdf_path ) ) {
            return (string) $request->pdf_path;
        }

        $order = wc_get_order( (int) $request->order_id );
        if ( ! $order instanceof WC_Order ) {
            return false;
        }

        $tfpdf_file = WPNEST_RFW_PLUGIN_DIR . 'libraries/tfpdf/tfpdf.php';
        if ( ! file_exists( $tfpdf_file ) ) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Surfaces missing-library state to the admin during dev/install when WP_DEBUG_LOG is on.
            error_log( '[wpnest-rfw] tFPDF library missing at ' . $tfpdf_file . ' — skipping PDF generation.' );
            return false;
        }

        $dir = self::ensure_upload_dir();
        if ( ! $dir ) {
            return false;
        }

        // Random 32-hex token in the filename — even if .htaccess fails on a misconfigured
        // host, the URL is unguessable.
        $token = bin2hex( random_bytes( 16 ) );
        $filename  = sprintf( 'return-conf-%d-%s.pdf', $request_id, $token );
        $full_path = trailingslashit( $dir ) . $filename;

        try {
            require_once $tfpdf_file;
            self::render_pdf( $full_path, $order, $request );
        } catch ( \Throwable $e ) {
            // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Logs PDF generator faults to error_log when WP_DEBUG_LOG is enabled.
            error_log( '[wpnest-rfw] PDF generation failed: ' . $e->getMessage() );
            return false;
        }

        if ( ! file_exists( $full_path ) ) {
            return false;
        }

        // Persist both columns so the REST stream finds the file without regenerating.
        WPNest_RFW_Request_Model::update_column( $request_id, 'pdf_path', $full_path, '%s' );
        WPNest_RFW_Request_Model::update_column( $request_id, 'pdf_token', $token, '%s' );

        return $full_path;
    }

    /**
     * Returns the authenticated REST stream URL for a request's confirmation PDF.
     *
     * For logged-in users (admin clicking the link in the dashboard, or a customer
     * on My Account), a `_wpnonce=<wp_rest>` query var is appended so the REST API's
     * cookie-auth gate accepts the browser request. Guests pass their `?token=<guest>`
     * argument separately — they don't need a nonce.
     *
     * @param int $request_id
     * @return string
     */
    public static function get_confirmation_url( $request_id ) {
        $request_id = (int) $request_id;
        if ( $request_id <= 0 ) {
            return '';
        }
        $url = rest_url( 'wpnest-rfw/v1/pdf/' . $request_id );
        if ( is_user_logged_in() ) {
            $url = add_query_arg( '_wpnonce', wp_create_nonce( 'wp_rest' ), $url );
        }
        return $url;
    }

    /**
     * Returns the URL of the model form for given locale.
     *
     * Resolution order:
     *   1. Custom PDF uploaded via Settings → Tools (option `wpnest_rfw_custom_form_<locale>_id`)
     *   2. Bundled default at `assets/pdf/return-form-<locale>.pdf`
     *   3. Empty string (graceful fallback — UI hides the link)
     *
     * @param string $locale  'en' or 'pl'
     * @return string
     */
    public static function get_model_form_url( $locale = 'en' ) {
        $locale = in_array( $locale, self::SUPPORTED_LOCALES, true ) ? $locale : 'en';

        // 1. Custom upload from Media Library.
        $custom_id = (int) get_option( 'wpnest_rfw_custom_form_' . $locale . '_id', 0 );
        if ( $custom_id > 0 ) {
            $custom_url = wp_get_attachment_url( $custom_id );
            if ( $custom_url ) {
                return $custom_url;
            }
        }

        // 2. Bundled default.
        $file = WPNEST_RFW_PLUGIN_DIR . 'assets/pdf/return-form-' . $locale . '.pdf';
        if ( file_exists( $file ) ) {
            return WPNEST_RFW_PLUGIN_URL . 'assets/pdf/return-form-' . $locale . '.pdf';
        }

        return '';
    }

    /**
     * Returns absolute filesystem path of the upload directory and ensures the
     * privacy hardening files (index.php silence, .htaccess Deny, web.config Deny)
     * are present. Direct HTTP access to confirmation PDFs is denied — they must
     * be served through the authenticated REST endpoint `/pdf/{request_id}`.
     *
     * @return string|false
     */
    private static function ensure_upload_dir() {
        $upload = wp_upload_dir();
        if ( empty( $upload['basedir'] ) ) {
            return false;
        }
        $dir = trailingslashit( $upload['basedir'] ) . 'wpnest-rfw';
        if ( ! file_exists( $dir ) ) {
            if ( ! wp_mkdir_p( $dir ) ) {
                return false;
            }
        }

        $index = $dir . '/index.php';
        if ( ! file_exists( $index ) ) {
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- One-shot directory hardening.
            @file_put_contents( $index, "<?php\n// Silence is golden.\n" );
        }

        // Apache hardening — overwrite any older `Options -Indexes`-only file so the
        // privacy fix lands on existing installs after upgrade.
        $htaccess          = $dir . '/.htaccess';
        $expected_htaccess = "Options -Indexes\n<IfModule mod_authz_core.c>\nRequire all denied\n</IfModule>\n<IfModule !mod_authz_core.c>\nOrder allow,deny\nDeny from all\n</IfModule>\n";
        if ( ! file_exists( $htaccess ) || trim( (string) @file_get_contents( $htaccess ) ) !== trim( $expected_htaccess ) ) {
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- One-shot directory hardening.
            @file_put_contents( $htaccess, $expected_htaccess );
        }

        // IIS hardening (web.config). Apache hosts ignore this.
        $webconfig = $dir . '/web.config';
        if ( ! file_exists( $webconfig ) ) {
            // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- One-shot directory hardening.
            @file_put_contents(
                $webconfig,
                "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<configuration>\n  <system.webServer>\n    <authorization>\n      <deny users=\"*\" />\n    </authorization>\n  </system.webServer>\n</configuration>\n"
            );
        }

        return $dir;
    }

    /**
     * Returns the writable directory tFPDF should use for fontpath. Copies the
     * bundled DejaVu TTFs + ttfonts.php from libraries/ on first run so the
     * plugin's own folder stays read-only (per wp.org expectations) and font
     * metrics caches (`.mtx.php`, `.cw127.php`, `.cw.dat`) get written under
     * `wp-content/uploads/`.
     *
     * Returns path *with* trailing slash — tFPDF concatenates `$file` against
     * `$this->fontpath` directly.
     */
    private static function ensure_font_cache_dir() {
        $upload  = wp_upload_dir();
        $cache   = trailingslashit( $upload['basedir'] ) . 'wpnest-rfw/tfpdf-fonts/';
        $unifont = $cache . 'unifont/';
        if ( ! file_exists( $unifont ) ) {
            wp_mkdir_p( $unifont );
        }
        $lib_unifont = WPNEST_RFW_PLUGIN_DIR . 'libraries/tfpdf/font/unifont/';
        foreach ( array( 'DejaVuSans.ttf', 'DejaVuSans-Bold.ttf', 'DejaVuSans-Oblique.ttf', 'ttfonts.php' ) as $f ) {
            $dest = $unifont . $f;
            $src  = $lib_unifont . $f;
            if ( file_exists( $src ) && ! file_exists( $dest ) ) {
                // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_copy -- One-shot bundle warm-up to a writable dir.
                @copy( $src, $dest );
            }
        }
        return $cache;
    }

    /**
     * Renders the PDF body. Uses tFPDF + DejaVu Sans TTF for native UTF-8
     * support — Polish (ą/ć/ę/ł/ń/ś/ź/ż), German, Czech, French and other
     * European characters render correctly without transliteration.
     *
     * Pro can replace the entire body via the `wpnest_rfw_render_pdf_body`
     * filter (signature: callable( tFPDF $pdf, WC_Order $order, object $request ) : void).
     */
    private static function render_pdf( $path, WC_Order $order, $request ) {
        // tFPDF zapisuje font metrics (.mtx.php, .cw127.php, .cw.dat) obok
        // TTF. Plugin libraries/ ma być read-only po install, więc kierujemy
        // fontpath na writable uploads dir przez stałą FPDF_FONTPATH (tFPDF
        // czyta ją w konstruktorze) i kopiujemy TTF tam przy pierwszym uruchomieniu.
        if ( ! defined( 'FPDF_FONTPATH' ) ) {
            define( 'FPDF_FONTPATH', self::ensure_font_cache_dir() );
        }

        $pdf = new tFPDF( 'P', 'mm', 'A4' );
        $pdf->SetTitle( __( 'Return Request Confirmation', 'wpnest-returns-for-woocommerce' ) );
        $pdf->SetAuthor( get_bloginfo( 'name' ) );
        $pdf->SetCreator( 'WPNest Returns for WooCommerce' );

        $pdf->AddFont( 'DejaVu', '', 'DejaVuSans.ttf', true );
        $pdf->AddFont( 'DejaVu', 'B', 'DejaVuSans-Bold.ttf', true );
        $pdf->AddFont( 'DejaVu', 'I', 'DejaVuSans-Oblique.ttf', true );

        $pdf->AddPage();

        /**
         * Pro filter — when present, fully replaces the default body so Pro can
         * render a merchant-edited template instead.
         *
         * @param callable|null $callback function( tFPDF $pdf, WC_Order $order, object $request ): void
         * @param object        $request
         */
        $custom = apply_filters( 'wpnest_rfw_render_pdf_body', null, $request );
        if ( is_callable( $custom ) ) {
            call_user_func( $custom, $pdf, $order, $request );
        } else {
            self::render_default_body( $pdf, $order, $request );
        }

        $pdf->Output( 'F', $path );
    }

    /**
     * Default PDF body — used when Pro does not override via the filter.
     * Public so the Pro editor can call back into it for the "reset to default"
     * flow.
     */
    public static function render_default_body( $pdf, WC_Order $order, $request ) {
        // Store header.
        $pdf->SetFont( 'DejaVu', 'B', 14 );
        $pdf->Cell( 0, 8, get_bloginfo( 'name' ), 0, 1 );

        $pdf->SetFont( 'DejaVu', '', 10 );
        $pdf->Cell( 0, 5, home_url(), 0, 1 );
        $pdf->Ln( 4 );

        // Title.
        $pdf->SetFont( 'DejaVu', 'B', 16 );
        $pdf->Cell( 0, 9, __( 'Return Request Confirmation', 'wpnest-returns-for-woocommerce' ), 0, 1 );

        $pdf->SetFont( 'DejaVu', '', 10 );
        /* translators: %s: ISO-8601 date */
        $pdf->Cell( 0, 5, sprintf( __( 'Generated: %s', 'wpnest-returns-for-woocommerce' ), gmdate( 'Y-m-d H:i' ) . ' UTC' ), 0, 1 );
        /* translators: %d: numeric request ID */
        $pdf->Cell( 0, 5, sprintf( __( 'Request ID: #%d', 'wpnest-returns-for-woocommerce' ), (int) $request->id ), 0, 1 );
        $pdf->Ln( 4 );

        // Order details.
        $pdf->SetFont( 'DejaVu', 'B', 11 );
        $pdf->Cell( 0, 6, __( 'Order details', 'wpnest-returns-for-woocommerce' ), 0, 1 );
        $pdf->SetFont( 'DejaVu', '', 10 );
        $pdf->Cell( 50, 5, __( 'Order number:', 'wpnest-returns-for-woocommerce' ), 0, 0 );
        $pdf->Cell( 0, 5, '#' . $order->get_order_number(), 0, 1 );

        $order_date = $order->get_date_created();
        $pdf->Cell( 50, 5, __( 'Order date:', 'wpnest-returns-for-woocommerce' ), 0, 0 );
        $pdf->Cell( 0, 5, $order_date ? $order_date->date( 'Y-m-d H:i' ) : '', 0, 1 );

        $billing = trim( $order->get_billing_first_name() . ' ' . $order->get_billing_last_name() );
        if ( $billing ) {
            $pdf->Cell( 50, 5, __( 'Customer:', 'wpnest-returns-for-woocommerce' ), 0, 0 );
            $pdf->Cell( 0, 5, $billing, 0, 1 );
        }

        $pdf->Ln( 4 );

        // Returned items table.
        $pdf->SetFont( 'DejaVu', 'B', 11 );
        $pdf->Cell( 0, 6, __( 'Items being returned', 'wpnest-returns-for-woocommerce' ), 0, 1 );
        $pdf->SetFont( 'DejaVu', 'B', 10 );
        $pdf->Cell( 16, 6, __( 'Qty', 'wpnest-returns-for-woocommerce' ), 'B', 0 );
        $pdf->Cell( 90, 6, __( 'Item', 'wpnest-returns-for-woocommerce' ), 'B', 0 );
        $pdf->Cell( 0, 6, __( 'Reason', 'wpnest-returns-for-woocommerce' ), 'B', 1 );

        $pdf->SetFont( 'DejaVu', '', 10 );
        $items_rows = WPNest_RFW_Request_Item_Model::get_for_request( (int) $request->id );
        if ( empty( $items_rows ) ) {
            $pdf->Cell( 0, 5, __( '(No itemized data — see linked order.)', 'wpnest-returns-for-woocommerce' ), 0, 1 );
        } else {
            foreach ( $items_rows as $ir ) {
                $product_name = '';
                $oi = $order->get_item( (int) $ir->order_item_id );
                if ( $oi ) {
                    $product_name = $oi->get_name();
                }
                if ( '' === $product_name && $ir->product_id ) {
                    $p = wc_get_product( (int) $ir->product_id );
                    $product_name = $p ? $p->get_name() : ( '#' . (int) $ir->product_id );
                }
                $reason_label = WPNest_RFW_Reason_Codes::get_label( (string) $ir->reason_code );

                $pdf->Cell( 16, 6, (string) (int) $ir->qty, 'B', 0 );
                $pdf->Cell( 90, 6, $product_name, 'B', 0 );
                $pdf->Cell( 0, 6, $reason_label, 'B', 1 );

                if ( $ir->reason_text ) {
                    $pdf->SetFont( 'DejaVu', 'I', 9 );
                    $pdf->Cell( 16, 5, '', 0, 0 );
                    $pdf->MultiCell( 0, 5, '„' . trim( (string) $ir->reason_text ) . '"', 0, 'L' );
                    $pdf->SetFont( 'DejaVu', '', 10 );
                }
            }
        }

        $pdf->Ln( 4 );

        // Customer statement.
        $pdf->SetFont( 'DejaVu', 'I', 10 );
        $pdf->MultiCell( 0, 5, __( 'I hereby exercise my right of return in accordance with Directive (EU) 2023/2673.', 'wpnest-returns-for-woocommerce' ), 0, 'L' );

        // Footer — give it enough headroom so the legal disclaimer never breaks across pages.
        $pdf->SetY( -30 );
        $pdf->SetFont( 'DejaVu', 'I', 7 );
        $pdf->MultiCell( 0, 3.5, __( 'This document serves as durable medium confirmation per Art. 11 of Directive 2011/83/EU (Consumer Rights Directive).', 'wpnest-returns-for-woocommerce' ), 0, 'C' );
    }

    /**
     * @deprecated Kept for backwards compatibility with any third-party code
     * that called the iconv transliterator before the tFPDF switch.
     */
    private static function s( $str ) {
        $str = (string) $str;
        if ( function_exists( 'iconv' ) ) {
            $converted = @iconv( 'UTF-8', 'CP1252//TRANSLIT//IGNORE', $str );
            if ( false !== $converted ) {
                return $converted;
            }
        }
        return $str;
    }
}
