<?php
declare(strict_types=1);
// ==================================================
// Nuclear Fancy Pants Edition™ — NUCLEAR+++
// File: /includes/class-webhook-receiver.php
// Description: WP REST endpoint — receives HMAC-signed subscription events from Core, updates plan state — NUCLEAR+++
// Last Edited: 2026-03-22
// ==================================================
namespace AutoMarketerAI;

if (!defined('ABSPATH')) {
    exit('No direct script access allowed.');
}

final class WebhookReceiver
{
    // Maps Stripe price IDs to internal plan slugs
    private const PRICE_TO_PLAN = [
        'price_1TAYegJfH56oJEdkSgQPS5bx' => 'starter',        // starter monthly
        'price_1TAYfhJfH56oJEdkb03ncBO1' => 'starter',        // starter yearly
        'price_1TAYgyJfH56oJEdkP9mcO8ni' => 'starter_pro',    // starter_pro monthly
        'price_1TAYhnJfH56oJEdkxKPnfbCR' => 'starter_pro',    // starter_pro yearly
        'price_1TAYjAJfH56oJEdkt8jufrgM' => 'unlimited_ai',   // unlimited_ai monthly
        'price_1TAYkYJfH56oJEdkCBvqRIFH' => 'unlimited_ai',   // unlimited_ai yearly
        'price_1TAYsYJfH56oJEdkwUdzpjkW' => 'agency',         // agency monthly
        'price_1TAYtHJfH56oJEdkgion9Gzy' => 'agency',         // agency yearly
    ];

    // Add-on price IDs — seat blocks for Agency
    private const ADDON_PRICE_TO_SEATS = [
        'price_1TAZboJfH56oJEdkDgQ0Rlbn' => 10,  // +10 monthly
        'price_1TAbCFJfH56oJEdkud6GxSQX' => 10,  // +10 yearly
        'price_1TAbP8JfH56oJEdkt5naeX8L' => 25,  // +25 monthly
        'price_1TAbQnJfH56oJEdkNnqNdHOx' => 25,  // +25 yearly
        'price_1TAbTaJfH56oJEdkp1ahNzq1' => 50,  // +50 monthly
        'price_1TAbVXJfH56oJEdkoLNG4gW8' => 50,  // +50 yearly
    ];

    private static ?self $instance = null;

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

    private function __construct()
    {
        add_action('rest_api_init', [$this, 'register_routes']);
    }

    public function register_routes(): void
    {
        register_rest_route('amai/v1', '/subscription-update', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle'],
            'permission_callback' => '__return_true', // auth done via HMAC below
        ]);

        // Endpoint WP billing page calls to get prorated refund preview before cancel
        register_rest_route('amai/v1', '/refund-preview', [
            'methods'             => 'GET',
            'callback'            => [$this, 'handle_refund_preview'],
            'permission_callback' => function () {
                return current_user_can('manage_automarketer_ai');
            },
        ]);

