<?php
/**
 * Auto Labelling admin API bridge (/label endpoint).
 */

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

add_action('wp_ajax_askagento_ai_labelling_action', 'askagento_ai_labelling_ajax_action');
add_action('rest_api_init', 'askagento_ai_labelling_register_rest_routes');

/**
 * Map API /label action + mode request.
 */
function askagento_ai_labelling_ajax_action() {
    if (!check_ajax_referer('askagento_admin', 'nonce', false)) {
        wp_send_json_error(['message' => __('Security check failed. Please reload the page.', 'askagento')], 403);
    }
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Permission denied.', 'askagento')], 403);
    }

    if (!function_exists('askagento_get_install_settings') || !function_exists('askagento_install_api_is_enabled')) {
        wp_send_json_error(['message' => __('Labelling backend is unavailable.', 'askagento')], 500);
    }

    if (!askagento_install_api_is_enabled()) {
        wp_send_json_error(['message' => __('Installation API not enabled.', 'askagento')], 400);
    }

    if (function_exists('askagento_feature_is_enabled') && !askagento_feature_is_enabled('auto_labels')) {
        wp_send_json_error(['message' => __('Auto Labels is not available on your current plan. Please upgrade to unlock this feature.', 'askagento'), 'code' => 'feature_not_available'], 403);
    }

    $mode = isset($_POST['mode']) ? sanitize_key(wp_unslash($_POST['mode'])) : 'attributes';
    if (!in_array($mode, ['attributes', 'categories', 'tags'], true)) {
        $mode = 'attributes';
    }

    $label_action = isset($_POST['label_action']) ? sanitize_key(wp_unslash($_POST['label_action'])) : '';
    if (!in_array($label_action, ['analyze', 'generate', 'upload', 'export'], true)) {
        wp_send_json_error(['message' => __('Invalid labelling action.', 'askagento')], 400);
    }

    $settings = askagento_get_install_settings();
    $site_id = trim((string) get_option('askagento_install_site_id', ''));
    $api_key = isset($settings['api_key']) ? trim((string) $settings['api_key']) : '';
    $api_url = rtrim((string) ($settings['api_url'] ?? ''), '/');

    if ($site_id === '' || $api_key === '' || $api_url === '') {
        wp_send_json_error(['message' => __('Complete Askagento installation first (site ID + Discovery API key).', 'askagento')], 400);
    }

    $payload = [
        'mode' => $mode,
        'action' => $label_action,
    ];

    if ($label_action === 'generate') {
        $payload['sources'] = 'images_and_text';
        $canonical_key = 'canonical_attributes';
        if ($mode === 'categories') {
            $canonical_key = 'canonical_categories';
        } elseif ($mode === 'tags') {
            $canonical_key = 'canonical_tags';
        }

        $canonical_values = [];
        if (isset($_POST[$canonical_key])) {
            if (is_array($_POST[$canonical_key])) {
                foreach (array_map('sanitize_text_field', (array) wp_unslash($_POST[$canonical_key])) as $name) {
                    if ($name !== '') {
                        $canonical_values[] = $name;
                    }
                }
            } else {
                $txt = sanitize_text_field(wp_unslash($_POST[$canonical_key]));
                if ($txt !== '') {
                    foreach (preg_split('/[\n,]/', $txt) as $part) {
                        $name = sanitize_text_field((string) $part);
                        if ($name !== '') {
                            $canonical_values[] = $name;
                        }
                    }
                }
            }
        }
        if (!empty($canonical_values)) {
            $payload[$canonical_key] = array_values(array_unique($canonical_values));
        }
    }

    if ($label_action === 'upload') {
        askagento_ai_labelling_ajax_upload_two_step($api_url, $site_id, $api_key, $mode);
    }

    $remote = askagento_ai_labelling_remote_label_post($api_url, $site_id, $api_key, $payload);
    askagento_ai_labelling_send_remote_label_result($remote);
}

/**
 * POST /label on the Discovery API.
 *
 * @param array<string, mixed> $payload
 * @return array|\WP_Error
 */
function askagento_ai_labelling_remote_label_post($api_url, $site_id, $api_key, array $payload, $timeout = 80) {
    $remote = wp_remote_post(
        rtrim((string) $api_url, '/') . '/label',
        [
            'method' => 'POST',
            'timeout' => (int) $timeout,
            'headers' => [
                'Content-Type'        => 'application/json',
                'X-Askagento-Site-Id' => $site_id,
                'X-Askagento-Api-Key' => $api_key,
            ],
            'body' => wp_json_encode($payload),
        ]
    );

    if (is_wp_error($remote)) {
        return $remote;
    }

    return [
        'status' => (int) wp_remote_retrieve_response_code($remote),
        'body' => json_decode((string) wp_remote_retrieve_body($remote), true),
    ];
}

