<?php

if (!defined('WPINC')) {
    die;
}

class Aysa_Google_Business_Profile_Manager {
    const NONCE_ACTION = 'aysa_google_oauth_nonce';
    const JOB_HOOK = 'aysa_gbp_job_event';
    const OVERVIEW_OPTION_PREFIX = 'aysa_gbp_overview_';
    const JOB_OPTION_PREFIX = 'aysa_gbp_job_';

    private static $instance = null;

    public static function get_instance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    private function __construct() {
        add_action('wp_ajax_aysa_gbp_get_overview', array($this, 'ajax_get_overview'));
        add_action('wp_ajax_aysa_gbp_start_job', array($this, 'ajax_start_job'));
        add_action('wp_ajax_aysa_gbp_get_job_status', array($this, 'ajax_get_job_status'));
        add_action('wp_ajax_aysa_gbp_get_job_history', array($this, 'ajax_get_job_history'));
        add_action('wp_ajax_aysa_gbp_set_primary_location', array($this, 'ajax_set_primary_location'));
        add_action('wp_ajax_aysa_gbp_update_location_info', array($this, 'ajax_update_location_info'));
        add_action('wp_ajax_aysa_gbp_update_hours', array($this, 'ajax_update_hours'));
        add_action('wp_ajax_aysa_gbp_update_attributes', array($this, 'ajax_update_attributes'));
        add_action('wp_ajax_aysa_gbp_update_services', array($this, 'ajax_update_services'));
        add_action('wp_ajax_aysa_gbp_create_location', array($this, 'ajax_create_location'));
        add_action('wp_ajax_aysa_gbp_delete_location', array($this, 'ajax_delete_location'));
        add_action('wp_ajax_aysa_gbp_create_post', array($this, 'ajax_create_post'));
        add_action('wp_ajax_aysa_gbp_upload_media', array($this, 'ajax_upload_media'));
        add_action('wp_ajax_aysa_gbp_list_reviews', array($this, 'ajax_list_reviews'));
        add_action('wp_ajax_aysa_gbp_reply_review', array($this, 'ajax_reply_review'));
        add_action('wp_ajax_aysa_gbp_delete_review_reply', array($this, 'ajax_delete_review_reply'));
        add_action('wp_ajax_aysa_gbp_get_performance', array($this, 'ajax_get_performance'));
        add_action('wp_ajax_aysa_gbp_get_verification_status', array($this, 'ajax_get_verification_status'));
        add_action('wp_ajax_aysa_gbp_start_verification', array($this, 'ajax_start_verification'));
        add_action(self::JOB_HOOK, array($this, 'run_job_event'), 10, 1);
    }

    private function assert_admin_request() {
        $nonce = isset($_POST['nonce']) ? sanitize_text_field((string) wp_unslash($_POST['nonce'])) : '';
        $valid = ($nonce !== '') && (wp_verify_nonce($nonce, self::NONCE_ACTION) || wp_verify_nonce($nonce, 'aysa_nonce'));
        if (!$valid) {
            wp_send_json_error('Invalid nonce');
            return false;
        }
        if (!current_user_can('manage_options')) {
            wp_send_json_error('Insufficient permissions');
            return false;
        }
        return true;
    }

    /**
     * Sanitize nested broker payloads without converting booleans or numbers.
     *
     * @param mixed $value Raw decoded JSON value.
     * @return mixed
     */
    private function sanitize_broker_payload($value) {
        if (is_array($value)) {
            $sanitized = array();
            foreach ($value as $key => $item) {
                $safe_key = is_int($key) ? $key : sanitize_key((string) $key);
                $sanitized[$safe_key] = $this->sanitize_broker_payload($item);
            }
            return $sanitized;
        }

        if (is_string($value)) {
            return sanitize_textarea_field($value);
        }

        return is_scalar($value) || null === $value ? $value : null;
    }

    private function resolve_project_id($posted_project_id = null) {
        // The installed site binding is authoritative for every GBP operation.
        // Keep the argument for caller compatibility, but never trust it.
        $project_id = intval(get_option('aysa_project_id', 0));
        if ($project_id <= 0 && defined('AYSA_LOCAL_DEV_PROJECT_ID')) {
            $project_id = intval(AYSA_LOCAL_DEV_PROJECT_ID);
            if ($project_id > 0) {
                update_option('aysa_project_id', $project_id);
            }
        }
        return $project_id;
    }

