<?php

namespace Storeplugin\PDFInvoice\App;

defined('ABSPATH') || exit;

use Storeplugin\PDFInvoice\Framework\Contracts\ServiceInterface;
use Storeplugin\PDFInvoice\App\Settings\SettingsSchema;
use Storeplugin\PDFInvoice\App\Integrations\PdfDocumentGenerator;
use Storeplugin\PDFInvoice\App\Preview\DocumentPreviewPage;
use Storeplugin\PDFInvoice\App\Utils;
use Mpdf\Mpdf;

class AjaxHandler implements ServiceInterface {
    public function register(): void
    {
        (new DocumentPreviewPage())->register();
        \add_action('wp_ajax_nxtpdf_generate_document_preview', [$this, 'generate_preview']);
        \add_action('wp_ajax_nxtpdf_save_document_settings', [$this, 'save_settings']);
        \add_action('wp_ajax_nxtpdf_download_document', [$this, 'download_pdf']);
    }

    /**
     * Check if mPDF is available
     */
    private function check_mpdf() {
        return class_exists(Mpdf::class);
    }

    /**
     * Prepare live preview and return the dedicated preview page URL.
     */
    public function generate_preview() {
        check_ajax_referer('nxtpdf_document_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Unauthorized', 'storeinvoice-print-pdf-invoices-and-packing-slips')]);
        }

        try {
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Raw settings are unslashed in parse_settings_payload() and sanitized in sanitize_settings().
            $rawSettings = isset($_POST['settings']) ? $_POST['settings'] : null;
            $optionGroup = $this->resolve_editor_group($rawSettings);
            $settings = $rawSettings !== null ? $this->sanitize_settings($rawSettings, $optionGroup) : [];
            $previewUrl = DocumentPreviewPage::createPreviewUrl($settings, $optionGroup, get_current_user_id());

            wp_send_json_success([
                'preview_url' => $previewUrl,
            ]);
        } catch (\Exception $e) {
            wp_send_json_error([
                'message' => 'Error: ' . $e->getMessage(),
            ]);
        } catch (\Error $e) {
            wp_send_json_error([
                'message' => 'Fatal Error: ' . $e->getMessage(),
            ]);
        }
    }

    /**
     * Save settings
     */
    public function save_settings() {
        check_ajax_referer('nxtpdf_document_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(['message' => __('Unauthorized', 'storeinvoice-print-pdf-invoices-and-packing-slips')]);
        }

        if (!isset($_POST['settings'])) {
            wp_send_json_error(['message' => __('No settings provided', 'storeinvoice-print-pdf-invoices-and-packing-slips')]);
        }

        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Raw settings are unslashed in parse_settings_payload() and sanitized in sanitize_settings().
        $rawSettings = $_POST['settings'];
        $optionGroup = $this->resolve_editor_group($rawSettings);
        $settings = $this->sanitize_settings($rawSettings, $optionGroup);
        $existing = Utils::getOptions($optionGroup);
        Utils::updateOptions($optionGroup, array_merge($existing, $settings));

        wp_send_json_success(['message' => __('Settings saved successfully', 'storeinvoice-print-pdf-invoices-and-packing-slips')]);
    }

    /**
     * Download PDF
     */
    public function download_pdf() {
        check_ajax_referer('nxtpdf_document_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('Unauthorized', 'storeinvoice-print-pdf-invoices-and-packing-slips'));
        }

        if (!$this->check_mpdf()) {
            wp_die(esc_html__('mPDF library not found. Please run "composer install" in the plugin directory.', 'storeinvoice-print-pdf-invoices-and-packing-slips'));
        }

        $order_id = isset($_GET['order_id']) ? absint($_GET['order_id']) : 0;
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Raw settings are unslashed in parse_settings_payload() and sanitized in sanitize_settings().
        $rawSettings = isset($_POST['settings']) ? $_POST['settings'] : null;
        $optionGroup = $this->resolve_editor_group($rawSettings);
        $settings = $rawSettings !== null ? $this->sanitize_settings($rawSettings, $optionGroup) : [];

        try {
            $generator = new PdfDocumentGenerator($settings, $optionGroup);

            if ($order_id && class_exists('WooCommerce')) {
                $generator->generate_order_pdf($order_id);
                $filenamePrefix = $optionGroup === Utils::GROUP_PACKING_SLIP_EDITOR ? 'packing-slip' : 'invoice';
                $filename = $filenamePrefix . '-' . $order_id . '.pdf';
            } else {
                $generator->generate_preview_pdf();
                $filename = $optionGroup === Utils::GROUP_PACKING_SLIP_EDITOR ? 'packing-slip-preview.pdf' : 'invoice-preview.pdf';
            }

            $generator->output($filename, 'D');
            exit;
        } catch (\Exception $e) {
            wp_die(esc_html__('Error generating PDF:', 'storeinvoice-print-pdf-invoices-and-packing-slips') . ' ' . esc_html($e->getMessage()));
        }
    }

    /**
     * Sanitize settings
     */
    private function sanitize_settings($settings, string $optionGroup = Utils::GROUP_INVOICE_EDITOR) {
        $settings = $this->parse_settings_payload($settings);
        if (!is_array($settings)) {
            return [];
        }

        $settings = $this->extract_nested_settings($settings, $optionGroup);
        if (!is_array($settings)) {
            return [];
        }

        $sanitized = [];
        $renderedToggles = [];

        foreach ($settings as $key => $value) {
            $key = sanitize_key((string) $key);
            if ($key === '') {
                continue;
            }

            if (strpos($key, '_nxtf_rendered_') === 0) {
                $toggleKey = sanitize_key(substr($key, 15));
                if ($toggleKey !== '') {
                    $renderedToggles[$toggleKey] = true;
                }
                continue;
            }

            $sanitized[$key] = $this->sanitize_setting_value($key, $value);
        }

        foreach (array_keys($renderedToggles) as $toggleKey) {
            if (!array_key_exists($toggleKey, $sanitized)) {
                $sanitized[$toggleKey] = false;
            }
        }

        return $sanitized;
    }

    /**
     * Parse raw settings payload into an array.
     *
     * @param mixed $settings Raw settings payload from request.
     * @return array Parsed settings payload.
     */
    private function parse_settings_payload($settings): array
    {
        if (is_string($settings) || is_array($settings)) {
            $settings = wp_unslash($settings);
        }

        if (is_string($settings)) {
            $parsed = [];
            parse_str($settings, $parsed);
            return is_array($parsed) ? $parsed : [];
        }

        return is_array($settings) ? $settings : [];
    }

    /**
     * Resolve editor option group from request or settings payload.
     *
     * @param mixed $rawSettings Raw settings payload (optional).
     * @return string Supported editor option group key.
     */
    private function resolve_editor_group($rawSettings = null): string
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Option group is determined based on settings payload or request parameter, no sensitive action being performed
        $requestGroup = isset($_REQUEST['option_group']) ? sanitize_key((string) wp_unslash($_REQUEST['option_group'])) : '';
        if ($this->is_supported_editor_group($requestGroup)) {
            return $requestGroup;
        }

        if ($rawSettings === null) {
            return Utils::GROUP_INVOICE_EDITOR;
        }

        $parsedSettings = $this->parse_settings_payload($rawSettings);
        foreach ($this->supported_editor_groups() as $group) {
            if (isset($parsedSettings[$group]) && is_array($parsedSettings[$group])) {
                return $group;
            }
        }

        return Utils::GROUP_INVOICE_EDITOR;
    }

    /**
     * Supported editor option groups.
     *
     * @return array<int, string>
     */
    private function supported_editor_groups(): array
    {
        return [
            Utils::GROUP_INVOICE_EDITOR,
            Utils::GROUP_PACKING_SLIP_EDITOR,
        ];
    }

    /**
     * Check whether an option group is a supported editor group.
     */
    private function is_supported_editor_group(string $group): bool
    {
        return in_array($group, $this->supported_editor_groups(), true);
    }

    /**
     * Sanitize a field value based on field key.
     *
     * @param string $key
     * @param mixed  $value
     * @return mixed
     */
    private function sanitize_setting_value(string $key, $value)
    {
        return SettingsSchema::sanitizeValue($key, $value);
    }

    /**
     * Extract settings from nested array structure created by form serialization.
     *
     * The FieldRenderer creates input names like: prefix[field_id]
     * After parse_str, this becomes: ['prefix' => ['field_id' => 'value']]
     * This method extracts the nested settings array.
     *
     * @param array $settings Parsed settings array
     * @return array Flattened settings array
     */
    private function extract_nested_settings(array $settings, string $optionKey = Utils::GROUP_INVOICE_EDITOR): array {
        $optionKey = sanitize_key($optionKey);
        if (!$this->is_supported_editor_group($optionKey)) {
            $optionKey = Utils::GROUP_INVOICE_EDITOR;
        }

        if (isset($settings[$optionKey]) && is_array($settings[$optionKey])) {
            return $settings[$optionKey];
        }

        foreach ($this->supported_editor_groups() as $group) {
            if (isset($settings[$group]) && is_array($settings[$group])) {
                return $settings[$group];
            }
        }

        // Return as-is if no nested structure found (flat field names).
        return $settings;
    }

}