/**
 * @param array|\WP_Error $remote
 */
function askagento_ai_labelling_send_remote_label_result($remote) {
    if (is_wp_error($remote)) {
        wp_send_json_error(['message' => $remote->get_error_message()], 502);
    }

    $status = (int) ($remote['status'] ?? 0);
    $body = isset($remote['body']) && is_array($remote['body']) ? $remote['body'] : null;

    if ($status >= 200 && $status < 300 && is_array($body)) {
        wp_send_json_success(
            function_exists('askagento_enrich_admin_response_token_usage')
                ? askagento_enrich_admin_response_token_usage($body)
                : $body
        );
    }

    $error = '';
    if (is_array($body) && !empty($body['error'])) {
        $error = (string) $body['error'];
    }
    if ($error === '') {
        $error = __('Labelling request failed.', 'askagento');
    } elseif (function_exists('askagento_remote_error_to_message')) {
        $error = askagento_remote_error_to_message($error);
    }

    wp_send_json_error(['message' => $error], $status > 0 ? $status : 500);
}

/**
 * Upload flow: apply labels in WooCommerce locally, then persist catalogue via API.
 */
function askagento_ai_labelling_ajax_upload_two_step($api_url, $site_id, $api_key, $mode) {
    $export_remote = askagento_ai_labelling_remote_label_post(
        $api_url,
        $site_id,
        $api_key,
        [
            'mode' => $mode,
            'action' => 'export',
        ],
        80
    );

    if (is_wp_error($export_remote)) {
        wp_send_json_error(['message' => $export_remote->get_error_message()], 502);
    }

    $export_status = (int) ($export_remote['status'] ?? 0);
    $export_body = isset($export_remote['body']) && is_array($export_remote['body']) ? $export_remote['body'] : null;

    if ($export_status < 200 || $export_status >= 300 || !is_array($export_body)) {
        $error = __('Could not load generated labels for upload.', 'askagento');
        if (is_array($export_body) && !empty($export_body['error'])) {
            $error = (string) $export_body['error'];
            if (function_exists('askagento_remote_error_to_message')) {
                $error = askagento_remote_error_to_message($error);
            }
        }
        wp_send_json_error(['message' => $error], $export_status > 0 ? $export_status : 502);
    }

    $export_rows = isset($export_body['products']) && is_array($export_body['products']) ? $export_body['products'] : [];
    $shop_products = askagento_ai_labelling_shop_upload_rows_from_export($mode, $export_rows);
    if (empty($shop_products)) {
        wp_send_json_error(
            ['message' => __('No generated labels found. Run generate first.', 'askagento')],
            400
        );
    }

    $shop_response = askagento_ai_labelling_invoke_local_upload($site_id, $api_key, $mode, $shop_products);
    $shop_status = askagento_ai_labelling_rest_response_to_shop_status($shop_response);

    $catalogue_remote = askagento_ai_labelling_remote_label_post(
        $api_url,
        $site_id,
        $api_key,
        [
            'mode' => $mode,
            'action' => 'upload',
        ],
        80
    );

    if (is_wp_error($catalogue_remote)) {
        wp_send_json_error(
            [
                'message' => $catalogue_remote->get_error_message(),
                'shop' => $shop_status,
            ],
            502
        );
    }

    $catalogue_status = (int) ($catalogue_remote['status'] ?? 0);
    $catalogue_body = isset($catalogue_remote['body']) && is_array($catalogue_remote['body'])
        ? $catalogue_remote['body']
        : null;

    if ($catalogue_status < 200 || $catalogue_status >= 300 || !is_array($catalogue_body)) {
        $error = __('Catalogue upload failed after applying labels in the shop.', 'askagento');
        if (is_array($catalogue_body) && !empty($catalogue_body['error'])) {
            $error = (string) $catalogue_body['error'];
            if (function_exists('askagento_remote_error_to_message')) {
                $error = askagento_remote_error_to_message($error);
            }
        }
        wp_send_json_error(
            [
                'message' => $error,
                'shop' => $shop_status,
            ],
            $catalogue_status > 0 ? $catalogue_status : 502
        );
    }

    $catalogue_body['shop'] = $shop_status;
    wp_send_json_success(
        function_exists('askagento_enrich_admin_response_token_usage')
            ? askagento_enrich_admin_response_token_usage($catalogue_body)
            : $catalogue_body
    );
}

/**
 * Build labelling-upload product rows from an export response.
 *
 * @param array<int, array<string, mixed>> $export_rows
 * @return array<int, array<string, mixed>>
 */