    private function get_site_host() {
        $host = wp_parse_url(home_url(), PHP_URL_HOST);
        return is_string($host) ? strtolower(trim($host)) : '';
    }

    private function overview_option_key($project_id, $site_host) {
        return self::OVERVIEW_OPTION_PREFIX . intval($project_id) . '_' . md5((string) $site_host);
    }

    private function job_option_key($project_id, $site_host) {
        return self::JOB_OPTION_PREFIX . intval($project_id) . '_' . md5((string) $site_host);
    }

    private function get_payload_base() {
        $project_id = $this->resolve_project_id();
        return array(
            'project_id' => $project_id,
            'site_host' => $this->get_site_host(),
        );
    }

    public function ajax_get_overview() {
        if (!$this->assert_admin_request()) {
            return;
        }

        $payload = $this->get_payload_base();
        $result = $this->broker_request('POST', '/v1/offpage/gbp/overview', $payload, 60);
        if (is_wp_error($result)) {
            $cached = get_option($this->overview_option_key($payload['project_id'], $payload['site_host']), array());
            if (is_array($cached) && !empty($cached)) {
                wp_send_json_success($cached);
                return;
            }
            wp_send_json_error($result->get_error_message());
            return;
        }
        update_option($this->overview_option_key($payload['project_id'], $payload['site_host']), $result, false);
        wp_send_json_success($result);
    }

