<?php
defined('ABSPATH') || exit;

/**
 * Registers WP admin menu pages and handles all admin POST actions
 * (settings save, connect/disconnect, mappings save, log clear).
 */
class JRA_Merchant_Admin {

    public function register(): void {
        add_action('admin_menu',    [$this, 'addMenuPages']);
        add_action('admin_notices', [$this, 'showNotices']);
        add_action('admin_enqueue_scripts', [$this, 'enqueueAssets']);

        // POST handlers via admin-post.php
        add_action('admin_post_jra_merchant_save_settings',      [$this, 'saveSettings']);
        add_action('admin_post_jra_merchant_disconnect',         [$this, 'disconnect']);
        add_action('admin_post_jra_merchant_register_gcp',       [$this, 'registerGcp']);
        add_action('admin_post_jra_merchant_reset_data_source',  [$this, 'resetDataSource']);
        add_action('admin_post_jra_merchant_save_attr_mappings',  [$this, 'saveAttrMappings']);
        add_action('admin_post_jra_merchant_save_term_mappings',  [$this, 'saveTermMappings']);
        add_action('admin_post_jra_merchant_clear_log',           [$this, 'clearLog']);
        add_action('admin_post_jra_merchant_preview_sync',        [$this, 'previewSync']);
        if ( jpffgmc_fs()->is__premium_only() ) {
            add_action('admin_post_jra_merchant_offer_id_map_import', [$this, 'importOfferIdMap']);
            add_action('admin_post_jra_merchant_offer_id_map_add',    [$this, 'addOfferIdMapRow']);
            add_action('admin_post_jra_merchant_offer_id_map_delete', [$this, 'deleteOfferIdMapRow']);
            add_action('admin_post_jra_merchant_generate_report',        [$this, 'generateReport']);
            add_action('admin_post_jra_merchant_report_csv_download',   [$this, 'handleReportCSVDownload']);
            add_action('admin_post_jra_merchant_save_google_cat_map',    [$this, 'saveGoogleCategoryMap']);
            add_action('admin_post_jra_merchant_save_custom_label_maps', [$this, 'saveCustomLabelMaps']);
            add_action('admin_post_jra_merchant_refresh_taxonomy',       [$this, 'refreshTaxonomy']);
            add_action('wp_ajax_jra_merchant_search_taxonomy',           [$this, 'ajaxSearchTaxonomy']);
        }
    }

    // -------------------------------------------------------------------------
    // Menu
    // -------------------------------------------------------------------------

    public function addMenuPages(): void {
        add_menu_page(
            'Google Merchant Feed',
            'Merchant Feed',
            'manage_woocommerce',
            'jra-merchant-feed',
            [$this, 'renderSyncPage'],
            'dashicons-google',
            58
        );

        add_submenu_page('jra-merchant-feed', 'Sync',        'Sync',        'manage_woocommerce', 'jra-merchant-feed',               [$this, 'renderSyncPage']);
        add_submenu_page('jra-merchant-feed', 'Settings',   'Settings',   'manage_woocommerce', 'jra-merchant-feed-settings',      [$this, 'renderSettingsPage']);
        add_submenu_page('jra-merchant-feed', 'Mappings',   'Mappings',   'manage_woocommerce', 'jra-merchant-feed-mappings',      [$this, 'renderMappingsPage']);
        add_submenu_page('jra-merchant-feed', 'Offer ID Map', 'Offer ID Map', 'manage_woocommerce', 'jra-merchant-feed-offer-id-map', [$this, 'renderOfferIdMapPage']);
        add_submenu_page('jra-merchant-feed', 'Reports',    'Reports',    'manage_woocommerce', 'jra-merchant-feed-reports',       [$this, 'renderReportsPage']);
        add_submenu_page('jra-merchant-feed', 'Log',        'Log',        'manage_woocommerce', 'jra-merchant-feed-log',           [$this, 'renderLogPage']);
    }