function askagento_ai_labelling_shop_upload_rows_from_export($mode, array $export_rows) {
    $out = [];
    foreach ($export_rows as $row) {
        if (!is_array($row)) {
            continue;
        }
        $id = isset($row['id']) ? (int) $row['id'] : 0;
        if ($id <= 0) {
            continue;
        }
        if ($mode === 'attributes') {
            $attrs = askagento_ai_labelling_normalize_attribute_map($row['generated_attributes'] ?? []);
            if (!empty($attrs)) {
                $out[] = ['id' => $id, 'attributes' => $attrs];
            }
            continue;
        }
        if ($mode === 'categories') {
            $cats = askagento_ai_labelling_normalize_label_list($row['generated_categories'] ?? []);
            if (!empty($cats)) {
                $out[] = ['id' => $id, 'categories' => $cats];
            }
            continue;
        }
        $tags = askagento_ai_labelling_normalize_label_list($row['generated_tags'] ?? []);
        if (!empty($tags)) {
            $out[] = ['id' => $id, 'tags' => $tags];
        }
    }

    return $out;
}

/**
 * Invoke labelling-upload in-process (no HTTP callback to the shop).
 *
 * @param array<int, array<string, mixed>> $products
 * @return \WP_REST_Response
 */
function askagento_ai_labelling_invoke_local_upload($site_id, $api_key, $mode, array $products) {
    $request = new \WP_REST_Request('POST', '/askagento/v1/labelling-upload');
    $request->set_header('Content-Type', 'application/json');
    $request->set_header('X-Askagento-Site-Id', $site_id);
    $request->set_header('X-Askagento-Api-Key', $api_key);
    $request->set_body(
        wp_json_encode(
            [
                'site_id' => $site_id,
                'mode' => $mode,
                'action' => 'upload',
                'products' => $products,
            ]
        )
    );

    return askagento_ai_labelling_rest_upload($request);
}

/**
 * @param \WP_REST_Response $response
 * @return array<string, mixed>
 */
function askagento_ai_labelling_rest_response_to_shop_status($response) {
    if (!($response instanceof \WP_REST_Response)) {
        return ['status' => 'error', 'reason' => 'invalid_response'];
    }

    $code = (int) $response->get_status();
    $data = $response->get_data();
    if (!is_array($data)) {
        return [
            'status' => 'error',
            'shop_status_code' => $code,
        ];
    }

    $status = ($code >= 200 && $code < 300) || $code === 422 ? 'uploaded' : 'error';

    return array_merge(
        ['status' => $status, 'shop_status_code' => $code],
        $data
    );
}

/**
 * Register REST route used for in-process WooCommerce labelling apply.
 */
function askagento_ai_labelling_register_rest_routes() {
    register_rest_route(
        'askagento/v1',
        '/labelling-upload',
        [
            'methods' => 'POST',
            'callback' => 'askagento_ai_labelling_rest_upload',
            'permission_callback' => '__return_true',
        ]
    );
}

/**
 * Constant-time string compare helper.
 */
function askagento_ai_labelling_const_time_eq($left, $right) {
    if (!is_string($left) || !is_string($right)) {
        return false;
    }
    if ($left === '' || $right === '') {
        return false;
    }
    if (function_exists('hash_equals')) {
        return hash_equals($left, $right);
    }
    if (strlen($left) !== strlen($right)) {
        return false;
    }
    $res = 0;
    $len = strlen($left);
    for ($i = 0; $i < $len; $i++) {
        $res |= (ord($left[$i]) ^ ord($right[$i]));
    }
    return $res === 0;
}

/**
 * Normalize incoming generated attribute map to [name => [values...]].
 *
 * @param mixed $raw
 * @return array<string, array<int, string>>
 */
function askagento_ai_labelling_normalize_attribute_map($raw) {
    if (!is_array($raw)) {
        return [];
    }

    $out = [];
    foreach ($raw as $name => $vals) {
        $attr_name = sanitize_text_field((string) $name);
        if ($attr_name === '') {
            continue;
        }

        $tmp = [];
        if (is_array($vals)) {
            foreach ($vals as $v) {
                $vv = sanitize_text_field((string) $v);
                if ($vv !== '') {
                    $tmp[] = $vv;
                }
            }
        } else {
            $vv = sanitize_text_field((string) $vals);
            if ($vv !== '') {
                $tmp[] = $vv;
            }
        }

        $tmp = array_values(array_unique($tmp));
        if (!empty($tmp)) {
            $out[$attr_name] = $tmp;
        }
    }

    return $out;
}

/**
 * Normalize incoming generated label list to [name, ...].
 *
 * @param mixed $raw
 * @return array<int, string>
 */
function askagento_ai_labelling_normalize_label_list($raw) {
    if (!is_array($raw)) {
        return [];
    }

    $out = [];
    $seen = [];
    foreach ($raw as $item) {
        $label = sanitize_text_field((string) $item);
        if ($label === '') {
            continue;
        }
        $key = strtolower($label);
        if (isset($seen[$key])) {
            continue;
        }
        $seen[$key] = true;
        $out[] = $label;
    }

    return $out;
}