    public function ajax_start_job() {
        if (!$this->assert_admin_request()) {
            return;
        }

        $payload = $this->get_payload_base();
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $payload['job_type'] = sanitize_key((string) wp_unslash($_POST['job_type'] ?? ''));
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $payload['location_name'] = sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? ''));
        // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
        $payload['account_name'] = sanitize_text_field((string) wp_unslash($_POST['account_name'] ?? ''));

        $local = array(
            'job_type' => $payload['job_type'],
            'status' => 'queued',
            'remote_job_id' => 0,
            'updated_at' => gmdate('c'),
            'error' => '',
        );
        update_option($this->job_option_key($payload['project_id'], $payload['site_host']), $local, false);

        $event_payload = $payload;
        $event_payload['phase'] = 'start';
        $this->schedule_job_event($event_payload, 1);
        spawn_cron();

        wp_send_json_success(array(
            'ok' => true,
            'job' => $local,
        ));
    }

    public function ajax_get_job_status() {
        if (!$this->assert_admin_request()) {
            return;
        }

        $payload = $this->get_payload_base();
        $local = get_option($this->job_option_key($payload['project_id'], $payload['site_host']), array());
        if (!is_array($local)) {
            $local = array();
        }

        $remote_job_id = intval($local['remote_job_id'] ?? 0);
        if ($remote_job_id > 0) {
            $status = $this->broker_request('POST', '/v1/offpage/gbp/jobs/status', array(
                'project_id' => $payload['project_id'],
                'job_id' => $remote_job_id,
            ), 45);
            if (!is_wp_error($status) && is_array($status) && isset($status['job']) && is_array($status['job'])) {
                $job = $status['job'];
                $local = array(
                    'job_type' => (string) ($job['job_type'] ?? ''),
                    'status' => (string) ($job['status'] ?? 'queued'),
                    'remote_job_id' => intval($job['id'] ?? 0),
                    'updated_at' => (string) ($job['updated_at'] ?? gmdate('c')),
                    'completed_at' => (string) ($job['completed_at'] ?? ''),
                    'error' => (string) ($job['error'] ?? ''),
                    'job' => $job,
                );
                update_option($this->job_option_key($payload['project_id'], $payload['site_host']), $local, false);
                if (in_array($local['status'], array('queued', 'running'), true)) {
                    $event_payload = $payload;
                    $event_payload['phase'] = 'advance';
                    $event_payload['remote_job_id'] = $remote_job_id;
                    $this->schedule_job_event($event_payload, 5);
                } else {
                    $overview = $this->broker_request('POST', '/v1/offpage/gbp/overview', $payload, 60);
                    if (!is_wp_error($overview) && is_array($overview)) {
                        update_option($this->overview_option_key($payload['project_id'], $payload['site_host']), $overview, false);
                    }
                }
            }
        }

        wp_send_json_success($local);
    }

    public function ajax_get_job_history() {
        if (!$this->assert_admin_request()) {
            return;
        }

        $payload = $this->get_payload_base();
        $result = $this->broker_request('POST', '/v1/offpage/gbp/jobs/history', $payload, 45);
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
            return;
        }
        wp_send_json_success($result);
    }

    public function ajax_set_primary_location() {
        $this->ajax_direct_proxy('/v1/offpage/gbp/location/set-primary', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'location_name' => sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? '')),
        ));
    }

    public function ajax_update_location_info() {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing -- Raw JSON is decoded and immediately passed through sanitize_broker_payload before leaving WordPress.
        $patch = json_decode((string) wp_unslash($_POST['patch'] ?? '{}'), true);
        $this->ajax_direct_proxy('/v1/offpage/gbp/location/update', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'location_name' => sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? '')),
            'patch' => is_array($patch) ? $this->sanitize_broker_payload($patch) : array(),
        ));
    }

    public function ajax_update_hours() {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing -- Raw JSON is decoded and immediately passed through sanitize_broker_payload before leaving WordPress.
        $regular = json_decode((string) wp_unslash($_POST['regularHours'] ?? '{}'), true);
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing -- Raw JSON is decoded and immediately passed through sanitize_broker_payload before leaving WordPress.
        $special = json_decode((string) wp_unslash($_POST['specialHours'] ?? '{}'), true);
        $this->ajax_direct_proxy('/v1/offpage/gbp/location/hours', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'location_name' => sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? '')),
            'regularHours' => is_array($regular) ? $this->sanitize_broker_payload($regular) : array(),
            'specialHours' => is_array($special) ? $this->sanitize_broker_payload($special) : array(),
        ));
    }

    public function ajax_update_attributes() {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing -- Raw JSON is decoded and immediately passed through sanitize_broker_payload before leaving WordPress.
        $attributes = json_decode((string) wp_unslash($_POST['attributes'] ?? '[]'), true);
        $this->ajax_direct_proxy('/v1/offpage/gbp/location/attributes', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'location_name' => sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? '')),
            'attributes' => is_array($attributes) ? $this->sanitize_broker_payload($attributes) : array(),
        ));
    }

    public function ajax_update_services() {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing -- Raw JSON is decoded and immediately passed through sanitize_broker_payload before leaving WordPress.
        $items = json_decode((string) wp_unslash($_POST['serviceItems'] ?? '[]'), true);
        $this->ajax_direct_proxy('/v1/offpage/gbp/location/services', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'location_name' => sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? '')),
            'serviceItems' => is_array($items) ? $this->sanitize_broker_payload($items) : array(),
        ));
    }

    public function ajax_create_location() {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing -- Raw JSON is decoded and immediately passed through sanitize_broker_payload before leaving WordPress.
        $location = json_decode((string) wp_unslash($_POST['location'] ?? '{}'), true);
        $this->ajax_direct_proxy('/v1/offpage/gbp/location/create', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'account_name' => sanitize_text_field((string) wp_unslash($_POST['account_name'] ?? '')),
            'location' => is_array($location) ? $this->sanitize_broker_payload($location) : array(),
        ));
    }

    public function ajax_delete_location() {
        $this->ajax_direct_proxy('/v1/offpage/gbp/location/delete', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'location_name' => sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? '')),
        ));
    }

    public function ajax_create_post() {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing -- Raw JSON is decoded and immediately passed through sanitize_broker_payload before leaving WordPress.
        $post = json_decode((string) wp_unslash($_POST['post'] ?? '{}'), true);
        $this->ajax_direct_proxy('/v1/offpage/gbp/posts/create', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'location_name' => sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? '')),
            'post' => is_array($post) ? $this->sanitize_broker_payload($post) : array(),
        ));
    }

    public function ajax_upload_media() {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing -- Raw JSON is decoded and immediately passed through sanitize_broker_payload before leaving WordPress.
        $media = json_decode((string) wp_unslash($_POST['media'] ?? '{}'), true);
        $this->ajax_direct_proxy('/v1/offpage/gbp/media/upload', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'location_name' => sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? '')),
            'media' => is_array($media) ? $this->sanitize_broker_payload($media) : array(),
        ));
    }

    public function ajax_list_reviews() {
        $this->ajax_direct_proxy('/v1/offpage/gbp/reviews/list', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'location_name' => sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? '')),
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'force_refresh' => !empty($_POST['force_refresh']) ? 1 : 0,
        ));
    }

    public function ajax_reply_review() {
        $this->ajax_direct_proxy('/v1/offpage/gbp/reviews/reply', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'review_name' => sanitize_text_field((string) wp_unslash($_POST['review_name'] ?? '')),
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'comment' => sanitize_textarea_field((string) wp_unslash($_POST['comment'] ?? '')),
        ));
    }

    public function ajax_delete_review_reply() {
        $this->ajax_direct_proxy('/v1/offpage/gbp/reviews/reply/delete', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'review_name' => sanitize_text_field((string) wp_unslash($_POST['review_name'] ?? '')),
        ));
    }

    public function ajax_get_performance() {
        $this->ajax_direct_proxy('/v1/offpage/gbp/performance/get', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'location_name' => sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? '')),
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'force_refresh' => !empty($_POST['force_refresh']) ? 1 : 0,
        ));
    }

    public function ajax_get_verification_status() {
        $this->ajax_direct_proxy('/v1/offpage/gbp/verification/status', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'location_name' => sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? '')),
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'force_refresh' => !empty($_POST['force_refresh']) ? 1 : 0,
        ));
    }

    public function ajax_start_verification() {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing -- Raw JSON is decoded and immediately passed through sanitize_broker_payload before leaving WordPress.
        $verification = json_decode((string) wp_unslash($_POST['verification'] ?? '{}'), true);
        $this->ajax_direct_proxy('/v1/offpage/gbp/verification/start', array(
            // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reviewed callback inventory: this request read is downstream of the handler nonce and capability guard, a shared guarded delegate, or the authenticated WordPress core save/query flow.
            'location_name' => sanitize_text_field((string) wp_unslash($_POST['location_name'] ?? '')),
            'verification' => is_array($verification) ? $this->sanitize_broker_payload($verification) : array(),
        ));
    }

    private function ajax_direct_proxy($route, array $extra = array()) {
        if (!$this->assert_admin_request()) {
            return;
        }
        $payload = array_merge($this->get_payload_base(), $extra);
        $result = $this->broker_request('POST', $route, $payload, 60);
        if (is_wp_error($result)) {
            wp_send_json_error($result->get_error_message());
            return;
        }
        $overview = $this->broker_request('POST', '/v1/offpage/gbp/overview', $this->get_payload_base(), 60);
        if (!is_wp_error($overview) && is_array($overview)) {
            update_option($this->overview_option_key($payload['project_id'], $payload['site_host']), $overview, false);
            $result['overview'] = $overview;
        }
        wp_send_json_success($result);
    }

    public function run_job_event($payload) {
        if (!is_array($payload)) {
            return;
        }

        $project_id = intval($payload['project_id'] ?? 0);
        $site_host = strtolower(trim((string) ($payload['site_host'] ?? '')));
        $bound_project_id = $this->resolve_project_id();
        $bound_site_host = $this->get_site_host();
        if (
            $project_id <= 0
            || $site_host === ''
            || $bound_project_id <= 0
            || $bound_site_host === ''
            || $project_id !== $bound_project_id
            || !hash_equals($bound_site_host, $site_host)
        ) {
            return;
        }

        $job_key = $this->job_option_key($project_id, $site_host);
        $local = get_option($job_key, array());
        if (!is_array($local)) {
            $local = array();
        }

        $phase = sanitize_key((string) ($payload['phase'] ?? 'start'));
        if ($phase === 'start' && intval($local['remote_job_id'] ?? 0) <= 0) {
            $start = $this->broker_request('POST', '/v1/offpage/gbp/jobs/start', $payload, 60);
            if (is_wp_error($start) || !is_array($start) || empty($start['job'])) {
                update_option($job_key, array(
                    'job_type' => (string) ($payload['job_type'] ?? ''),
                    'status' => 'failed',
                    'remote_job_id' => 0,
                    'updated_at' => gmdate('c'),
                    'error' => is_wp_error($start) ? $start->get_error_message() : 'GBP broker start failed.',
                ), false);
                return;
            }
            $job = is_array($start['job']) ? $start['job'] : array();
            $remote_job_id = intval($job['id'] ?? 0);
            update_option($job_key, array(
                'job_type' => (string) ($job['job_type'] ?? $payload['job_type'] ?? ''),
                'status' => (string) ($job['status'] ?? 'queued'),
                'remote_job_id' => $remote_job_id,
                'updated_at' => (string) ($job['updated_at'] ?? gmdate('c')),
                'completed_at' => (string) ($job['completed_at'] ?? ''),
                'error' => (string) ($job['error'] ?? ''),
                'job' => $job,
            ), false);
            if ($remote_job_id > 0) {
                $next = $payload;
                $next['phase'] = 'advance';
                $next['remote_job_id'] = $remote_job_id;
                $this->schedule_job_event($next, 5);
            }
            return;
        }

        $remote_job_id = intval($payload['remote_job_id'] ?? ($local['remote_job_id'] ?? 0));
        if ($remote_job_id <= 0) {
            return;
        }

        $status = $this->broker_request('POST', '/v1/offpage/gbp/jobs/status', array(
            'project_id' => $project_id,
            'job_id' => $remote_job_id,
            'advance' => 1,
        ), 90);
        if (is_wp_error($status) || !is_array($status) || empty($status['job'])) {
            update_option($job_key, array(
                'job_type' => (string) ($local['job_type'] ?? ''),
                'status' => 'failed',
                'remote_job_id' => $remote_job_id,
                'updated_at' => gmdate('c'),
                'error' => is_wp_error($status) ? $status->get_error_message() : 'GBP job status failed.',
            ), false);
            return;
        }

        $job = is_array($status['job']) ? $status['job'] : array();
        $local = array(
            'job_type' => (string) ($job['job_type'] ?? $local['job_type'] ?? ''),
            'status' => (string) ($job['status'] ?? 'running'),
            'remote_job_id' => $remote_job_id,
            'updated_at' => (string) ($job['updated_at'] ?? gmdate('c')),
            'completed_at' => (string) ($job['completed_at'] ?? ''),
            'error' => (string) ($job['error'] ?? ''),
            'job' => $job,
        );
        update_option($job_key, $local, false);

        if (in_array($local['status'], array('queued', 'running'), true)) {
            $next = $payload;
            $next['phase'] = 'advance';
            $next['remote_job_id'] = $remote_job_id;
            $this->schedule_job_event($next, 10);
            return;
        }

        $overview = $this->broker_request('POST', '/v1/offpage/gbp/overview', array(
            'project_id' => $project_id,
            'site_host' => $site_host,
        ), 60);
        if (!is_wp_error($overview) && is_array($overview)) {
            update_option($this->overview_option_key($project_id, $site_host), $overview, false);
        }
    }

    private function schedule_job_event(array $payload, $delay_seconds = 1) {
        if (!wp_next_scheduled(self::JOB_HOOK, array($payload))) {
            wp_schedule_single_event(time() + max(1, intval($delay_seconds)), self::JOB_HOOK, array($payload));
        }
    }

    private function get_broker_config() {
        $base_url = defined('AYSA_OAUTH_BROKER_BASE_URL')
            ? AYSA_OAUTH_BROKER_BASE_URL
            : (class_exists('Aysa_API_Config') ? Aysa_API_Config::get_base_url() : 'https://api.aysa.ai');
        $key_id = defined('AYSA_OAUTH_BROKER_KEY_ID') ? AYSA_OAUTH_BROKER_KEY_ID : 'wp_plugin';
        $key_secret = defined('AYSA_OAUTH_BROKER_KEY_SECRET') ? AYSA_OAUTH_BROKER_KEY_SECRET : '';

        $base_url = rtrim((string) $base_url, '/');
        $key_id = trim((string) $key_id);
        $key_secret = trim((string) $key_secret);

        if ($base_url === '' || $key_id === '' || $key_secret === '') {
            return new WP_Error('broker_config_missing', 'OAuth broker is not configured on this site.');
        }

        return array(
            'base_url' => $base_url,
            'key_id' => $key_id,
            'key_secret' => $key_secret,
        );
    }

    private function broker_request($method, $path, $payload = array(), $timeout = 30) {
        $cfg = $this->get_broker_config();
        if (is_wp_error($cfg)) {
            return $cfg;
        }

        $method = strtoupper((string) $method);
        $path = '/' . ltrim((string) $path, '/');
        $url = $cfg['base_url'] . $path;
        $body = ($method === 'POST') ? wp_json_encode($payload) : '';
        if ($body === false) {
            $body = '{}';
        }

        $timestamp = (string) time();
        try {
            $nonce = bin2hex(random_bytes(16));
        } catch (Exception $e) {
            $nonce = wp_generate_password(32, false, false);
        }

        $body_hash = hash('sha256', $body);
        $canonical = implode("\n", array($method, $path, $body_hash, $timestamp, $nonce));
        $signature = hash_hmac('sha256', $canonical, $cfg['key_secret']);

        $args = array(
            'timeout' => max(10, min(120, intval($timeout))),
            'method' => $method,
            'headers' => array(
                'Content-Type' => 'application/json',
                'X-Aysa-Key' => $cfg['key_id'],
                'X-Aysa-Timestamp' => $timestamp,
                'X-Aysa-Nonce' => $nonce,
                'X-Aysa-Signature' => $signature,
            ),
        );

        if ($method === 'POST') {
            $args['body'] = $body;
        }

        $resp = wp_remote_request($url, $args);
        if (is_wp_error($resp)) {
            if (function_exists('aysa_report_operational_error')) {
                aysa_report_operational_error(array(
                    'source' => 'plugin',
                    'module' => 'google',
                    'severity' => 'error',
                    'error_code' => 'google_gbp_broker_http_failed',
                    'message_safe' => 'Google Business Profile broker request failed.',
                    'action_key' => 'google_business_profile_broker_request',
                    'technical_context' => array(
                        'method' => $method,
                        'path' => $path,
                        'wp_error_code' => $resp->get_error_code(),
                        'wp_error_message' => $resp->get_error_message(),
                    ),
                ));
            }
            return new WP_Error('broker_http_failed', 'Broker request failed: ' . $resp->get_error_message());
        }

        $code = wp_remote_retrieve_response_code($resp);
        $decoded = json_decode((string) wp_remote_retrieve_body($resp), true);
        if (!is_array($decoded)) {
            if (function_exists('aysa_report_operational_error')) {
                aysa_report_operational_error(array(
                    'source' => 'plugin',
                    'module' => 'google',
                    'severity' => 'error',
                    'error_code' => 'google_gbp_broker_invalid_response',
                    'message_safe' => 'Google Business Profile broker returned invalid JSON.',
                    'action_key' => 'google_business_profile_broker_request',
                    'technical_context' => array(
                        'method' => $method,
                        'path' => $path,
                        'http_code' => intval($code),
                    ),
                ));
            }
            return new WP_Error('broker_invalid_response', 'Broker returned invalid JSON response. [code=' . intval($code) . ']');
        }
        if ($code < 200 || $code >= 300 || (isset($decoded['ok']) && !$decoded['ok'])) {
            $msg = isset($decoded['error']) ? (string) $decoded['error'] : 'Broker returned error.';
            if (function_exists('aysa_report_operational_error')) {
                aysa_report_operational_error(array(
                    'source' => 'plugin',
                    'module' => 'google',
                    'severity' => 'error',
                    'error_code' => 'google_gbp_broker_error',
                    'message_safe' => 'Google Business Profile broker returned an error.',
                    'action_key' => 'google_business_profile_broker_request',
                    'technical_context' => array(
                        'method' => $method,
                        'path' => $path,
                        'http_code' => intval($code),
                        'broker_error' => $msg,
                    ),
                ));
            }
            return new WP_Error('broker_error', $msg . ' [code=' . intval($code) . ']');
        }

        return $decoded;
    }
}