    public function enqueueAssets(string $hook): void {
        if (strpos($hook, 'jra-merchant-feed') === false) {
            return;
        }
        wp_enqueue_style('jra-merchant-feed', JRA_MERCHANT_URL . 'assets/admin.css', [], JRA_MERCHANT_VERSION);

        // Virtual footer script — no src, just a handle for view-level inline scripts.
        // Must be registered here (before wp_head) so wp_add_inline_script() calls from
        // view files (which run after wp_head) are captured and printed by wp_footer().
        wp_register_script('jra-admin-footer', false, ['jquery'], JRA_MERCHANT_VERSION, true);
        wp_enqueue_script('jra-admin-footer');

        if (str_contains($hook, 'jra-merchant-feed-mappings')) {
            wp_add_inline_script('jquery', 'var jraMappings = ' . wp_json_encode([
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'nonce'   => wp_create_nonce('jra_merchant_taxonomy_search'),
            ]) . ';');
            wp_add_inline_script('jra-admin-footer', <<<'JRAJS'
document.addEventListener('DOMContentLoaded',function(){
    document.addEventListener('click',function(e){
        if(e.target.classList.contains('jra-remove-row')){e.target.closest('tr').remove();}
    });
});
JRAJS);
        }
    }

    // -------------------------------------------------------------------------
    // Page renderers
    // -------------------------------------------------------------------------

    public function renderSyncPage(): void {
        $auth   = new JRA_Merchant_Auth();
        $logger = new JRA_Merchant_Logger();
        include JRA_MERCHANT_DIR . 'admin/views/sync.php';
    }

    public function renderSettingsPage(): void {
        $auth      = new JRA_Merchant_Auth();
        $isPremium = jpffgmc_fs()->is__premium_only();
        include JRA_MERCHANT_DIR . 'admin/views/settings.php';
    }

    private function renderUpgradeNotice(string $title, string $description, array $features): void {
        $upgradeUrl = jpffgmc_fs()->get_upgrade_url();
        echo '<div class="wrap jra-merchant">';
        echo '<h1>' . esc_html('Google Merchant Feed — ' . $title) . '</h1>';
        echo '<div class="jra-card" style="max-width:640px;">';
        echo '<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px;">';
        echo '<span style="font-size:28px;">🔒</span>';
        echo '<h2 style="margin:0;">' . esc_html($title) . ' <span style="font-size:13px;color:#9e2f7f;font-weight:normal;">Pro feature</span></h2>';
        echo '</div>';
        echo '<p style="color:#3c434a;">' . esc_html($description) . '</p>';
        echo '<ul style="list-style:disc;margin-left:20px;margin-bottom:20px;">';
        foreach ($features as $feature) {
            echo '<li style="margin-bottom:4px;">' . esc_html($feature) . '</li>';
        }
        echo '</ul>';
        echo '<a href="' . esc_url($upgradeUrl) . '" class="button button-primary button-large">Upgrade to Pro</a>';
        echo '</div></div>';
    }