/**
 * Slug for a global WooCommerce attribute row (without pa_ prefix, max 28 chars).
 *
 * @param string $label Human-readable attribute label from generated data.
 * @return string
 */
function askagento_ai_labelling_wc_attribute_slug_from_label($label) {
    $base = sanitize_title((string) $label);
    if (function_exists('wc_sanitize_taxonomy_name')) {
        $base = wc_sanitize_taxonomy_name(str_replace('.', '-', $base));
    }
    $base = preg_replace('/^pa_/', '', (string) $base);
    $base = trim($base, '-');
    if ($base === '') {
        $base = 'attr-' . substr(md5((string) $label), 0, 8);
    }
    // woocommerce_attribute_taxonomies.attribute_name is varchar(28).
    if (strlen($base) > 28) {
        $base = substr($base, 0, 28);
    }
    return $base;
}

/**
 * Ensure a global product attribute exists and its pa_* taxonomy is registered.
 *
 * @param string $attribute_label Attribute name from API / LLM (e.g. "Color").
 * @return array{taxonomy: string, attribute_id: int}|null
 */
function askagento_ai_labelling_ensure_global_attribute_taxonomy($attribute_label, &$error_message = null) {
    $error_message = null;
    if (!function_exists('wc_attribute_taxonomy_name') || !function_exists('wc_attribute_taxonomy_id_by_name')) {
        $error_message = 'woocommerce_attribute_functions_missing';

        return null;
    }

    $slug = askagento_ai_labelling_wc_attribute_slug_from_label($attribute_label);
    if ($slug === '') {
        $error_message = 'invalid_attribute_slug';

        return null;
    }

    $taxonomy = wc_attribute_taxonomy_name($slug);
    $attribute_id = (int) wc_attribute_taxonomy_id_by_name($taxonomy);

    if ($attribute_id <= 0 && function_exists('wc_create_attribute')) {
        $created = wc_create_attribute(
            [
                'name' => $attribute_label,
                'slug' => $slug,
                'type' => 'select',
                'order_by' => 'menu_order',
                'has_archives' => false,
            ]
        );
        if (is_wp_error($created)) {
            $error_message = $created->get_error_message();

            return null;
        }
        $attribute_id = (int) $created;
        askagento_ai_labelling_bootstrap_product_attribute_taxonomies();
        askagento_ai_labelling_register_pa_taxonomy_if_missing($taxonomy);
    }

    if ($attribute_id <= 0) {
        if ($error_message === null) {
            $error_message = 'attribute_id_unresolved';
        }

        return null;
    }

    if (!taxonomy_exists($taxonomy)) {
        askagento_ai_labelling_bootstrap_product_attribute_taxonomies();
        askagento_ai_labelling_register_pa_taxonomy_if_missing($taxonomy);
    }

    return ['taxonomy' => $taxonomy, 'attribute_id' => $attribute_id];
}

/**
 * Attribute labels that map to the product Brands taxonomy (not pa_*).
 *
 * Matches WooCommerce core Brands and typical canonical names from Auto Label.
 *
 * @return array<int, string>
 */
function askagento_ai_labelling_brand_attribute_labels() {
    return apply_filters(
        'askagento_ai_labelling_brand_attribute_labels',
        ['Brand', 'Brands']
    );
}

/**
 * Whether this attribute key should assign WooCommerce Brands terms.
 *
 * @param string $label Incoming attribute name from generated data.
 */
function askagento_ai_labelling_is_brand_attribute_label($label) {
    $needle = strtolower(trim((string) $label));
    if ($needle === '') {
        return false;
    }
    foreach (askagento_ai_labelling_brand_attribute_labels() as $candidate) {
        if (strtolower(trim((string) $candidate)) === $needle) {
            return true;
        }
    }

    return false;
}

/**
 * Taxonomy used for product brands (WooCommerce Brands and common extensions).
 *
 * @return string|null
 */
function askagento_ai_labelling_get_product_brand_taxonomy() {
    $resolved = null;

    $candidates = [
        'product_brand',
        'product_brands',
        'yith_product_brand',
        'pwb-brand',
        'berocket_brand',
    ];
    $candidates = apply_filters('askagento_ai_labelling_product_brand_taxonomy_candidates', $candidates);

    foreach ($candidates as $tax) {
        $tax = sanitize_key((string) $tax);
        if ($tax === '') {
            continue;
        }
        if (taxonomy_exists($tax) && is_object_in_taxonomy('product', $tax)) {
            $resolved = $tax;
            break;
        }
    }

    if ($resolved === null) {
        foreach (get_object_taxonomies('product', 'names') as $tax) {
            if (!is_string($tax) || $tax === '') {
                continue;
            }
            if (stripos($tax, 'brand') !== false && taxonomy_exists($tax)) {
                $resolved = $tax;
                break;
            }
        }
    }

    return apply_filters('askagento_ai_labelling_product_brand_taxonomy', $resolved);
}