        // Endpoint WP billing page calls to initiate cancellation
        register_rest_route('amai/v1', '/cancel-subscription', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_cancel'],
            'permission_callback' => function () {
                return current_user_can('manage_automarketer_ai');
            },
        ]);

        // Endpoint WP billing page calls to downgrade plan
        register_rest_route('amai/v1', '/downgrade-plan', [
            'methods'             => 'POST',
            'callback'            => [$this, 'handle_downgrade'],
            'permission_callback' => function () {
                return current_user_can('manage_automarketer_ai');
            },
        ]);
    }

    // ── Core → WP callback ─────────────────────────────────────────────
    public function handle(\WP_REST_Request $request): \WP_REST_Response
    {
        if (!$this->verify_hmac($request)) {
            return new \WP_REST_Response(['error' => 'unauthorized'], 401);
        }

        $body = json_decode($request->get_body(), true);
        if (!is_array($body)) {
            return new \WP_REST_Response(['error' => 'invalid_body'], 400);
        }

        $event      = $body['event']       ?? '';
        $planId     = $body['plan_id']     ?? null;
        $addonId    = $body['addon_id']    ?? null;
        $subId      = $body['sub_id']      ?? null;
        $customerId = $body['customer_id'] ?? null;
        $licenseKey  = $body['license_key']   ?? '';
        $adminNotify   = $body['admin_notify']    ?? '';
        $firstName     = $body['first_name']      ?? '';
        $email         = $body['email']           ?? '';
        $customerEmail = $body['customer_email']  ?? '';
        $customerName  = $body['customer_name']   ?? '';
        $planSlug    = null;

        switch ($event) {
            case 'subscription_active':
                $slug = is_string($planId) ? (self::PRICE_TO_PLAN[$planId] ?? null) : null;
                if ($slug !== null) {
                    update_option('automarketer_ai_plan_id', $slug);
                    update_option('automarketer_ai_license_status', 'active');
                    update_option('automarketer_ai_stripe_sub_id', (string) $subId);
                    update_option('automarketer_ai_stripe_customer_id', (string) $customerId);
                    \AutoMarketerAI\PlanEnforcer::reconcile($slug);
                }
                if ($addonId !== null && isset(self::ADDON_PRICE_TO_SEATS[$addonId])) {
                    update_option('automarketer_ai_addon_seats', self::ADDON_PRICE_TO_SEATS[$addonId]);
                } else {
                    delete_option('automarketer_ai_addon_seats');
                // Store license key if provided
                if (is_string($licenseKey) && $licenseKey !== '') {
                    update_option('automarketer_ai_license_key', $licenseKey);
                }
                // Send customer welcome email for paid signup
                if (is_string($email) && $email !== '' && is_string($slug) && $slug !== null) {
                    $storedKey = is_string($licenseKey) && $licenseKey !== '' ? $licenseKey : (string) get_option('automarketer_ai_license_key', '');
                    $prevPlanAtSend = (string) get_option('automarketer_ai_plan_id', 'free');
                    if ($prevPlanAtSend === 'free' || $prevPlanAtSend === '') {
                    $prevPlanAtSend = (string) get_option('automarketer_ai_plan_id', 'free');
                    if ($prevPlanAtSend === 'free' || $prevPlanAtSend === '') {
                        self::sendPaidWelcomeEmail($email, is_string($firstName) && $firstName !== '' ? $firstName : 'there', $slug, $storedKey);
                    } else {
                        self::sendUpgradeEmail($email, is_string($firstName) && $firstName !== '' ? $firstName : 'there', $slug, $prevPlanAtSend);
                    }
                    } else {
                        self::sendUpgradeEmail($email, is_string($firstName) && $firstName !== '' ? $firstName : 'there', $slug, $prevPlanAtSend);
                    }
                }
                // Admin notification
                if (is_string($adminNotify) && $adminNotify === 'subscription_active') {
                    $prevPlan = (string) get_option('automarketer_ai_plan_id', 'free');
                    self::sendAdminNotification($adminNotify, [
                        'email'     => is_string($email) ? $email : '',
                        'plan'      => is_string($slug) && $slug !== null ? $slug : '',
                        'prev_plan' => $prevPlan,
                    ]);
                }
                }
                break;

            case 'subscription_cancelled':
                update_option('automarketer_ai_plan_id', 'free');
                update_option('automarketer_ai_license_status', 'free');
                delete_option('automarketer_ai_stripe_sub_id');
                delete_option('automarketer_ai_addon_seats');
                \AutoMarketerAI\PlanEnforcer::reconcile('free');
                // Keep customer_id so they can re-subscribe without re-entering payment
                if (is_string($adminNotify) && $adminNotify === 'subscription_cancelled') {
                    self::sendAdminNotification($adminNotify, [
                        'email'      => is_string($customerEmail) && $customerEmail !== '' ? $customerEmail : (is_string($email) ? $email : ''),
                        'plan'       => (string) get_option('automarketer_ai_plan_id', ''),
                        'refund'     => isset($body['refund_amount']) ? (int) $body['refund_amount'] : 0,
                        'currency'   => isset($body['currency']) ? (string) $body['currency'] : 'usd',
                    ]);
                }
                $cancelEmail = is_string($customerEmail) && $customerEmail !== '' ? $customerEmail : (is_string($email) ? $email : '');
                $cancelName  = is_string($customerName) && $customerName !== '' ? $customerName : 'there';
                if ($cancelEmail !== '') {
                    self::sendCancellationEmail($cancelEmail, $cancelName);
                }
                break;

            case 'payment_failed':
                // Flag only — do not immediately revoke access
                update_option('automarketer_ai_payment_failed', '1');
                break;

            default:
                // Unknown event — log and ignore
                break;
        }

        return new \WP_REST_Response(['ok' => true], 200);
    }

    // ── Refund preview — called by billing page before cancel dialog ───
    public function handle_refund_preview(\WP_REST_Request $request): \WP_REST_Response
    {
        if (!check_ajax_referer('amai_refund_preview', '_wpnonce', false)) {
            return new \WP_REST_Response(['error' => 'nonce_failed'], 403);
        }

        $subId = get_option('automarketer_ai_stripe_sub_id', '');
        if ($subId === '') {
            return new \WP_REST_Response(['refund_amount' => 0, 'currency' => 'usd', 'formatted' => '$0.00'], 200);
        }

        // Ask Core for the refund calculation
        $result = $this->call_core('/api/stripe/refund-preview', 'GET', ['sub_id' => $subId]);
        if ($result === null) {
            return new \WP_REST_Response(['error' => 'core_unavailable'], 502);
        }

        return new \WP_REST_Response($result, 200);
    }

    // ── Cancel subscription ─────────────────────────────────────────────
    public function handle_cancel(\WP_REST_Request $request): \WP_REST_Response
    {
        $body  = json_decode($request->get_body(), true);
        $nonce = isset($body['_wpnonce']) ? (string) $body['_wpnonce'] : '';
        if (!wp_verify_nonce($nonce, 'amai_cancel_subscription')) {
            return new \WP_REST_Response(['error' => 'nonce_failed'], 403);
        }

        $subId = get_option('automarketer_ai_stripe_sub_id', '');
        if ($subId === '') {
            return new \WP_REST_Response(['error' => 'no_active_subscription'], 400);
        }

        $result = $this->call_core('/api/stripe/cancel-subscription', 'POST', ['sub_id' => $subId]);
        if ($result === null) {
            return new \WP_REST_Response(['error' => 'core_unavailable'], 502);
        }

        // Stripe webhook will fire subscription.deleted and update options via handle()
        // Optimistically update here so UI responds immediately
        update_option('automarketer_ai_plan_id', 'free');
        update_option('automarketer_ai_license_status', 'free');
        delete_option('automarketer_ai_stripe_sub_id');
        delete_option('automarketer_ai_addon_seats');
        \AutoMarketerAI\PlanEnforcer::reconcile('free');

        return new \WP_REST_Response($result, 200);
    }

    // ── Downgrade plan ──────────────────────────────────────────────────
    public function handle_downgrade(\WP_REST_Request $request): \WP_REST_Response
    {
        $body       = json_decode($request->get_body(), true);
        $nonce      = isset($body['_wpnonce']) ? (string) $body['_wpnonce'] : '';
        if (!wp_verify_nonce($nonce, 'amai_downgrade_plan')) {
            return new \WP_REST_Response(['error' => 'nonce_failed'], 403);
        }

        $targetPlan = sanitize_text_field($body['target_plan'] ?? '');
        $subId      = get_option('automarketer_ai_stripe_sub_id', '');

        if ($targetPlan === '' || $subId === '') {
            return new \WP_REST_Response(['error' => 'missing_parameters'], 400);
        }

        $validPlans = ['starter', 'starter_pro', 'unlimited_ai', 'agency'];
        if (!in_array($targetPlan, $validPlans, true)) {
            return new \WP_REST_Response(['error' => 'invalid_target_plan'], 400);
        }

        $result = $this->call_core('/api/stripe/downgrade-plan', 'POST', [
            'sub_id'      => $subId,
            'target_plan' => $targetPlan,
        ]);

        if ($result === null) {
            return new \WP_REST_Response(['error' => 'core_unavailable'], 502);
        }

        // Optimistically update plan so UI reflects change immediately
        update_option('automarketer_ai_plan_id', $targetPlan);
        \AutoMarketerAI\PlanEnforcer::reconcile($targetPlan);
        return new \WP_REST_Response($result, 200);
    }

    // ── Public test entry point (called from SettingsPage) ─────────────────
    public static function sendTestEmail(string $type): bool
    {
        $to        = (string) get_option('admin_email', '');
        $user      = get_user_by('email', $to);
        $firstName = '';
        if ($user instanceof \WP_User) {
            $firstName = (string) $user->first_name;
            if ($firstName === '') {
                $firstName = (string) $user->display_name;
            }
        }
        if ($firstName === '') {
            $firstName = 'there';
        }
        $licenseKey = (string) get_option('automarketer_ai_license_key', '');
        switch ($type) {
            case 'welcome':
                MarketingSite::sendTestWelcomeEmail($to, $firstName, $licenseKey);
                return true;
            case 'paid_welcome':
                self::sendPaidWelcomeEmail($to, $firstName, 'starter_pro', $licenseKey);
                return true;
            case 'upgrade_starter':
                self::sendUpgradeEmail($to, $firstName, 'starter', 'free');
                return true;
            case 'upgrade_starter_pro':
                self::sendUpgradeEmail($to, $firstName, 'starter_pro', 'starter');
                return true;
            case 'upgrade_unlimited_ai':
                self::sendUpgradeEmail($to, $firstName, 'unlimited_ai', 'starter_pro');
                return true;
            case 'cancellation':
                self::sendCancellationEmail($to, $firstName);
                return true;
            case 'blog_nudge':
                $dummy_titles = [
                    ['title' => '5 Ways AI Is Changing Small Business Marketing', 'url' => admin_url()],
                    ['title' => 'How to Get More Customers Without Spending More on Ads', 'url' => admin_url()],
                    ['title' => 'The One Thing Most Small Businesses Get Wrong on Social Media', 'url' => admin_url()],
                ];
                $site_name = get_bloginfo('name');
                $html = MarketingSite::buildNudgeEmailHtml($firstName, $site_name, $dummy_titles);
                wp_mail($to, 'Time to write a new blog post for ' . $site_name, $html, ['Content-Type: text/html; charset=UTF-8']);
                return true;
            case 'blog_draft_ready':
                $draft_posts = get_posts(['numberposts' => 1, 'post_status' => ['draft', 'publish'], 'post_type' => 'post']);
                $draft_title = !empty($draft_posts) ? $draft_posts[0]->post_title : 'How to Grow Your Business with Automated Marketing';
                $draft_edit  = !empty($draft_posts) ? (string) get_edit_post_link($draft_posts[0]->ID, 'raw') : admin_url();
                $html = MarketingSite::buildDraftReadyEmailHtml($firstName, $draft_title, $draft_edit);
                wp_mail($to, 'We wrote a blog post for you - go take a look', $html, ['Content-Type: text/html; charset=UTF-8']);
                return true;
            case 'blog_published':
                $pub_posts = get_posts(['numberposts' => 1, 'post_status' => 'publish', 'post_type' => 'post']);
                $pub_title = !empty($pub_posts) ? $pub_posts[0]->post_title : 'How to Grow Your Business with Automated Marketing';
                $pub_id    = !empty($pub_posts) ? $pub_posts[0]->ID : 0;
                $html = MarketingSite::buildBlogPublishedEmailHtml($firstName, $pub_title, $pub_id);
                wp_mail($to, 'AutoMarketer AI just published a new blog post for you', $html, ['Content-Type: text/html; charset=UTF-8']);
                return true;
                return true;
            case 'cart_abandonment':
                AbandonedCart::sendTestEmail($to, $firstName, 1);
                return true;
            case 'cart_abandonment_2':
                AbandonedCart::sendTestEmail($to, $firstName, 2);
                return true;
            case 'cart_abandonment_3':
                AbandonedCart::sendTestEmail($to, $firstName, 3);
                return true;
            case 'cart_abandonment_2_opened':
                AbandonedCart::sendTestEmail($to, $firstName, 2, true);
                return true;
            case 'cart_abandonment_3_opened':
                AbandonedCart::sendTestEmail($to, $firstName, 3, true);
                return true;
            case 'cart_recovery':
                $order = wc_get_orders(['limit' => 1, 'status' => ['processing', 'completed']]);
                $order = !empty($order) ? $order[0] : null;
                if ($order instanceof \WC_Order) {
                    AbandonedCart::sendRecoveryNotification($to, $order);
                } else {
                    wp_mail($to, 'Recovered cart: Test Customer ($49.99)',
                        '<p>Test recovery notification -- no real orders found to use as sample.</p>',
                        ['Content-Type: text/html; charset=UTF-8']);
                }
                return true;
            case 'comment_digest':
                CommentDigest::send();
                return true;
            case 'posting_failure':
                PostingEngine::sendTestFailureEmail();
                return true;
            case 're_engagement_followup':
                ProductViewTracker::sendTestFollowupEmail($to, $firstName);
                return true;
            case 'setup_nudge':
                NudgeEngine::sendTestNudgeEmail($to, $firstName);
                return true;

            default:
                return false;
        }
    }


    // ── Send paid welcome email (new signup) ───────────────────────────────
    // ── Send paid welcome email (new signup) ───────────────────────────────
    private static function sendPaidWelcomeEmail(string $email, string $firstName, string $plan, string $licenseKey): void
    {
        $loginUrl = home_url('/get-started');
        $subject  = 'Welcome to AutoMarketer AI';
        $html     = MarketingSite::buildWizardWelcomeEmailHtml($firstName, $email, '', $licenseKey, $loginUrl, wp_lostpassword_url());
        $headers  = [
            'From: AutoMarketer AI <ron@automarketer-ai.com>',
            'Reply-To: ron@automarketer-ai.com',
            'Content-Type: text/html; charset=UTF-8',
        ];
        wp_mail($email, $subject, $html, $headers);
    }

    // ── Send upgrade email (existing paid -> higher paid plan) ─────────────────
    private static function sendUpgradeEmail(string $email, string $firstName, string $newPlan, string $prevPlan): void
    {
        $planLabels = [
            'free'         => 'Free',
            'starter'      => 'Starter',
            'starter_pro'  => 'Starter Pro',
            'unlimited_ai' => 'Unlimited AI',
            'agency'       => 'Agency',
        ];
        $newLabel   = $planLabels[$newPlan]  ?? ucfirst($newPlan);
        $prevLabel  = $planLabels[$prevPlan] ?? ucfirst($prevPlan);
        $subject    = 'You upgraded - here is what is waiting for you';
        $modulesUrl = admin_url('admin.php?page=automarketer-ai-modules');

        $builderMap = [
            'starter'      => ['\AutoMarketerAI\MarketingSite', 'buildUpgradeEmailHtmlStarter'],
            'starter_pro'  => ['\AutoMarketerAI\MarketingSite', 'buildUpgradeEmailHtmlStarterPro'],
            'unlimited_ai' => ['\AutoMarketerAI\MarketingSite', 'buildUpgradeEmailHtmlUnlimitedAi'],
        ];

        if (isset($builderMap[$newPlan])) {
            $html = call_user_func($builderMap[$newPlan], $firstName, $modulesUrl);
        } else {
            $html = '<p>Hi ' . esc_html($firstName) . ', you upgraded to ' . esc_html($newLabel) . '. Everything is live on your site.</p>';
        }

        wp_mail($email, $subject, $html, [
            'Content-Type: text/html; charset=UTF-8',
            'From: AutoMarketer AI <ron@automarketer-ai.com>',
            'Reply-To: ron@automarketer-ai.com',
        ]);
    }
    // ── Send cancellation email to customer ────────────────────────────────
    private static function sendCancellationEmail(string $email, string $firstName): void
    {
        $subject = 'Your AutoMarketer AI subscription has been cancelled';
        $html    = MarketingSite::buildCancellationEmailHtml($firstName);
        wp_mail($email, $subject, $html, [
            'Content-Type: text/html; charset=UTF-8',
            'From: AutoMarketer AI <ron@automarketer-ai.com>',
            'Reply-To: ron@automarketer-ai.com',
        ]);
    }

    // ── Send admin notification to ron@ ─────────────────────────────────
    // ── Public entry point for free signup admin notification ───────────
    public static function notifyAdminFreeSignup(string $email): void
    {
        self::sendAdminNotification('free_signup', ['email' => $email]);
    }

    private static function sendAdminNotification(string $event, array $data): void
    {
        $adminEmail = 'ron@automarketer-ai.com';
        $email      = isset($data['email']) ? (string) $data['email'] : 'unknown';
        $plan       = isset($data['plan'])  ? (string) $data['plan']  : 'unknown';
        $prevPlan   = isset($data['prev_plan']) ? (string) $data['prev_plan'] : '';
        $refund     = isset($data['refund']) ? (int) $data['refund'] : 0;
        $currency   = isset($data['currency']) ? strtoupper((string) $data['currency']) : 'USD';
        $date       = gmdate('Y-m-d H:i:s') . ' UTC';

        switch ($event) {
            case 'subscription_active':
                $isUpgrade = $prevPlan !== '' && $prevPlan !== 'free' && $prevPlan !== $plan;
                $subject   = $isUpgrade
                    ? "[AutoMarketer AI] Upgrade - {$email}"
                    : "[AutoMarketer AI] New Paid Signup - {$email}";
                $message   = $isUpgrade
                    ? "Upgrade detected.\n\nEmail: {$email}\nNew Plan: {$plan}\nPrevious Plan: {$prevPlan}\nDate: {$date}\n"
                    : "New paid signup.\n\nEmail: {$email}\nPlan: {$plan}\nDate: {$date}\n";
                break;
            case 'subscription_cancelled':
                $formatted = $refund > 0 ? '$' . number_format($refund / 100, 2) . ' ' . $currency : 'none';
                $subject   = "[AutoMarketer AI] Cancellation - {$email}";
                $message   = "Subscription cancelled.\n\nEmail: {$email}\nPlan: {$plan}\nRefund: {$formatted}\nDate: {$date}\n";
                break;
            case 'free_signup':
                $subject = "[AutoMarketer AI] Free Signup - {$email}";
                $message = "New free signup.\n\nEmail: {$email}\nDate: {$date}\n";
                break;
            default:
                return;
        }

        wp_mail($adminEmail, $subject, $message, [
            'Content-Type: text/plain; charset=UTF-8',
            'From: AutoMarketer AI <ron@automarketer-ai.com>',
        ]);
    }

    private function verify_hmac(\WP_REST_Request $request): bool
    {
        $secret = defined('AMAI_WP_CALLBACK_SECRET') ? AMAI_WP_CALLBACK_SECRET : '';
        if ($secret === '') {
            return false;
        }

        $method    = 'POST';
        $path      = '/wp-json/amai/v1/subscription-update';
        $timestamp = $request->get_header('x_amai_timestamp') ?? '';
        $nonce     = $request->get_header('x_amai_nonce')     ?? '';
        $tenant    = $request->get_header('x_amai_tenant')    ?? '';
        $keyId     = $request->get_header('x_amai_key_id')    ?? '';
        $bodyB64   = $request->get_header('x_amai_body_sha256') ?? '';
        $signature = $request->get_header('x_amai_signature') ?? '';

        if ($timestamp === '' || $nonce === '' || $signature === '') {
            return false;
        }

        // Replay guard — reject requests older than 5 minutes
        $skew = abs(time() - (int) $timestamp);
        if ($skew > 300) {
            return false;
        }

        $body      = $request->get_body();
        $expected  = base64_encode(hash('sha256', $body, true));
        if (!hash_equals($expected, $bodyB64)) {
            return false;
        }

        $canonical = implode('|', [$method, $path, $timestamp, $nonce, $tenant, $keyId, $bodyB64]);
        $expected  = rtrim(base64_encode(hash_hmac('sha256', $canonical, hex2bin($secret), true)), "\n");

        return hash_equals($expected, $signature);
    }

    // ── Proxy a request to Core (HMAC signed) ──────────────────────────
    private function call_core(string $path, string $method, array $params): ?array
    {
        $coreUrl = defined('AMAI_CORE_URL') ? rtrim(AMAI_CORE_URL, '/') : 'https://core.automarketer-ai.com';
        $secret  = hex2bin((string) get_option('amai_tenant_hmac_secret', ''));
        $tenant  = amai_tenant_id();
        $keyId   = 'WP_PROXY';

        $url = $coreUrl . $path;

        if ($method === 'GET' && count($params) > 0) {
            $url .= '?' . http_build_query($params);
            $body = '';
        } else {
            $body = (string) json_encode($params, JSON_UNESCAPED_SLASHES);
        }

        $timestamp = (string) time();
        $nonce     = bin2hex(random_bytes(16));
        $bodyB64   = base64_encode(hash('sha256', $body, true));
        $canonical = implode('|', [$method, $path, $timestamp, $nonce, $tenant, $keyId, $bodyB64]);
        $signature = rtrim(base64_encode(hash_hmac('sha256', $canonical, $secret, true)), "\n");

        $args = [
            'method'  => $method,
            'timeout' => 20,
            'headers' => [
                'Content-Type'       => 'application/json',
                'X-Amai-Tenant'      => $tenant,
                'X-Amai-Key-Id'      => $keyId,
                'X-Amai-Timestamp'   => $timestamp,
                'X-Amai-Nonce'       => $nonce,
                'X-Amai-Body-SHA256' => $bodyB64,
                'X-Amai-Signature'   => $signature,
            ],
        ];

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

        $response = wp_remote_request($url, $args);

        if (is_wp_error($response)) {
            return null;
        }

        $decoded = json_decode(wp_remote_retrieve_body($response), true);
        return is_array($decoded) ? $decoded : null;
    }
}