    public function renderMappingsPage(): void {
        $isPremium           = jpffgmc_fs()->is__premium_only();
        $attrMappings        = (array) get_option('jra_merchant_attr_mappings', []);
        $excluded            = array_map('intval', (array) get_option('jra_merchant_excluded_categories', []));
        $wcAttributes        = wc_get_attribute_taxonomies();
        $activeAttr          = sanitize_text_field(wp_unslash($_GET['attr'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $googleAttrs         = ['color', 'size', 'material', 'pattern', 'age_group', 'gender'];
        $googleCategoryMap   = (array) get_option('jra_merchant_google_category_map', []);
        $customLabelTagMap   = (array) get_option('jra_merchant_custom_label_tag_map', []);
        $customLabelCatMap   = (array) get_option('jra_merchant_custom_label_cat_map', []);
        $allTags             = get_terms(['taxonomy' => 'product_tag', 'hide_empty' => false, 'orderby' => 'name']);
        $googleTaxonomy      = (array) get_option('jra_merchant_google_taxonomy', []);
        $taxonomyUpdatedAt   = (string) get_option('jra_merchant_google_taxonomy_updated', '');
        include JRA_MERCHANT_DIR . 'admin/views/mappings.php';
    }

    public function renderOfferIdMapPage(): void {
        if ( ! jpffgmc_fs()->is__premium_only() ) {
            $this->renderUpgradeNotice(
                'Offer ID Map',
                'Override the offer ID used for any product when pushing to Google Merchant Center. Useful when migrating from another feed tool and you need to preserve existing offer IDs to avoid losing review history and performance data.',
                [ 'Import offer ID overrides via CSV', 'Map any product to a custom Google offer ID', 'Preserves Google review history and Shopping performance on migration' ]
            );
            return;
        }
        $map     = new JRA_Merchant_Offer_Id_Map();
        $page    = max(1, absint(wp_unslash($_GET['paged'] ?? 1))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $perPage = 100;
        $offset  = ($page - 1) * $perPage;
        $rows    = $map->getAll($perPage, $offset);
        $total   = $map->count();
        $pages   = (int) ceil($total / $perPage);
        include JRA_MERCHANT_DIR . 'admin/views/offer-id-map.php';
    }

    public function renderLogPage(): void {
        $logger   = new JRA_Merchant_Logger();
        $filter   = sanitize_text_field(wp_unslash($_GET['status'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $page     = max(1, absint(wp_unslash($_GET['paged'] ?? 1))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        $perPage  = 100;
        $offset   = ($page - 1) * $perPage;
        $entries  = $logger->getRecent($perPage, $offset, $filter);
        $total    = $logger->countAll($filter);
        $pages    = (int) ceil($total / $perPage);
        include JRA_MERCHANT_DIR . 'admin/views/logs.php';
    }

    public function renderReportsPage(): void {
        if ( ! jpffgmc_fs()->is__premium_only() ) {
            $this->renderUpgradeNotice(
                'Reports',
                'Scan all your products in Google Merchant Center and get a full report of issues grouped by type — missing images, policy violations, disapproved products, and more. Export to CSV for bulk review.',
                [ 'Bulk issue scanning across all Merchant Center products', 'Issues grouped by type with affected product list', 'Export full report to CSV', 'Drill into any issue to see exactly which products are affected' ]
            );
            return;
        }
        $reportService = new JRA_Merchant_Report_Service();
        $reportData    = $reportService->getReportData();
        $isRunning     = $reportService->isReportRunning();
        $viewIssue     = isset($_GET['view']) ? sanitize_text_field(wp_unslash($_GET['view'])) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
        include JRA_MERCHANT_DIR . 'admin/views/reports.php';
    }

    // -------------------------------------------------------------------------
    // POST handlers
    // -------------------------------------------------------------------------

    public function saveSettings(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_settings')) {
            wp_die('Insufficient permissions.', 403);
        }

        // Parse markets: each entry is ['country' => 'GB', 'language' => 'en', 'enabled' => true]
        // POST shape: markets[0][country], markets[0][language], markets[0][enabled]
        $rawMarkets = (array) wp_unslash($_POST['markets'] ?? []); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- elements sanitized in loop below
        $markets    = [];
        foreach ($rawMarkets as $m) {
            $country  = strtoupper(sanitize_text_field($m['country']  ?? ''));
            $language = strtolower(sanitize_text_field($m['language'] ?? ''));
            $enabled  = !empty($m['enabled']);
            if ($country !== '' && $language !== '' && $enabled) {
                $markets[] = ['country' => $country, 'language' => $language];
            }
        }

        $auth = new JRA_Merchant_Auth();
        $auth->saveSettings(
            sanitize_text_field(wp_unslash($_POST['client_id']     ?? '')),
            sanitize_text_field(wp_unslash($_POST['client_secret'] ?? '')),
            [
                'merchant_id'             => sanitize_text_field(wp_unslash($_POST['merchant_id']   ?? '')),
                'store_name'              => sanitize_text_field(wp_unslash($_POST['store_name']     ?? '')),
                'currency'                => strtoupper(sanitize_text_field(wp_unslash($_POST['currency'] ?? 'GBP'))),
                'default_brand'           => sanitize_text_field(wp_unslash($_POST['default_brand'] ?? '')),
                'default_google_category' => sanitize_text_field(wp_unslash($_POST['default_google_category'] ?? '')),
                'low_stock_threshold'     => max(0, absint(wp_unslash($_POST['low_stock_threshold'] ?? 5))),
                'default_lead_time'       => max(0, absint(wp_unslash($_POST['default_lead_time'] ?? 0))),
                'group_title_prefix'      => !empty($_POST['group_title_prefix']),
                'due_date_meta_key'       => sanitize_key(wp_unslash($_POST['due_date_meta_key']     ?? '')),
                'due_date_meta_format'    => sanitize_text_field(wp_unslash($_POST['due_date_meta_format'] ?? '')),
                'lead_time_meta_key'      => sanitize_key(wp_unslash($_POST['lead_time_meta_key']  ?? '')),
                'bundle_price_meta_key'   => sanitize_key(wp_unslash($_POST['bundle_price_meta_key'] ?? '')),
                'markets'                 => $markets,
                'primary_market'          => strtoupper(sanitize_text_field(wp_unslash($_POST['primary_market'] ?? ''))),
            ]
        );

        $this->setNotice('success', 'Settings saved.');
        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-settings'));
        exit;
    }

    public function disconnect(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_disconnect')) {
            wp_die('Insufficient permissions.', 403);
        }

        (new JRA_Merchant_Auth())->disconnect();
        $this->setNotice('success', 'Disconnected from Google Merchant Center.');
        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-settings'));
        exit;
    }

    public function registerGcp(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_register_gcp')) {
            wp_die('Insufficient permissions.', 403);
        }

        try {
            (new JRA_Merchant_Auth())->registerGcpDeveloper();
            $this->setNotice('success', 'Google Cloud project registered with Merchant Center. Merchant API calls (including sync) should work within a few minutes.');
        } catch (Exception $e) {
            $this->setNotice('error', 'Registration failed: ' . $e->getMessage()); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
        }

        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-settings'));
        exit;
    }

    public function resetDataSource(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_reset_data_source')) {
            wp_die('Insufficient permissions.', 403);
        }

        (new JRA_Merchant_Auth())->clearDataSourceName();
        $this->setNotice('success', 'Data source cleared — the next sync will re-detect the correct API data source on your Merchant Center account.');

        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-settings'));
        exit;
    }

    public function saveAttrMappings(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_attr_mappings')) {
            wp_die('Insufficient permissions.', 403);
        }

        $googleAttrs = ['color', 'size', 'material', 'pattern', 'age_group', 'gender'];
        $mappings    = [];
        foreach ($googleAttrs as $attr) {
            $mappings[$attr] = sanitize_text_field(wp_unslash($_POST['mapping'][$attr] ?? ''));
        }
        update_option('jra_merchant_attr_mappings', $mappings, false);

        if ( jpffgmc_fs()->is__premium_only() ) {
            $excluded = array_map('absint', (array) wp_unslash($_POST['excluded_categories'] ?? [])); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- absint sanitizes each element
            update_option('jra_merchant_excluded_categories', $excluded, false);
        }

        $this->setNotice('success', 'Attribute mappings saved.');
        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-mappings'));
        exit;
    }

    public function saveTermMappings(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_term_mappings')) {
            wp_die('Insufficient permissions.', 403);
        }

        global $wpdb;
        $table      = $wpdb->prefix . 'jra_merchant_term_mappings';
        $googleAttr = sanitize_text_field(wp_unslash($_POST['google_attribute'] ?? ''));

        if (empty($googleAttr)) {
            wp_die('Missing google_attribute.', 400);
        }

        // Replace all rows for this google attribute
        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->delete($table, ['google_attribute' => $googleAttr], ['%s']);

        $localValues  = (array) wp_unslash($_POST['local_value']  ?? []); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- elements sanitized in loop below
        $googleValues = (array) wp_unslash($_POST['google_value'] ?? []); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- elements sanitized in loop below

        foreach ($localValues as $i => $localValue) {
            $local  = sanitize_text_field($localValue);
            $google = sanitize_text_field($googleValues[$i] ?? '');
            if ($local === '' || $google === '') {
                continue;
            }
            // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
            $wpdb->replace($table, [
                'google_attribute' => $googleAttr,
                'local_value'      => $local,
                'google_value'     => $google,
            ], ['%s', '%s', '%s']);
        }

        $this->setNotice('success', 'Term value mappings saved.');
        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-mappings'));
        exit;
    }

    public function previewSync(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_preview')) {
            wp_die('Insufficient permissions.', 403);
        }

        $limit  = max(1, min(50, absint(wp_unslash($_POST['preview_limit'] ?? 10))));
        $query  = new JRA_Merchant_Product_Query();
        $mapper = new JRA_Merchant_Product_Mapper();

        $allIds  = $query->getAllProductIds();
        $total   = count($allIds);
        $ids     = array_slice($allIds, 0, $limit);

        ['mapped' => $mapped, 'errors' => $mapErrors] = $mapper->mapBatch($ids);

        // Pre-compute offer ID resolutions keyed by effective offer ID so we can
        // annotate each preview row with local vs google offer ID information.
        $offerIdByEffective = []; // effective_offer_id => ['local' => ..., 'effective' => ..., 'is_mapped' => ...]
        foreach ($ids as $id) {
            $res = $mapper->resolveOfferId($id);
            $offerIdByEffective[$res['effective']] = $res;
        }

        // Extract only the fields we want to display — deduplicate by offer ID (mapBatch
        // returns one entry per market; for preview one row per product is enough).
        $rows    = [];
        $seenIds = [];
        foreach ($mapped as $product) {
            if (isset($seenIds[$product->getOfferId()])) {
                continue;
            }
            $seenIds[$product->getOfferId()] = true;

            $attrs        = $product->getProductAttributes();
            $priceObj     = $attrs?->getPrice();
            $salePriceObj = $attrs?->getSalePrice();
            $weightObj    = $attrs?->getShippingWeight();
            $availDate    = $attrs?->getAvailabilityDate();
            $gtins        = iterator_to_array($attrs?->getGtins() ?? []);
            $res          = $offerIdByEffective[$product->getOfferId()] ?? null;

            $rows[] = [
                'offer_id'          => $product->getOfferId(),
                'offer_id_local'    => $res ? $res['local']     : $product->getOfferId(),
                'offer_id_is_mapped'=> $res ? $res['is_mapped'] : false,
                'item_group_id'     => $attrs?->getItemGroupId() ?? '',
                'title'             => $attrs?->getTitle(),
                'description'       => mb_substr($attrs?->getDescription() ?? '', 0, 120),
                'price'             => $priceObj ? number_format($priceObj->getAmountMicros() / 1000000, 2) . ' ' . $priceObj->getCurrencyCode() : '—',
                'sale_price'        => $salePriceObj ? number_format($salePriceObj->getAmountMicros() / 1000000, 2) . ' ' . $salePriceObj->getCurrencyCode() : '',
                'availability'      => $attrs ? strtolower(\Google\Shopping\Merchant\Products\V1\Availability::name($attrs->getAvailability())) : '',
                'availability_date' => $availDate ? $availDate->toDateTime()->format('Y-m-d') : '',
                'link'              => $attrs?->getLink(),
                'image_link'        => $attrs?->getImageLink(),
                'has_image'         => (string)$attrs?->getImageLink() !== '',
                'additional_images' => count(iterator_to_array($attrs?->getAdditionalImageLinks() ?? [])),
                'brand'             => $attrs?->getBrand() ?? '',
                'mpn'               => $attrs?->getMpn() ?? '',
                'gtin'              => !empty($gtins) ? implode(', ', $gtins) : '',
                'identifier_exists' => $attrs?->getIdentifierExists() ? 'yes' : 'no',
                'condition'         => $attrs ? strtolower(\Google\Shopping\Merchant\Products\V1\Condition::name($attrs->getCondition())) : '',
                'weight_g'          => $weightObj ? $weightObj->getValue() . ' ' . $weightObj->getUnit() : '',
                'color'             => $attrs?->getColor() ?? '',
                'size'              => $attrs?->getSize() ?? '',
                'gender'            => $attrs ? strtolower(\Google\Shopping\Merchant\Products\V1\Gender::name($attrs->getGender())) : '',
                'age_group'         => $attrs ? strtolower(\Google\Shopping\Merchant\Products\V1\AgeGroup::name($attrs->getAgeGroup())) : '',
            ];
        }

        set_transient('jra_merchant_preview', [
            'rows'    => $rows,
            'errors'  => $mapErrors,
            'total'   => $total,
            'limit'   => $limit,
            'ran_at'  => current_time('mysql'),
        ], 300); // 5 minutes

        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed&preview=1'));
        exit;
    }

    public function clearLog(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_clear_log')) {
            wp_die('Insufficient permissions.', 403);
        }

        (new JRA_Merchant_Logger())->clear();
        $this->setNotice('success', 'Log cleared.');
        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-log'));
        exit;
    }