/**
 * Reload WooCommerce global attribute definitions so pa_* taxonomies are registered.
 *
 * Needed during server-to-server uploads when new attributes were just created.
 */
function askagento_ai_labelling_bootstrap_product_attribute_taxonomies() {
    delete_transient('wc_attribute_taxonomies');
    if (class_exists('\WC_Post_Types') && method_exists('\WC_Post_Types', 'register_taxonomies')) {
        \WC_Post_Types::register_taxonomies();
    } elseif (class_exists('\WC_Post_Data') && method_exists('\WC_Post_Data', 'register_product_taxonomies')) {
        \WC_Post_Data::register_product_taxonomies();
    }
}

/**
 * Register a single global product-attribute taxonomy (pa_*) if WooCommerce has not done so yet.
 *
 * WC_Post_Types::register_taxonomies() only runs early on init; new attributes created during
 * REST uploads need explicit register_taxonomy() before wp_insert_term().
 *
 * @param string $taxonomy Full taxonomy name e.g. pa_material.
 * @return bool True if taxonomy_exists after this call.
 */
function askagento_ai_labelling_register_pa_taxonomy_if_missing($taxonomy) {
    if (!is_string($taxonomy) || $taxonomy === '') {
        return false;
    }
    if (taxonomy_exists($taxonomy)) {
        return true;
    }
    if (!preg_match('/^pa_/i', $taxonomy)) {
        return false;
    }
    $slug_in = strtolower(preg_replace('/^pa_/i', '', $taxonomy));
    if ($slug_in === '' || !function_exists('wc_get_attribute_taxonomies') || !function_exists('wc_attribute_taxonomy_name')) {
        return false;
    }

    $tax_row = null;
    foreach ([false, true] as $flush_retry) {
        if ($flush_retry) {
            delete_transient('wc_attribute_taxonomies');
        }
        foreach (wc_get_attribute_taxonomies() as $tax) {
            if (!is_object($tax)) {
                continue;
            }
            $row_slug = isset($tax->attribute_name) ? strtolower((string) $tax->attribute_name) : '';
            if ($row_slug === $slug_in) {
                $tax_row = $tax;
                break 2;
            }
        }
    }
    if ($tax_row === null) {
        return false;
    }

    $name = wc_attribute_taxonomy_name($tax_row->attribute_name);
    if ($name === '') {
        return false;
    }

    $label = !empty($tax_row->attribute_label) ? (string) $tax_row->attribute_label : (string) $tax_row->attribute_name;
    $public = isset($tax_row->attribute_public) ? absint($tax_row->attribute_public) : 1;

    $taxonomy_data = [
        'hierarchical' => false,
        'update_count_callback' => '_update_post_term_count',
        'labels' => [
            /* translators: %s: attribute label */
            'name' => sprintf(__('Product %s', 'askagento'), $label),
            'singular_name' => $label,
        ],
        'show_ui' => false,
        'show_in_quick_edit' => false,
        'show_in_nav_menus' => false,
        'meta_box_cb' => false,
        'query_var' => 1 === $public,
        'capabilities' => [
            'manage_terms' => 'manage_product_terms',
            'edit_terms' => 'edit_product_terms',
            'delete_terms' => 'delete_product_terms',
            'assign_terms' => 'assign_product_terms',
        ],
        'rewrite' => false,
        'public' => 1 === $public,
        'show_in_rest' => true,
    ];

    // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WooCommerce core filter names, applied verbatim so attribute taxonomies registered here stay filterable by the same hooks WooCommerce fires for its own. An askagento_ prefix would make them invisible to WooCommerce and third-party code.
    register_taxonomy(
        $name,
        apply_filters('woocommerce_taxonomy_objects_' . $name, ['product']),
        apply_filters('woocommerce_taxonomy_args_' . $name, $taxonomy_data)
    );
    // phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

    // phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- $wc_product_attributes is WooCommerce's own global, not one this plugin defines; it must be populated under that exact name for WooCommerce to see the taxonomy registered above.
    global $wc_product_attributes;
    if (!is_array($wc_product_attributes)) {
        $wc_product_attributes = [];
    }
    $wc_product_attributes[$name] = $tax_row;
    // phpcs:enable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound

    return taxonomy_exists($name);
}

/**
 * Resolve or create a term under a registered taxonomy (e.g. pa_* or product_brand).
 *
 * @param string          $taxonomy Taxonomy name.
 * @param string          $term_name Display name for the term.
 * @param string|null     $last_error Set when null is returned (for diagnostics).
 * @return int|null Term ID.
 */