    public function importOfferIdMap(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_offer_id_map')) {
            wp_die('Insufficient permissions.', 403);
        }

        $file = $_FILES['offer_id_map_csv'] ?? null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
        if (!$file || $file['error'] !== UPLOAD_ERR_OK || !is_uploaded_file($file['tmp_name'])) {
            $this->setNotice('error', 'No file uploaded or upload error.');
            wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-offer-id-map'));
            exit;
        }

        $csv     = file_get_contents($file['tmp_name']);
        $note    = sanitize_text_field(wp_unslash($_POST['import_note'] ?? 'CSV import ' . current_time('Y-m-d')));
        $map     = new JRA_Merchant_Offer_Id_Map();
        $result  = $map->importCsv($csv, $note);

        if ($result['parse_error']) {
            $this->setNotice('error', 'CSV parse error: ' . $result['parse_error']);
        } else {
            $msg = sprintf(
                'Imported %d row(s)%s.',
                $result['imported'],
                $result['skipped'] ? ', skipped ' . $result['skipped'] . ' blank' : ''
            );
            if (!empty($result['errors'])) {
                $msg .= ' Errors: ' . implode('; ', $result['errors']);
                $this->setNotice('error', $msg);
            } else {
                $this->setNotice('success', $msg);
            }
        }

        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-offer-id-map'));
        exit;
    }

    public function addOfferIdMapRow(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_offer_id_map')) {
            wp_die('Insufficient permissions.', 403);
        }

        $local  = sanitize_text_field(wp_unslash($_POST['local_offer_id']  ?? ''));
        $google = sanitize_text_field(wp_unslash($_POST['google_offer_id'] ?? ''));
        $note   = sanitize_text_field(wp_unslash($_POST['note']            ?? ''));

        if ($local === '' || $google === '') {
            $this->setNotice('error', 'Both local and Google offer IDs are required.');
        } else {
            $map = new JRA_Merchant_Offer_Id_Map();
            $err = $map->add($local, $google, $note);
            if ($err === null) {
                $this->setNotice('success', "Mapping added: {$local} → {$google}");
            } else {
                $this->setNotice('error', "Could not save mapping: {$err}");
            }
        }

        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-offer-id-map'));
        exit;
    }

    public function deleteOfferIdMapRow(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_offer_id_map_delete')) {
            wp_die('Insufficient permissions.', 403);
        }

        $local = sanitize_text_field(wp_unslash($_POST['local_offer_id'] ?? ''));
        if ($local === '') {
            wp_die('Missing local_offer_id.', 400);
        }

        $map = new JRA_Merchant_Offer_Id_Map();
        $map->delete($local);
        $this->setNotice('success', "Mapping deleted: {$local}. The next sync will push this product under its auto-generated offer ID — Google will treat it as a new product.");
        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-offer-id-map'));
        exit;
    }

    public function generateReport(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_generate_report')) {
            wp_die('Insufficient permissions.', 403);
        }

        $reportService = new JRA_Merchant_Report_Service();

        // Check if report is already running
        if ($reportService->isReportRunning()) {
            $this->setNotice('warning', 'A report is already running. Please wait for it to complete.');
            wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-reports'));
            exit;
        }

        // Queue background scan
        if (function_exists('as_enqueue_async_action')) {
            as_enqueue_async_action('jra_merchant_run_report_scan', [], 'jra-merchant-feed');
        } else {
            wp_schedule_single_event(time() + 5, 'jra_merchant_run_report_scan');
            spawn_cron();
        }

        // Mark as running
        set_transient('jra_merchant_report_data', [
            'generated_at'  => current_time('mysql', true),
            'status'        => 'running',
            'error_message' => null,
            'pages_scanned' => 0,
            'total_issues'  => 0,
            'issues'        => [],
        ], 3600);

        $this->setNotice('info', 'Report generation started. It will run in the background. Refresh this page to check progress.');
        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-reports'));
        exit;
    }

    public function handleReportCSVDownload(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_report_csv')) {
            wp_die('Insufficient permissions.', 403);
        }

        $reportService = new JRA_Merchant_Report_Service();
        $filter        = sanitize_text_field(wp_unslash($_GET['filter'] ?? ''));
        $csv           = $reportService->exportCSV($filter ? $filter : null);

        if (!$csv) {
            wp_die('No report data available.');
        }

        header('Content-Type: text/csv; charset=utf-8');
        header('Content-Disposition: attachment; filename="jra-merchant-report-' . current_time('Y-m-d-His') . '.csv"');
        echo $csv; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw CSV download, not HTML
        exit;
    }

    public function ajaxSearchTaxonomy(): void {
        check_ajax_referer('jra_merchant_taxonomy_search');
        if (!current_user_can('manage_woocommerce')) {
            wp_send_json_error('Insufficient permissions.');
            return;
        }

        $query    = strtolower(sanitize_text_field(wp_unslash($_GET['q'] ?? '')));
        $taxonomy = (array) get_option('jra_merchant_google_taxonomy', []);

        if (empty($taxonomy)) {
            $taxonomy = $this->downloadGoogleTaxonomy();
        }

        $results = [];
        foreach ($taxonomy as $id => $label) {
            if ($query === '' || str_contains(strtolower($label), $query)) {
                $results[] = ['id' => (string) $id, 'text' => $label . ' [' . $id . ']'];
                if (count($results) >= 25) {
                    break;
                }
            }
        }

        wp_send_json(['results' => $results]);
    }

    public function refreshTaxonomy(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_refresh_taxonomy')) {
            wp_die('Insufficient permissions.', 403);
        }

        $taxonomy = $this->downloadGoogleTaxonomy();

        if (empty($taxonomy)) {
            $this->setNotice('error', 'Failed to download Google taxonomy. Check the server can reach google.com.');
        } else {
            $this->setNotice('success', sprintf('Google taxonomy refreshed — %s categories loaded.', number_format(count($taxonomy))));
        }

        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-mappings'));
        exit;
    }

    private function downloadGoogleTaxonomy(): array {
        $response = wp_remote_get(
            'https://www.google.com/basepages/producttype/taxonomy-with-ids.en-GB.txt',
            ['timeout' => 20]
        );

        if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
            return [];
        }

        $body     = wp_remote_retrieve_body($response);
        $taxonomy = [];

        foreach (explode("\n", $body) as $line) {
            $line = trim($line);
            if ($line === '' || str_starts_with($line, '#')) {
                continue;
            }
            // Format: "3237 - Animals & Pet Supplies > Live Animals"
            if (preg_match('/^(\d+)\s+-\s+(.+)$/', $line, $m)) {
                $taxonomy[(int) $m[1]] = trim($m[2]);
            }
        }

        if (!empty($taxonomy)) {
            update_option('jra_merchant_google_taxonomy', $taxonomy, false);
            update_option('jra_merchant_google_taxonomy_updated', current_time('mysql'), false);
        }

        return $taxonomy;
    }

    public function saveGoogleCategoryMap(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_google_cat_map')) {
            wp_die('Insufficient permissions.', 403);
        }

        $termIds     = (array) wp_unslash($_POST['cat_term_id']     ?? []); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- elements sanitized in loop below
        $taxonomyIds = (array) wp_unslash($_POST['cat_taxonomy_id'] ?? []); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- elements sanitized in loop below
        $map         = [];
        foreach ($termIds as $i => $termId) {
            $termId     = (int) $termId;
            $taxonomyId = sanitize_text_field(wp_unslash($taxonomyIds[$i] ?? ''));
            if ($termId > 0 && $taxonomyId !== '') {
                $map[$termId] = $taxonomyId;
            }
        }
        update_option('jra_merchant_google_category_map', $map, false);

        $this->setNotice('success', 'Google category mappings saved.');
        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-mappings'));
        exit;
    }

    public function saveCustomLabelMaps(): void {
        if (!current_user_can('manage_woocommerce') || !check_admin_referer('jra_merchant_custom_label_maps')) {
            wp_die('Insufficient permissions.', 403);
        }

        // Tag map: tag slug → label value
        $tagSlugs  = (array) wp_unslash($_POST['label_tag_slug']  ?? []); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- elements sanitized in loop below
        $tagValues = (array) wp_unslash($_POST['label_tag_value'] ?? []); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- elements sanitized in loop below
        $tagMap    = [];
        foreach ($tagSlugs as $i => $slug) {
            $slug  = sanitize_text_field(wp_unslash($slug));
            $value = sanitize_text_field(wp_unslash($tagValues[$i] ?? ''));
            if ($slug !== '' && $value !== '') {
                $tagMap[$slug] = $value;
            }
        }
        update_option('jra_merchant_custom_label_tag_map', $tagMap, false);

        // Category map: term ID → label value
        $catIds    = (array) wp_unslash($_POST['label_cat_id']    ?? []); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- elements sanitized in loop below
        $catValues = (array) wp_unslash($_POST['label_cat_value'] ?? []); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- elements sanitized in loop below
        $catMap    = [];
        foreach ($catIds as $i => $catId) {
            $catId = (int) $catId;
            $value = sanitize_text_field(wp_unslash($catValues[$i] ?? ''));
            if ($catId > 0 && $value !== '') {
                $catMap[$catId] = $value;
            }
        }
        update_option('jra_merchant_custom_label_cat_map', $catMap, false);

        $this->setNotice('success', 'Custom label mappings saved.');
        wp_safe_redirect(admin_url('admin.php?page=jra-merchant-feed-mappings'));
        exit;
    }

    // -------------------------------------------------------------------------
    // Notices
    // -------------------------------------------------------------------------

    public function showNotices(): void {
        $notice = get_transient('jra_merchant_notice');
        if (!$notice) {
            return;
        }
        delete_transient('jra_merchant_notice');
        $class = $notice['type'] === 'success' ? 'notice-success' : 'notice-error';
        echo '<div class="notice ' . esc_attr($class) . ' is-dismissible"><p>' . esc_html($notice['message']) . '</p></div>';
    }

    private function setNotice(string $type, string $message): void {
        set_transient('jra_merchant_notice', compact('type', 'message'), 60);
    }
}