function askagento_ai_labelling_get_or_create_attribute_term_id($taxonomy, $term_name, &$last_error = null) {
    $last_error = null;
    $term_name = trim(wp_strip_all_tags((string) $term_name));
    if ($term_name === '') {
        return null;
    }

    $taxonomy = sanitize_key((string) $taxonomy);
    if ($taxonomy === '') {
        $last_error = 'invalid_taxonomy';

        return null;
    }

    if (!taxonomy_exists($taxonomy)) {
        askagento_ai_labelling_bootstrap_product_attribute_taxonomies();
    }
    if (!taxonomy_exists($taxonomy) && preg_match('/^pa_/i', $taxonomy)) {
        askagento_ai_labelling_register_pa_taxonomy_if_missing($taxonomy);
    }
    if (!taxonomy_exists($taxonomy)) {
        $last_error = 'taxonomy_not_registered:' . $taxonomy;

        return null;
    }

    $slug = sanitize_title($term_name);
    $existing = get_terms(
        [
            'taxonomy' => $taxonomy,
            'slug' => $slug,
            'hide_empty' => false,
            'number' => 1,
        ]
    );
    if (!is_wp_error($existing) && !empty($existing)) {
        return (int) $existing[0]->term_id;
    }

    $insert = wp_insert_term(
        $term_name,
        $taxonomy,
        ['slug' => $slug]
    );
    if (!is_wp_error($insert)) {
        return isset($insert['term_id']) ? (int) $insert['term_id'] : null;
    }

    if ($insert->get_error_code() === 'term_exists') {
        $data = $insert->get_error_data();
        if (is_int($data)) {
            return $data;
        }
        if (is_array($data) && isset($data['term_id'])) {
            return (int) $data['term_id'];
        }
    }

    $last_error = $insert->get_error_message();

    return null;
}

/**
 * WordPress user ID used for capability-sensitive WooCommerce writes during labelling REST callbacks.
 *
 * @return int First administrator ID, or 0 if none exists.
 */
function askagento_ai_labelling_default_labelling_actor_user_id() {
    $ids = get_users(
        [
            'role' => 'administrator',
            'number' => 1,
            'orderby' => 'ID',
            'order' => 'ASC',
            'fields' => 'ids',
        ]
    );
    if (!empty($ids)) {
        return (int) $ids[0];
    }

    return 0;
}

/**
 * REST callback that applies generated labels to WooCommerce products.
 *
 * Auth headers must match the registered site and Discovery API key:
 * - X-Askagento-Site-Id: <site_id>
 * - X-Askagento-Api-Key: <api_key>
 */
function askagento_ai_labelling_rest_upload(\WP_REST_Request $request) {
    $settings = function_exists('askagento_get_install_settings') ? askagento_get_install_settings() : [];
    $expected_site_id = trim((string) get_option('askagento_install_site_id', ''));
    $expected_api_key = isset($settings['api_key']) ? trim((string) $settings['api_key']) : '';

    if ($expected_site_id === '' || $expected_api_key === '') {
        return new \WP_REST_Response(['error' => 'Shop not configured'], 400);
    }

    $x_site = trim((string) $request->get_header('X-Askagento-Site-Id'));
    $x_key  = trim((string) $request->get_header('X-Askagento-Api-Key'));

    if ($x_site !== $expected_site_id) {
        return new \WP_REST_Response(['error' => 'Invalid site authentication'], 401);
    }
    if (!askagento_ai_labelling_const_time_eq($x_key, $expected_api_key)) {
        return new \WP_REST_Response(['error' => 'Invalid API key'], 401);
    }

    $body = $request->get_json_params();
    if (!is_array($body)) {
        return new \WP_REST_Response(['error' => 'Invalid JSON body'], 400);
    }

    $mode = isset($body['mode']) ? sanitize_key((string) $body['mode']) : '';
    $action = isset($body['action']) ? sanitize_key((string) $body['action']) : '';
    if (!in_array($mode, ['attributes', 'categories', 'tags'], true) || $action !== 'upload') {
        return new \WP_REST_Response(['error' => 'Unsupported mode/action'], 400);
    }

    $rows = isset($body['products']) && is_array($body['products']) ? $body['products'] : [];
    if (empty($rows)) {
        return new \WP_REST_Response(['error' => 'No products provided'], 400);
    }

    $saved_uid = get_current_user_id();
    $actor = (int) apply_filters('askagento_ai_labelling_rest_upload_user_id', askagento_ai_labelling_default_labelling_actor_user_id());
    if ($actor > 0) {
        wp_set_current_user($actor);
    }

    $updated = 0;
    $failed = 0;
    $updated_ids = [];
    $failed_ids = [];
    $failed_reasons = [];

    try {
        if ($mode === 'attributes') {
            askagento_ai_labelling_bootstrap_product_attribute_taxonomies();
        }
        foreach ($rows as $row) {
            if (!is_array($row)) {
                $failed++;
                continue;
            }
            $requested_product_id = isset($row['id']) ? (int) $row['id'] : 0;
            $product_id = $requested_product_id;
            if ($product_id <= 0) {
                $failed++;
                $failed_ids[] = $requested_product_id;
                $failed_reasons[(string) $requested_product_id] = 'invalid_product_id';
                continue;
            }

            $product = function_exists('wc_get_product') ? wc_get_product($product_id) : null;
            if (!$product) {
                $failed++;
                $failed_ids[] = $requested_product_id;
                $failed_reasons[(string) $requested_product_id] = 'product_not_found';
                continue;
            }

            if ($product->is_type('variation')) {
                $parent_id = (int) $product->get_parent_id();
                if ($parent_id <= 0) {
                    $failed++;
                    $failed_ids[] = $requested_product_id;
                    $failed_reasons[(string) $requested_product_id] = 'variation_without_parent';
                    continue;
                }
                $product_id = $parent_id;
                $product = wc_get_product($parent_id);
                if (!$product) {
                    $failed++;
                    $failed_ids[] = $requested_product_id;
                    $failed_reasons[(string) $requested_product_id] = 'parent_product_not_found';
                    continue;
                }
            }

            if ($mode === 'attributes') {
                $incoming = askagento_ai_labelling_normalize_attribute_map($row['attributes'] ?? []);
                if (empty($incoming)) {
                    $failed++;
                    $failed_ids[] = $requested_product_id;
                    $failed_reasons[(string) $requested_product_id] = 'empty_attributes_payload';
                    continue;
                }

                $existing = $product->get_attributes();
                $taxonomy_attrs = [];
                $custom_existing = [];
                if (is_array($existing)) {
                    foreach ($existing as $attr_obj) {
                        if (!($attr_obj instanceof \WC_Product_Attribute)) {
                            continue;
                        }
                        if ($attr_obj->is_taxonomy()) {
                            $taxonomy_attrs[] = $attr_obj;
                            continue;
                        }
                        $name = trim((string) $attr_obj->get_name());
                        if ($name !== '') {
                            $custom_existing[strtolower($name)] = $attr_obj;
                        }
                    }
                }

                $taxonomy_by_key = [];
                foreach ($taxonomy_attrs as $attr_obj) {
                    $tax_name = strtolower(trim((string) $attr_obj->get_name()));
                    if ($tax_name !== '') {
                        $taxonomy_by_key[$tax_name] = $attr_obj;
                    }
                }

                $applied_any = false;
                $diag = [];
                foreach ($incoming as $label => $vals) {
                    if (askagento_ai_labelling_is_brand_attribute_label((string) $label)) {
                        $brand_tax = askagento_ai_labelling_get_product_brand_taxonomy();
                        if ($brand_tax !== null && $brand_tax !== '') {
                            $brand_term_ids = [];
                            foreach ($vals as $val) {
                                $term_err = null;
                                $tid = askagento_ai_labelling_get_or_create_attribute_term_id($brand_tax, (string) $val, $term_err);
                                if ($tid !== null && $tid > 0) {
                                    $brand_term_ids[] = $tid;
                                } elseif ($term_err !== null && $term_err !== '') {
                                    $diag[] = 'Brand (' . (string) $val . '): ' . $term_err;
                                }
                            }
                            $brand_term_ids = array_values(array_unique(array_map('intval', $brand_term_ids)));
                            if (!empty($brand_term_ids)) {
                                $brand_assign = wp_set_object_terms($product_id, $brand_term_ids, $brand_tax, false);
                                if (!is_wp_error($brand_assign)) {
                                    $applied_any = true;
                                } else {
                                    $diag[] = 'Brand: ' . $brand_assign->get_error_message();
                                }
                            } else {
                                $diag[] = 'Brand: could_not_resolve_terms';
                            }
                            continue;
                        }
                        // No product brand taxonomy (e.g. WooCommerce Brands disabled): use global pa_* like other attributes.
                    }

                    $attr_err = null;
                    $meta = askagento_ai_labelling_ensure_global_attribute_taxonomy((string) $label, $attr_err);
                    if ($meta === null) {
                        $diag[] = trim((string) $label) . ($attr_err !== null && $attr_err !== '' ? ': ' . $attr_err : ': attribute_taxonomy_unavailable');
                        continue;
                    }
                    $taxonomy = $meta['taxonomy'];
                    $term_ids = [];
                    foreach ($vals as $val) {
                        $term_err = null;
                        $tid = askagento_ai_labelling_get_or_create_attribute_term_id($taxonomy, (string) $val, $term_err);
                        if ($tid !== null && $tid > 0) {
                            $term_ids[] = $tid;
                        } elseif ($term_err !== null && $term_err !== '') {
                            $diag[] = trim((string) $label) . ' (' . (string) $val . '): ' . $term_err;
                        }
                    }
                    $term_ids = array_values(array_unique(array_map('intval', $term_ids)));
                    if (empty($term_ids)) {
                        $diag[] = trim((string) $label) . ': could_not_create_terms';
                        continue;
                    }

                    $key = strtolower($taxonomy);
                    if (isset($taxonomy_by_key[$key])) {
                        $obj = $taxonomy_by_key[$key];
                        $prev = array_map('intval', (array) $obj->get_options());
                        $merged = array_values(array_unique(array_merge($prev, $term_ids)));
                        $obj->set_options($merged);
                    } else {
                        $obj = new \WC_Product_Attribute();
                        $obj->set_id($meta['attribute_id']);
                        $obj->set_name($taxonomy);
                        $obj->set_options($term_ids);
                        $obj->set_visible(true);
                        $obj->set_variation(false);
                        $taxonomy_by_key[$key] = $obj;
                    }
                    $applied_any = true;
                }

                if (!$applied_any) {
                    $failed++;
                    $failed_ids[] = $requested_product_id;
                    $failed_reasons[(string) $requested_product_id] = !empty($diag) ? implode('; ', $diag) : 'no_values_applied';
                    continue;
                }

                // Key by attribute name - WooCommerce persists attributes as a name => WC_Product_Attribute map.
                // Never call set_attributes( [] ): that clears product attributes; brand-only updates use Brands taxonomy only.
                $final_attrs = [];
                foreach (array_merge(array_values($taxonomy_by_key), array_values($custom_existing)) as $attr_obj) {
                    if (!($attr_obj instanceof \WC_Product_Attribute)) {
                        continue;
                    }
                    $nm = $attr_obj->get_name();
                    if (!is_string($nm) || $nm === '') {
                        continue;
                    }
                    $final_attrs[$nm] = $attr_obj;
                }

                try {
                    if (!empty($final_attrs)) {
                        $product->set_attributes($final_attrs);
                        $product->save();
                    }
                    $incoming_non_brand = false;
                    foreach (array_keys($incoming) as $_lk) {
                        if (!askagento_ai_labelling_is_brand_attribute_label((string) $_lk)) {
                            $incoming_non_brand = true;
                            break;
                        }
                    }
                    if ($incoming_non_brand && empty($final_attrs)) {
                        throw new \RuntimeException(
                            !empty($diag)
                                ? implode('; ', $diag)
                                : 'non_brand_attributes_not_saved_check_actor_and_taxonomies'
                        );
                    }
                    $updated++;
                    $updated_ids[] = $requested_product_id;
                } catch (\Throwable $exc) {
                    $failed++;
                    $failed_ids[] = $requested_product_id;
                    $msg = $exc->getMessage();
                    $failed_reasons[(string) $requested_product_id] = 'save_failed: ' . (function_exists('mb_substr') ? mb_substr($msg, 0, 220) : substr($msg, 0, 220));
                }
                continue;
            }

            $taxonomy = 'product_cat';
            $incoming_terms = askagento_ai_labelling_normalize_label_list($row['categories'] ?? []);
            if ($mode === 'tags') {
                $taxonomy = 'product_tag';
                $incoming_terms = askagento_ai_labelling_normalize_label_list($row['tags'] ?? []);
            }
            if (empty($incoming_terms)) {
                $failed++;
                $failed_ids[] = $requested_product_id;
                $failed_reasons[(string) $requested_product_id] = $mode === 'tags' ? 'empty_tags_payload' : 'empty_categories_payload';
                continue;
            }

            try {
                $result = wp_set_object_terms($product_id, $incoming_terms, $taxonomy, false);
                if (is_wp_error($result)) {
                    throw new \RuntimeException($result->get_error_message());
                }
                $updated++;
                $updated_ids[] = $requested_product_id;
            } catch (\Throwable $exc) {
                $failed++;
                $failed_ids[] = $requested_product_id;
                $msg = $exc->getMessage();
                $failed_reasons[(string) $requested_product_id] = 'terms_failed: ' . (function_exists('mb_substr') ? mb_substr($msg, 0, 220) : substr($msg, 0, 220));
            }
        }

        $status_code = 200;
        if ($updated === 0 && $failed > 0) {
            $status_code = 422;
        }

        return new \WP_REST_Response(
            [
                'status' => ($status_code >= 400) ? 'error' : 'ok',
                'updated_products' => $updated,
                'failed_products' => $failed,
                'updated_ids' => array_values(array_unique(array_map('intval', $updated_ids))),
                'failed_ids' => array_values(array_unique(array_map('intval', $failed_ids))),
                'failed_reasons' => $failed_reasons,
            ],
            $status_code
        );
    } finally {
        wp_set_current_user($saved_uid);
    }
}
