<?php
if ( ! defined( 'ABSPATH' ) ) exit;

/**
 * VMO Twilio A2P 10DLC Registration Manager
 * 
 * Handles automated Brand & Campaign registration via Twilio Trust Hub API.
 * Designed for ISV model: VMO registers on behalf of all end-users.
 * Works as direct customer until ISV approval, then seamlessly upgrades.
 * 
 * API Endpoints Used:
 *   - Trust Hub v1: Customer Profiles, End Users, Supporting Documents
 *   - Messaging v1: Brand Registrations, Campaign Use Cases
 *   - Webhooks: Status change notifications for brand/campaign approval
 * 
 * @package VMO_AI_Website_Builder
 * @since 1.0.52
 */

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

class VMO_Twilio_A2P_Manager {

    /** Twilio API bases */
    private const TRUSTHUB_BASE = 'https://trusthub.twilio.com/v1';
    private const MESSAGING_BASE = 'https://messaging.twilio.com/v1';

    /**
     * VMO's Primary Customer Profile SID (Trust Hub).
     * This is the ISV's own approved profile. Each end-user's Secondary
     * Customer Profile is linked to this via EntityAssignment.
     * Override in wp-config.php: define('VMO_TWILIO_PRIMARY_PROFILE_SID', 'BUxxx...');
     */
    private const DEFAULT_PRIMARY_PROFILE_SID = 'BU787a1fd98ad44df4290686937fa627e4';

    /** A2P Trust Product Policy SID (for ISV TrustProduct creation) */
    private const A2P_TRUST_PRODUCT_POLICY_SID = 'RNb0d4771c2c98518d916a3d4cd70a8f8b';

    /** @var string */
    private $account_sid;
    /** @var string */
    private $auth_token;
    /** @var string */
    private $primary_profile_sid;

    /** @var self|null */
    private static $instance = null;

    /**
     * Singleton accessor.
     */
    public static function get_instance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    private function __construct() {
        $this->account_sid = defined('VMO_TWILIO_ACCOUNT_SID')
            ? VMO_TWILIO_ACCOUNT_SID
            : get_option('vmo_twilio_account_sid', '');
        $this->auth_token = defined('VMO_TWILIO_AUTH_TOKEN')
            ? VMO_TWILIO_AUTH_TOKEN
            : get_option('vmo_twilio_auth_token', '');
        $this->primary_profile_sid = defined('VMO_TWILIO_PRIMARY_PROFILE_SID')
            ? VMO_TWILIO_PRIMARY_PROFILE_SID
            : self::DEFAULT_PRIMARY_PROFILE_SID;

        // AJAX endpoints for the in-plugin A2P registration form
        add_action('wp_ajax_vmo_a2p_register_brand', array($this, 'ajax_register_brand'));
        add_action('wp_ajax_vmo_a2p_register_campaign', array($this, 'ajax_register_campaign'));
        add_action('wp_ajax_vmo_a2p_check_status', array($this, 'ajax_check_status'));
        add_action('wp_ajax_vmo_a2p_create_messaging_service', array($this, 'ajax_create_messaging_service'));

        // Webhook handler (public — Twilio sends without auth)
        add_action('wp_ajax_nopriv_vmo_a2p_status_webhook', array($this, 'handle_status_webhook'));
        add_action('wp_ajax_vmo_a2p_status_webhook', array($this, 'handle_status_webhook'));

        // Fallback poll in case webhook delivery is delayed or unavailable.
        add_action('vmo_a2p_status_poll', array($this, 'poll_pending_registrations'));
        if (!wp_next_scheduled('vmo_a2p_status_poll')) {
            wp_schedule_event(time() + 900, 'hourly', 'vmo_a2p_status_poll');
        }
    }

    /**
     * Normalize registration statuses into a consistent internal shape.
     *
     * @param string $status
     * @return string
     */
    private function normalize_status_value(string $status): string {
        $normalized = strtoupper(trim($status));
        return $normalized === '' ? 'NOT_STARTED' : $normalized;
    }

    /**
     * Human-readable status label for emails and alerts.
     *
     * @param string $status
     * @return string
     */
    private function humanize_status(string $status): string {
        $normalized = $this->normalize_status_value($status);
        if ($normalized === 'NOT_STARTED') {
            return 'Not started';
        }
        if ($normalized === 'IN_PROGRESS') {
            return 'In progress';
        }
        return ucwords(strtolower(str_replace('_', ' ', $normalized)));
    }

    /**
     * Resolve the business label used in user-facing messages.
     *
     * @param int $user_id
     * @return string
     */
    private function get_a2p_business_label(int $user_id): string {
        $business_name = (string) get_user_meta($user_id, 'vmo_a2p_business_name', true);
        if ($business_name === '') {
            $business_name = (string) get_user_meta($user_id, 'vmo_business_name', true);
        }

        if ($business_name !== '') {
            return $business_name;
        }

        $user = get_userdata($user_id);
        if ($user && !empty($user->display_name)) {
            return (string) $user->display_name;
        }

        return 'VMO Business #' . $user_id;
    }

    /**
     * Resolve the destination email for registration updates.
     *
     * @param int $user_id
     * @return string
     */
    private function get_a2p_contact_email(int $user_id): string {
        $contact_email = sanitize_email((string) get_user_meta($user_id, 'vmo_a2p_contact_email', true));
        if ($contact_email !== '') {
            return $contact_email;
        }

        $user = get_userdata($user_id);
        if ($user && !empty($user->user_email)) {
            return sanitize_email((string) $user->user_email);
        }

        return '';
    }

    /**
     * Convert the current A2P state into a single user-facing guidance payload.
     *
     * @param int   $user_id
     * @param array $status
     * @return array<string,mixed>
     */
    private function build_a2p_status_guidance(int $user_id, array $status): array {
        $brand_status = $this->normalize_status_value((string) ($status['brand_status'] ?? get_user_meta($user_id, 'vmo_a2p_brand_status', true)));
        $campaign_status = $this->normalize_status_value((string) ($status['campaign_status'] ?? get_user_meta($user_id, 'vmo_a2p_campaign_status', true)));
        $sms_status = strtolower((string) ($status['sms_status'] ?? get_user_meta($user_id, 'vmo_sms_activated', true)));
        $sms_active = !empty($status['sms_active'])
            || $sms_status === 'active'
            || $sms_status === '1'
            || in_array($campaign_status, array('VERIFIED', 'APPROVED', 'ACTIVE'), true);
        $tracked_in_plugin = array_key_exists('tracked_in_plugin', $status)
            ? (bool) $status['tracked_in_plugin']
            : ((string) get_user_meta($user_id, 'vmo_a2p_brand_sid', true) !== ''
                || (string) get_user_meta($user_id, 'vmo_a2p_campaign_sid', true) !== ''
                || (string) get_user_meta($user_id, 'vmo_a2p_messaging_service_sid', true) !== '');

        $business_name = $this->get_a2p_business_label($user_id);
        $carrier_note = 'These steps are required by US carriers and compliance partners for local-number business texting. They are not VMO-created requirements.';
        $billing_note = 'Any one-time or monthly registration amounts are external carrier/compliance charges. VMO only applies them against your VMO credit balance so you do not need separate billing on another platform.';
        $status_summary = sprintf(
            'Brand status: %s. Campaign status: %s. SMS capability: %s.',
            $this->humanize_status($brand_status),
            $this->humanize_status($campaign_status),
            $sms_active ? 'Active' : 'Inactive'
        );

        $guidance = array(
            'stage' => 'not_started',
            'severity' => 'info',
            'title' => '',
            'summary' => '',
            'next_step' => '',
            'alert_message' => '',
            'email_subject' => '',
            'carrier_note' => $carrier_note,
            'billing_note' => $billing_note,
            'status_summary' => $status_summary,
            'business_name' => $business_name,
            'brand_status' => $brand_status,
            'campaign_status' => $campaign_status,
            'tracked_in_plugin' => $tracked_in_plugin,
            'sms_active' => $sms_active,
        );

        if ($sms_active) {
            $guidance['stage'] = 'sms_active';
            $guidance['severity'] = 'success';
            $guidance['title'] = 'SMS is now active in VMO';
            $guidance['summary'] = 'All required carrier registration steps are complete. This business number can now send and receive SMS inside VMO.';
            $guidance['next_step'] = 'No action is required. You can begin using the SMS workspace immediately.';
            $guidance['alert_message'] = 'All required carrier registration steps are complete. SMS is now active in VMO.';
            $guidance['email_subject'] = 'VMO SMS update: your number is now active';
            return $guidance;
        }

        if (in_array($brand_status, array('FAILED', 'REJECTED'), true) || in_array($campaign_status, array('FAILED', 'REJECTED'), true)) {
            $guidance['stage'] = 'needs_attention';
            $guidance['severity'] = 'error';
            $guidance['title'] = 'SMS registration needs attention';
            $guidance['summary'] = 'Carrier review returned a status that needs attention before SMS can go live.';
            $guidance['next_step'] = 'Open SMS Activation in VMO, review the latest registration status, and update the submission details if the carrier requested changes.';
            $guidance['alert_message'] = 'Carrier review needs attention. Open SMS Activation in VMO to review the latest status.';
            $guidance['email_subject'] = 'VMO SMS update: action required on your registration';
            return $guidance;
        }

        if (!$tracked_in_plugin) {
            $guidance['stage'] = 'sync_needed';
            $guidance['severity'] = 'warning';
            $guidance['title'] = 'Existing SMS approval still needs to be synced into VMO';
            $guidance['summary'] = 'VMO sees a business number for this account, but it has not imported a completed carrier approval record yet.';
            $guidance['next_step'] = 'After the external registration is fully finished, use Check Registration Status inside VMO so the approved number can be synced and activated.';
            $guidance['alert_message'] = 'Your number still needs to be synced into VMO after the external registration is fully finished.';
            $guidance['email_subject'] = 'VMO SMS update: finish syncing your approved number';
            return $guidance;
        }

        if (in_array($brand_status, array('APPROVED', 'VERIFIED'), true) && $campaign_status === 'NOT_STARTED') {
            $guidance['stage'] = 'campaign_required';
            $guidance['severity'] = 'warning';
            $guidance['title'] = 'Brand approved: one more registration step is still required';
            $guidance['summary'] = 'Your business brand is approved, but SMS cannot go live until the messaging campaign is completed inside VMO.';
            $guidance['next_step'] = 'Open SMS Activation in VMO and submit the campaign registration step. That campaign step is a carrier/compliance requirement, not a VMO-created requirement.';
            $guidance['alert_message'] = 'Brand approval is complete. Submit the messaging campaign inside VMO to finish SMS activation.';
            $guidance['email_subject'] = 'VMO SMS update: your campaign step is now required';
            return $guidance;
        }

        if ($campaign_status !== 'NOT_STARTED' && !in_array($campaign_status, array('VERIFIED', 'APPROVED', 'ACTIVE'), true)) {
            $guidance['stage'] = 'campaign_pending';
            $guidance['severity'] = 'info';
            $guidance['title'] = 'Campaign submitted and under review';
            $guidance['summary'] = 'Your messaging campaign has been submitted for carrier review. SMS will activate automatically once that review is approved.';
            $guidance['next_step'] = 'No action is usually required right now unless the carrier asks for changes. VMO will keep checking and will alert you when approval is complete.';
            $guidance['alert_message'] = 'Your messaging campaign is under carrier review. VMO will alert you when approval is complete.';
            $guidance['email_subject'] = 'VMO SMS update: your campaign is under review';
            return $guidance;
        }

        if ($brand_status !== 'NOT_STARTED' && !in_array($brand_status, array('APPROVED', 'VERIFIED'), true)) {
            $guidance['stage'] = 'brand_pending';
            $guidance['severity'] = 'info';
            $guidance['title'] = 'Brand registration submitted and under review';
            $guidance['summary'] = 'Your business brand details were submitted for review. The campaign step cannot be finalized until the brand is approved.';
            $guidance['next_step'] = 'No action is required right now. VMO will alert you as soon as brand approval is complete and the campaign step is ready.';
            $guidance['alert_message'] = 'Your brand registration is under review. VMO will alert you when the campaign step becomes available.';
            $guidance['email_subject'] = 'VMO SMS update: your brand is under review';
            return $guidance;
        }

        return $guidance;
    }

    /**
     * Queue a notification for the next dashboard load without overwriting
     * unrelated pending notifications.
     *
     * @param int   $user_id
     * @param array $notification
     * @return void
     */
    private function append_pending_notification(int $user_id, array $notification): void {
        $existing = get_user_meta($user_id, 'vmo_pending_notification', true);
        $items = array();

        if (is_string($existing) && $existing !== '') {
            $decoded = json_decode($existing, true);
            if (is_array($decoded)) {
                $is_list = empty($decoded) || array_keys($decoded) === range(0, count($decoded) - 1);
                $items = $is_list ? $decoded : array($decoded);
            }
        } elseif (is_array($existing)) {
            $is_list = empty($existing) || array_keys($existing) === range(0, count($existing) - 1);
            $items = $is_list ? $existing : array($existing);
        }

        $items[] = $notification;
        if (count($items) > 5) {
            $items = array_slice($items, -5);
        }

        update_user_meta($user_id, 'vmo_pending_notification', wp_json_encode($items));
    }

    /**
     * Send a white-labeled A2P status email.
     *
     * @param int   $user_id
     * @param array $guidance
     * @return void
     */
    private function send_a2p_status_email(int $user_id, array $guidance): void {
        $recipient = $this->get_a2p_contact_email($user_id);
        if ($recipient === '') {
            return;
        }

        if (!class_exists('VMO_Local_Mailer')) {
            $mailer_path = dirname(__FILE__) . '/class-vmo-local-mailer.php';
            if (file_exists($mailer_path)) {
                require_once $mailer_path;
            }
        }

        $business_name = esc_html((string) ($guidance['business_name'] ?? $this->get_a2p_business_label($user_id)));
        $title = esc_html((string) ($guidance['title'] ?? 'SMS registration update'));
        $summary = esc_html((string) ($guidance['summary'] ?? ''));
        $next_step = esc_html((string) ($guidance['next_step'] ?? ''));
        $status_summary = esc_html((string) ($guidance['status_summary'] ?? ''));
        $carrier_note = esc_html((string) ($guidance['carrier_note'] ?? ''));
        $billing_note = esc_html((string) ($guidance['billing_note'] ?? ''));
        $subject = (string) ($guidance['email_subject'] ?? 'VMO SMS registration update');

        $message = '';
        $message .= '<div style="font-family:Arial,sans-serif;max-width:640px;margin:0 auto;padding:24px;background:#ffffff;color:#111827;line-height:1.6;">';
        $message .= '<h2 style="margin:0 0 12px;font-size:24px;color:#111827;">' . $title . '</h2>';
        $message .= '<p style="margin:0 0 14px;color:#374151;">Business: <strong>' . $business_name . '</strong></p>';
        $message .= '<p style="margin:0 0 14px;color:#374151;">' . $summary . '</p>';
        $message .= '<div style="margin:18px 0;padding:16px;border:1px solid #e5e7eb;border-radius:12px;background:#f9fafb;">';
        $message .= '<p style="margin:0 0 8px;font-weight:700;color:#111827;">Current status</p>';
        $message .= '<p style="margin:0;color:#374151;">' . $status_summary . '</p>';
        $message .= '</div>';
        $message .= '<div style="margin:18px 0;padding:16px;border-left:4px solid #2563eb;background:#eff6ff;border-radius:8px;">';
        $message .= '<p style="margin:0 0 8px;font-weight:700;color:#1d4ed8;">What you need to do</p>';
        $message .= '<p style="margin:0;color:#1f2937;">' . $next_step . '</p>';
        $message .= '</div>';
        $message .= '<div style="margin:18px 0 0;padding:16px;border:1px solid #fcd34d;border-radius:12px;background:#fffbeb;">';
        $message .= '<p style="margin:0 0 10px;font-weight:700;color:#92400e;">Why this step exists</p>';
        $message .= '<p style="margin:0 0 10px;color:#78350f;">' . $carrier_note . '</p>';
        $message .= '<p style="margin:0;color:#78350f;">' . $billing_note . '</p>';
        $message .= '</div>';
        $message .= '<p style="margin:20px 0 0;color:#4b5563;">VMO will keep checking the registration status and will alert you again when approval is complete or if another step still needs your attention.</p>';
        $message .= '</div>';

        $headers = array('Content-Type: text/html; charset=UTF-8');
        $sent = false;

        if (class_exists('VMO_Local_Mailer')) {
            $mailer = new VMO_Local_Mailer();
            $sent = (bool) $mailer->send($recipient, $subject, $message, array(
                'headers' => $headers,
                'skip_storage' => true,
                'charge_tokens' => false,
                'from_name' => 'VMO',
            ));
        } else {
            $sent = wp_mail($recipient, $subject, $message, $headers);
        }

        if (!$sent) {
            error_log('[VMO A2P] Failed sending status email to user #' . $user_id . ' (' . $recipient . ')');
        }
    }

    /**
     * Dispatch one email and one queued dashboard alert when the user moves into
     * a new A2P stage.
     *
     * @param int    $user_id
     * @param array  $status
     * @param string $source
     * @return void
     */
    private function maybe_notify_status_change(int $user_id, array $status, string $source = ''): void {
        $guidance = $this->build_a2p_status_guidance($user_id, $status);
        $stage = (string) ($guidance['stage'] ?? 'not_started');
        if ($stage === 'not_started') {
            return;
        }

        $fingerprint = $stage;
        if ($stage === 'needs_attention') {
            $fingerprint .= '|' . (string) ($guidance['brand_status'] ?? '') . '|' . (string) ($guidance['campaign_status'] ?? '');
        }

        $last_fingerprint = (string) get_user_meta($user_id, 'vmo_a2p_last_notification_fingerprint', true);
        if ($last_fingerprint === $fingerprint) {
            return;
        }

        update_user_meta($user_id, 'vmo_a2p_last_notification_fingerprint', $fingerprint);
        update_user_meta($user_id, 'vmo_a2p_last_notification_stage', $stage);
        update_user_meta($user_id, 'vmo_a2p_last_notification_at', current_time('mysql'));
        if ($source !== '') {
            update_user_meta($user_id, 'vmo_a2p_last_notification_source', $source);
        }

        $this->append_pending_notification($user_id, array(
            'type' => 'a2p_status',
            'level' => (string) ($guidance['severity'] ?? 'info'),
            'title' => (string) ($guidance['title'] ?? 'SMS registration update'),
            'message' => trim((string) ($guidance['alert_message'] ?? '')),
            'created' => current_time('mysql'),
        ));

        $this->send_a2p_status_email($user_id, $guidance);
    }

    /**
     * Convert backend errors into white-labeled user-facing messages.
     *
     * @param WP_Error $error
     * @param string   $fallback
     * @return string
     */
    private function get_public_error_message(WP_Error $error, string $fallback): string {
        switch ((string) $error->get_error_code()) {
            case 'twilio_missing':
                return 'SMS registration is not available right now because the phone system configuration is incomplete.';
            case 'no_brand':
                return 'Complete the brand registration step inside VMO before submitting the campaign.';
            case 'brand_not_approved':
                return 'Your brand still needs carrier approval before the campaign step can be submitted.';
            case 'no_messaging_service':
                return 'VMO could not prepare the messaging service for this number yet. Please try again.';
            case 'service_unavailable':
                return 'VMO could not verify the current SMS registration right now. Please try again.';
            case 'service_not_approved':
                return 'Carrier approval is not complete yet for this number.';
            default:
                return $fallback;
        }
    }

    /**
     * Poll existing A2P registrations so users still get updates if webhooks are
     * delayed or unavailable.
     *
     * @return void
     */
    public function poll_pending_registrations(): void {
        global $wpdb;

        $meta_keys = array('vmo_a2p_brand_sid', 'vmo_a2p_campaign_sid', 'vmo_a2p_messaging_service_sid');
        $placeholders = implode(',', array_fill(0, count($meta_keys), '%s'));
        $query = $wpdb->prepare(
            "SELECT DISTINCT user_id FROM {$wpdb->usermeta} WHERE meta_key IN ({$placeholders}) AND meta_value <> '' LIMIT 200",
            ...$meta_keys
        );
        $user_ids = $wpdb->get_col($query);

        if (empty($user_ids)) {
            return;
        }

        foreach ($user_ids as $user_id) {
            $user_id = absint($user_id);
            if (!$user_id) {
                continue;
            }

            $sms_status = strtolower((string) get_user_meta($user_id, 'vmo_sms_activated', true));
            if ($sms_status === 'active' || $sms_status === '1') {
                continue;
            }

            $this->check_status($user_id);
        }
    }

    // ═══════════════════════════════════════════════════════════════════
    // TWILIO HTTP HELPERS
    // ═══════════════════════════════════════════════════════════════════

    /**
     * Make an authenticated request to Twilio.
     *
     * @param string $method  GET|POST|PUT|DELETE
     * @param string $url     Full URL
     * @param array  $body    POST body (form-encoded)
     * @return array|WP_Error Decoded JSON response or error
     */
    private function twilio_request(string $method, string $url, array $body = []) {
        if (empty($this->account_sid) || empty($this->auth_token)) {
            return new WP_Error('twilio_missing', 'Twilio credentials not configured.');
        }

        $args = array(
            'method'  => $method,
            'headers' => array(
                'Authorization' => 'Basic ' . base64_encode($this->account_sid . ':' . $this->auth_token), // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- HTTP Basic auth header
            ),
            'timeout' => 30,
        );

        if ($method === 'POST' || $method === 'PUT') {
            $args['headers']['Content-Type'] = 'application/x-www-form-urlencoded';
            $args['body'] = $body;
        }

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

        if (is_wp_error($response)) {
            error_log('[VMO A2P] HTTP error: ' . $response->get_error_message());
            return $response;
        }

        $code = wp_remote_retrieve_response_code($response);
        $json = json_decode(wp_remote_retrieve_body($response), true);

        if ($code >= 200 && $code < 300) {
            return $json;
        }

        $err_msg = isset($json['message']) ? $json['message'] : 'HTTP ' . $code;
        error_log('[VMO A2P] API error: ' . $err_msg . ' | URL: ' . $url);
        return new WP_Error('twilio_api_error', $err_msg, array('code' => $code, 'body' => $json));
    }

    /**
     * Resolve the user's Twilio phone SID and number from local storage first,
     * then fall back to Twilio lookups if the install predates the in-plugin A2P flow.
     *
     * @param int $user_id
     * @return array{phone_sid:string,phone_number:string}
     */
    private function get_user_phone_context(int $user_id): array {
        global $wpdb;

        $phone_sid = (string) get_user_meta($user_id, 'vmo_twilio_phone_sid', true);
        if ($phone_sid === '') {
            $phone_sid = (string) get_user_meta($user_id, 'vmo_phone_sid', true);
        }

        $phone_number = (string) get_user_meta($user_id, 'vmo_twilio_phone_number', true);
        $meta_phone = get_user_meta($user_id, 'vmo_phone_number', true);
        if (is_array($meta_phone)) {
            if ($phone_sid === '' && !empty($meta_phone['sid'])) {
                $phone_sid = (string) $meta_phone['sid'];
            }
            if ($phone_number === '') {
                $phone_number = (string) ($meta_phone['number'] ?? $meta_phone['phone_number'] ?? '');
            }
        } elseif ($phone_number === '' && !empty($meta_phone)) {
            $phone_number = (string) $meta_phone;
        }

        if (($phone_sid === '' || $phone_number === '') && class_exists('VMO_Twilio_Manager')) {
            try {
                $manager_phone = VMO_Twilio_Manager::get_instance()->get_user_phone($user_id);
                if (is_string($manager_phone) && $manager_phone !== '' && $phone_number === '') {
                    $phone_number = $manager_phone;
                }
            } catch (Throwable $e) {
                // Non-fatal: fall back to database/Twilio lookup.
            }
        }

        $table = $wpdb->prefix . 'vmo_phone_numbers';
        $table_exists = function_exists('vmo_table_exists')
            ? vmo_table_exists($table)
            : ($wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) === $table);

        if (($phone_sid === '' || $phone_number === '') && $table_exists) {
            $row = $wpdb->get_row($wpdb->prepare(
                "SELECT * FROM {$table} WHERE user_id = %d AND status IN ('active','pending_port') ORDER BY created_at DESC LIMIT 1",
                $user_id
            ));

            if ($row) {
                if ($phone_sid === '' && !empty($row->twilio_sid)) {
                    $phone_sid = (string) $row->twilio_sid;
                }
                if ($phone_number === '') {
                    foreach (array('phone_number', 'phone', 'number', 'twilio_number') as $column) {
                        if (!empty($row->{$column})) {
                            $phone_number = (string) $row->{$column};
                            break;
                        }
                    }
                }
            }
        }

        if ($phone_sid === '' && $phone_number !== '') {
            $lookup_url = add_query_arg(array(
                'PhoneNumber' => $phone_number,
                'PageSize' => 20,
            ), 'https://api.twilio.com/2010-04-01/Accounts/' . rawurlencode($this->account_sid) . '/IncomingPhoneNumbers.json');
            $owned = $this->twilio_request('GET', $lookup_url);
            if (!is_wp_error($owned) && !empty($owned['incoming_phone_numbers'][0]['sid'])) {
                $phone_sid = (string) $owned['incoming_phone_numbers'][0]['sid'];
                if ($phone_number === '' && !empty($owned['incoming_phone_numbers'][0]['phone_number'])) {
                    $phone_number = (string) $owned['incoming_phone_numbers'][0]['phone_number'];
                }
            }
        }

        if ($phone_sid !== '' && $phone_number === '') {
            $phone = $this->twilio_request(
                'GET',
                'https://api.twilio.com/2010-04-01/Accounts/' . rawurlencode($this->account_sid) . '/IncomingPhoneNumbers/' . rawurlencode($phone_sid) . '.json'
            );
            if (!is_wp_error($phone) && !empty($phone['phone_number'])) {
                $phone_number = (string) $phone['phone_number'];
            }
        }

        if ($phone_sid !== '') {
            update_user_meta($user_id, 'vmo_twilio_phone_sid', $phone_sid);
        }
        if ($phone_number !== '' && (string) get_user_meta($user_id, 'vmo_twilio_phone_number', true) === '') {
            update_user_meta($user_id, 'vmo_twilio_phone_number', $phone_number);
        }

        return array(
            'phone_sid' => $phone_sid,
            'phone_number' => $phone_number,
        );
    }

    /**
     * Fetch a Messaging Service by SID.
     *
     * @param string $service_sid
     * @return array|WP_Error
     */
    private function fetch_messaging_service(string $service_sid) {
        if ($service_sid === '') {
            return new WP_Error('missing_service_sid', 'Messaging Service SID is required.');
        }

        return $this->twilio_request('GET', self::MESSAGING_BASE . '/Services/' . rawurlencode($service_sid));
    }

    /**
     * Find the Messaging Service currently linked to a phone SID.
     *
     * @param string $phone_sid
     * @param string $phone_number
     * @return array|WP_Error
     */
    private function find_messaging_service_by_phone(string $phone_sid, string $phone_number = '') {
        if ($phone_sid === '' && $phone_number === '') {
            return new WP_Error('missing_phone_context', 'Phone SID or phone number is required.');
        }

        $services_url = self::MESSAGING_BASE . '/Services?PageSize=200';
        $service_page = 0;

        while ($services_url !== '' && $service_page < 25) {
            $services = $this->twilio_request('GET', $services_url);
            if (is_wp_error($services)) {
                return $services;
            }

            foreach (($services['services'] ?? array()) as $service) {
                $service_sid = (string) ($service['sid'] ?? '');
                if ($service_sid === '') {
                    continue;
                }

                $phone_url = self::MESSAGING_BASE . '/Services/' . rawurlencode($service_sid) . '/PhoneNumbers?PageSize=200';
                $phone_page = 0;

                while ($phone_url !== '' && $phone_page < 25) {
                    $numbers = $this->twilio_request('GET', $phone_url);
                    if (is_wp_error($numbers)) {
                        break;
                    }

                    foreach (($numbers['phone_numbers'] ?? array()) as $service_phone) {
                        $service_phone_sid = (string) ($service_phone['sid'] ?? '');
                        $service_phone_number = (string) ($service_phone['phone_number'] ?? '');
                        if (($phone_sid !== '' && $service_phone_sid === $phone_sid)
                            || ($phone_number !== '' && $service_phone_number === $phone_number)) {
                            $full_service = $this->fetch_messaging_service($service_sid);
                            return is_wp_error($full_service) ? $service : $full_service;
                        }
                    }

                    $phone_url = (string) ($numbers['meta']['next_page_url'] ?? '');
                    $phone_page++;
                }
            }

            $services_url = (string) ($services['meta']['next_page_url'] ?? '');
            $service_page++;
        }

        return new WP_Error('service_not_found', 'No Messaging Service is linked to this phone number.');
    }

    /**
     * Extract one campaign record from Twilio's UsAppToPerson responses.
     *
     * @param array $payload
     * @return array<string,string>
     */
    private function extract_campaign_record(array $payload): array {
        if (!empty($payload['sid']) || !empty($payload['campaign_status'])) {
            return array(
                'sid' => (string) ($payload['sid'] ?? ''),
                'campaign_status' => (string) ($payload['campaign_status'] ?? ''),
                'brand_registration_sid' => (string) ($payload['brand_registration_sid'] ?? ''),
                'usecase' => (string) ($payload['us_app_to_person_usecase'] ?? $payload['us_app_to_person_use_case'] ?? $payload['usecase'] ?? ''),
            );
        }

        foreach (array('us_app_to_person', 'us_app_to_persons', 'items') as $key) {
            if (empty($payload[$key]) || !is_array($payload[$key])) {
                continue;
            }

            $collection = $payload[$key];
            if (isset($collection['sid']) || isset($collection['campaign_status'])) {
                return $this->extract_campaign_record($collection);
            }

            foreach ($collection as $item) {
                if (!is_array($item)) {
                    continue;
                }
                $record = $this->extract_campaign_record($item);
                if (!empty($record['sid']) || !empty($record['campaign_status'])) {
                    return $record;
                }
            }
        }

        return array();
    }

    /**
     * Discover and import external A2P registration state for installs that had
     * SMS registration completed before VMO tracked the Twilio SIDs locally.
     *
     * @param int  $user_id
     * @param bool $force
     * @return array<string,mixed>
     */
    public function discover_existing_registration(int $user_id, bool $force = false): array {
        $cache_key = 'vmo_a2p_discovery_' . $user_id;
        if (!$force) {
            $cached = get_transient($cache_key);
            if (is_array($cached)) {
                return !empty($cached['_miss']) ? array() : $cached;
            }
        }

        $result = array();
        $phone = $this->get_user_phone_context($user_id);
        $phone_sid = (string) ($phone['phone_sid'] ?? '');
        $phone_number = (string) ($phone['phone_number'] ?? '');

        if ($phone_sid === '' && $phone_number === '') {
            set_transient($cache_key, array('_miss' => 1), MINUTE_IN_SECONDS);
            return array();
        }

        $service_sid = (string) get_user_meta($user_id, 'vmo_a2p_messaging_service_sid', true);
        $service = $service_sid !== '' ? $this->fetch_messaging_service($service_sid) : null;
        if (is_wp_error($service)) {
            $service = null;
        }

        if (!is_array($service) || empty($service['sid'])) {
            $service = $this->find_messaging_service_by_phone($phone_sid, $phone_number);
            if (is_wp_error($service) || empty($service['sid'])) {
                set_transient($cache_key, array('_miss' => 1), MINUTE_IN_SECONDS);
                return array();
            }
        }

        $service_sid = (string) ($service['sid'] ?? '');
        $service_registered = !empty($service['us_app_to_person_registered']);
        if ($service_sid !== '') {
            update_user_meta($user_id, 'vmo_a2p_messaging_service_sid', $service_sid);
            $result['tracked_in_plugin'] = true;
        }

        $campaign_sid = (string) get_user_meta($user_id, 'vmo_a2p_campaign_sid', true);
        $campaign_payload = array();
        if ($service_sid !== '' && $campaign_sid !== '') {
            $campaign_payload = $this->twilio_request('GET', self::MESSAGING_BASE . '/Services/' . rawurlencode($service_sid) . '/UsAppToPerson/' . rawurlencode($campaign_sid));
            if (is_wp_error($campaign_payload)) {
                $campaign_payload = array();
            }
        }

        if ($service_sid !== '' && empty($campaign_payload) && !empty($service['links']['us_app_to_person'])) {
            $campaign_payload = $this->twilio_request('GET', (string) $service['links']['us_app_to_person']);
            if (is_wp_error($campaign_payload)) {
                $campaign_payload = array();
            }
        }

        if ($service_sid !== '' && empty($campaign_payload) && !empty($service['us_app_to_person_registered'])) {
            $campaign_payload = $this->twilio_request('GET', self::MESSAGING_BASE . '/Services/' . rawurlencode($service_sid) . '/UsAppToPerson');
            if (is_wp_error($campaign_payload)) {
                $campaign_payload = array();
            }
        }

        $campaign = $this->extract_campaign_record(is_array($campaign_payload) ? $campaign_payload : array());
        $campaign_status = (string) ($campaign['campaign_status'] ?? get_user_meta($user_id, 'vmo_a2p_campaign_status', true));
        $brand_status = (string) get_user_meta($user_id, 'vmo_a2p_brand_status', true);

        if (!empty($campaign['sid'])) {
            update_user_meta($user_id, 'vmo_a2p_campaign_sid', (string) $campaign['sid']);
        }
        if ($campaign_status !== '') {
            update_user_meta($user_id, 'vmo_a2p_campaign_status', $campaign_status);
        } elseif ($service_registered) {
            $campaign_status = 'REGISTERED';
            update_user_meta($user_id, 'vmo_a2p_campaign_status', $campaign_status);
        }
        if (!empty($campaign['usecase'])) {
            update_user_meta($user_id, 'vmo_a2p_campaign_use_case', (string) $campaign['usecase']);
        }
        if (!empty($campaign['brand_registration_sid'])) {
            update_user_meta($user_id, 'vmo_a2p_brand_sid', (string) $campaign['brand_registration_sid']);
        }

        if ($campaign_status !== '' && $brand_status === '') {
            $brand_status = 'APPROVED';
            update_user_meta($user_id, 'vmo_a2p_brand_status', $brand_status);
        }

        $normalized_campaign = strtoupper($campaign_status);
        if (in_array($normalized_campaign, array('VERIFIED', 'APPROVED', 'ACTIVE'), true)) {
            update_user_meta($user_id, 'vmo_sms_activated', 'active');
            if ((string) get_user_meta($user_id, 'vmo_sms_activated_date', true) === '') {
                update_user_meta($user_id, 'vmo_sms_activated_date', current_time('mysql'));
            }
            $result['sms_active'] = true;
            $result['sms_status'] = 'active';
            $result['sms_activated_at'] = (string) get_user_meta($user_id, 'vmo_sms_activated_date', true);
        }

        $result['service_registered'] = $service_registered;
        $result['brand_status'] = (string) (get_user_meta($user_id, 'vmo_a2p_brand_status', true) ?: $brand_status ?: 'not_started');
        $result['campaign_status'] = (string) (get_user_meta($user_id, 'vmo_a2p_campaign_status', true) ?: ($campaign_status !== '' ? $campaign_status : 'not_started'));
        $result['checked_at'] = current_time('mysql');

        delete_transient($cache_key);
        return $result;
    }

    /**
     * One-time validation path for an already approved number that predates
     * VMO's in-plugin registration tracking.
     *
     * @param int $user_id
     * @return array|WP_Error
     */
    public function validate_existing_number(int $user_id) {
        $discovered = $this->discover_existing_registration($user_id, true);
        if (!empty($discovered['sms_active'])) {
            $status = $this->check_status($user_id);
            $status['validated_existing'] = true;
            return $status;
        }

        $service_sid = (string) get_user_meta($user_id, 'vmo_a2p_messaging_service_sid', true);
        if ($service_sid === '') {
            return new WP_Error('no_existing_registration', 'VMO could not find an existing approved SMS registration for this number yet.');
        }

        $service = $this->fetch_messaging_service($service_sid);
        if (is_wp_error($service) || empty($service['sid'])) {
            return new WP_Error('service_unavailable', 'VMO could not verify the existing registration for this number right now.');
        }

        if (empty($service['us_app_to_person_registered'])) {
            return new WP_Error('service_not_approved', 'VMO did not find a confirmed carrier approval on this number yet.');
        }

        if ((string) get_user_meta($user_id, 'vmo_a2p_brand_status', true) === '') {
            update_user_meta($user_id, 'vmo_a2p_brand_status', 'APPROVED');
        }

        $campaign_status = strtoupper((string) get_user_meta($user_id, 'vmo_a2p_campaign_status', true));
        if ($campaign_status === '' || in_array($campaign_status, array('NOT_STARTED', 'REGISTERED', 'PENDING', 'IN_PROGRESS'), true)) {
            update_user_meta($user_id, 'vmo_a2p_campaign_status', 'VERIFIED');
        }

        update_user_meta($user_id, 'vmo_sms_activated', 'active');
        if ((string) get_user_meta($user_id, 'vmo_sms_activated_date', true) === '') {
            update_user_meta($user_id, 'vmo_sms_activated_date', current_time('mysql'));
        }
        update_user_meta($user_id, 'vmo_a2p_imported_existing', '1');
        update_user_meta($user_id, 'vmo_a2p_imported_existing_at', current_time('mysql'));

        $status = $this->check_status($user_id);
        $status['validated_existing'] = true;
        return $status;
    }

    // ═══════════════════════════════════════════════════════════════════
    // 1. BRAND REGISTRATION (Trust Hub Customer Profile + A2P Brand)
    // ═══════════════════════════════════════════════════════════════════

    /**
     * Register a user's business as an A2P Brand.
     *
     * Steps:
     *   1. Create a Customer Profile (Trust Product)
     *   2. Create an End User entity and attach to profile
     *   3. Submit profile for review
     *   4. Create A2P Brand Registration linked to profile
     *
     * @param int   $user_id  WP user ID
     * @param array $business Business details:
     *   - business_name     (string) Legal business name
     *   - ein               (string) EIN / Tax ID
     *   - business_type     (string) e.g. 'Partnership', 'Corporation', 'Sole Proprietorship'
     *   - industry          (string) e.g. 'TECHNOLOGY', 'RETAIL'
     *   - website           (string) Business website URL
     *   - address_street    (string)
     *   - address_city      (string)
     *   - address_state     (string) 2-letter code
     *   - address_zip       (string)
     *   - address_country   (string) ISO 3166-1 alpha-2 (default 'US')
     *   - contact_first     (string) Authorized contact first name
     *   - contact_last      (string) Authorized contact last name
     *   - contact_email     (string)
     *   - contact_phone     (string, E.164)
     * @return array|WP_Error Brand registration data or error
     */
    public function register_brand(int $user_id, array $business) {
        update_user_meta($user_id, 'vmo_a2p_business_name', sanitize_text_field((string) ($business['business_name'] ?? '')));
        update_user_meta($user_id, 'vmo_a2p_contact_email', sanitize_email((string) ($business['contact_email'] ?? '')));
        update_user_meta(
            $user_id,
            'vmo_a2p_contact_name',
            trim(sanitize_text_field((string) ($business['contact_first'] ?? '')) . ' ' . sanitize_text_field((string) ($business['contact_last'] ?? '')))
        );

        // ── Step 1: Create Customer Profile ──
        $profile = $this->twilio_request('POST', self::TRUSTHUB_BASE . '/CustomerProfiles', array(
            'FriendlyName' => sanitize_text_field($business['business_name']) . ' (VMO User #' . $user_id . ')',
            'Email'        => sanitize_email($business['contact_email']),
            'PolicySid'    => 'RNdfbf3fae0e1107f8aded0e7cead80bf5', // A2P Messaging Policy SID (Twilio standard)
        ));

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

        $profile_sid = $profile['sid'];
        update_user_meta($user_id, 'vmo_a2p_profile_sid', $profile_sid);

        // ── Step 2: Create End User entity ──
        $end_user = $this->twilio_request('POST', self::TRUSTHUB_BASE . '/EndUsers', array(
            'FriendlyName' => sanitize_text_field($business['business_name']),
            'Type'         => 'us_a2p_messaging_profile_information',
            'Attributes'   => wp_json_encode(array(
                'business_name'           => $business['business_name'],
                'business_type'           => $business['business_type'] ?? 'Corporation',
                'business_industry'       => $business['industry'] ?? 'TECHNOLOGY',
                'business_registration_identifier' => $business['ein'] ?? '',
                'business_registration_number'     => $business['ein'] ?? '',
                'business_identity'       => 'direct_customer',
                'business_regions_of_operation' => 'USA_AND_CANADA',
                'website_url'             => esc_url_raw($business['website'] ?? ''),
                'social_media_profile_urls' => '',
                'company_type'            => 'private',
                'stock_ticker'            => '',
                'stock_exchange'          => 'NONE',
            )),
        ));

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

        $end_user_sid = $end_user['sid'];
        update_user_meta($user_id, 'vmo_a2p_end_user_sid', $end_user_sid);

        // ── Step 3: Attach End User to Customer Profile ──
        $attach = $this->twilio_request('POST',
            self::TRUSTHUB_BASE . '/CustomerProfiles/' . $profile_sid . '/EntityAssignments',
            array('ObjectSid' => $end_user_sid)
        );

        if (is_wp_error($attach)) {
            error_log('[VMO A2P] Entity assignment failed: ' . $attach->get_error_message());
        }

        // ── Step 3b: Create & attach address ──
        $address = $this->twilio_request('POST',
            'https://api.twilio.com/2010-04-01/Accounts/' . $this->account_sid . '/Addresses.json',
            array(
                'FriendlyName'  => $business['business_name'] . ' Address',
                'CustomerName'  => $business['business_name'],
                'Street'        => $business['address_street'] ?? '',
                'City'          => $business['address_city'] ?? '',
                'Region'        => $business['address_state'] ?? '',
                'PostalCode'    => $business['address_zip'] ?? '',
                'IsoCountry'    => $business['address_country'] ?? 'US',
            )
        );

        if (!is_wp_error($address) && isset($address['sid'])) {
            // Create supporting document for address and attach
            $addr_doc = $this->twilio_request('POST', self::TRUSTHUB_BASE . '/SupportingDocuments', array(
                'FriendlyName' => $business['business_name'] . ' Address Doc',
                'Type'         => 'customer_profile_address',
                'Attributes'   => wp_json_encode(array(
                    'address_sids' => $address['sid'],
                )),
            ));
            if (!is_wp_error($addr_doc) && isset($addr_doc['sid'])) {
                $this->twilio_request('POST',
                    self::TRUSTHUB_BASE . '/CustomerProfiles/' . $profile_sid . '/EntityAssignments',
                    array('ObjectSid' => $addr_doc['sid'])
                );
            }
        }

        // ── Step 3c: Create & attach authorized representative ──
        $rep = $this->twilio_request('POST', self::TRUSTHUB_BASE . '/EndUsers', array(
            'FriendlyName' => ($business['contact_first'] ?? '') . ' ' . ($business['contact_last'] ?? ''),
            'Type'         => 'authorized_representative_1',
            'Attributes'   => wp_json_encode(array(
                'first_name'    => $business['contact_first'] ?? '',
                'last_name'     => $business['contact_last'] ?? '',
                'email'         => $business['contact_email'] ?? '',
                'phone_number'  => $business['contact_phone'] ?? '',
                'business_title'=> 'Owner',
                'job_position'  => 'Director',
            )),
        ));

        if (!is_wp_error($rep) && isset($rep['sid'])) {
            $this->twilio_request('POST',
                self::TRUSTHUB_BASE . '/CustomerProfiles/' . $profile_sid . '/EntityAssignments',
                array('ObjectSid' => $rep['sid'])
            );
        }

        // ── Step 3d: ISV Linkage — Assign Secondary Profile to Primary Profile ──
        // Per Twilio ISV Onboarding Guide Step 1.9:
        // Link this user's (Secondary) Customer Profile to VMO's Primary Customer Profile.
        // This enables ISV-level throughput and consolidated compliance once ISV status is approved.
        // Safe to call even under "Business" profile type — returns success or harmless error.
        if (!empty($this->primary_profile_sid)) {
            $isv_link = $this->twilio_request('POST',
                self::TRUSTHUB_BASE . '/CustomerProfiles/' . $this->primary_profile_sid . '/EntityAssignments',
                array('ObjectSid' => $profile_sid)
            );

            if (is_wp_error($isv_link)) {
                // Non-fatal: ISV linkage is supplementary. Log and continue.
                error_log('[VMO A2P] ISV linkage (Secondary→Primary) skipped or failed: ' . $isv_link->get_error_message());
            } else {
                error_log('[VMO A2P] ISV linkage successful: Secondary ' . $profile_sid . ' → Primary ' . $this->primary_profile_sid);
                update_user_meta($user_id, 'vmo_a2p_isv_linked', true);
                update_user_meta($user_id, 'vmo_a2p_primary_profile_sid', $this->primary_profile_sid);
            }
        }

        // ── Step 3e: Create A2P TrustProduct (ISV compliance bundle) ──
        // Per Twilio ISV Guide Step 2: Create a TrustProduct, attach EndUser + CustomerProfile,
        // evaluate against the A2P TrustProduct policy, and submit for review.
        $trust_product = $this->twilio_request('POST', self::TRUSTHUB_BASE . '/TrustProducts', array(
            'FriendlyName' => sanitize_text_field($business['business_name']) . ' A2P Trust (VMO #' . $user_id . ')',
            'Email'        => sanitize_email($business['contact_email']),
            'PolicySid'    => self::A2P_TRUST_PRODUCT_POLICY_SID,
        ));

        if (!is_wp_error($trust_product) && isset($trust_product['sid'])) {
            $tp_sid = $trust_product['sid'];
            update_user_meta($user_id, 'vmo_a2p_trust_product_sid', $tp_sid);

            // Attach EndUser to TrustProduct
            $this->twilio_request('POST',
                self::TRUSTHUB_BASE . '/TrustProducts/' . $tp_sid . '/EntityAssignments',
                array('ObjectSid' => $end_user_sid)
            );

            // Attach CustomerProfile to TrustProduct
            $this->twilio_request('POST',
                self::TRUSTHUB_BASE . '/TrustProducts/' . $tp_sid . '/EntityAssignments',
                array('ObjectSid' => $profile_sid)
            );

            // Evaluate TrustProduct
            $tp_eval = $this->twilio_request('POST',
                self::TRUSTHUB_BASE . '/TrustProducts/' . $tp_sid . '/Evaluations',
                array('PolicySid' => self::A2P_TRUST_PRODUCT_POLICY_SID)
            );

            if (is_wp_error($tp_eval)) {
                error_log('[VMO A2P] TrustProduct evaluation failed: ' . $tp_eval->get_error_message());
            } else {
                error_log('[VMO A2P] TrustProduct ' . $tp_sid . ' evaluated successfully');
            }
        } else {
            error_log('[VMO A2P] TrustProduct creation failed (non-fatal): ' . (is_wp_error($trust_product) ? $trust_product->get_error_message() : 'Unknown'));
        }

        // ── Step 4: Submit Customer Profile for Review ──
        $submit = $this->twilio_request('POST',
            self::TRUSTHUB_BASE . '/CustomerProfiles/' . $profile_sid . '/Evaluation',
            array('PolicySid' => 'RNdfbf3fae0e1107f8aded0e7cead80bf5')
        );

        if (is_wp_error($submit)) {
            error_log('[VMO A2P] Profile evaluation submit failed: ' . $submit->get_error_message());
        }

        // ── Step 5: Create A2P Brand Registration ──
        $brand = $this->twilio_request('POST', self::MESSAGING_BASE . '/a2p/BrandRegistrations', array(
            'CustomerProfileBundleSid' => $profile_sid,
            'A2PProfileBundleSid'      => $profile_sid,
        ));

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

        // Save brand data
        update_user_meta($user_id, 'vmo_a2p_brand_sid', $brand['sid'] ?? '');
        update_user_meta($user_id, 'vmo_a2p_brand_status', $brand['status'] ?? 'pending');
        update_user_meta($user_id, 'vmo_a2p_brand_registered_at', current_time('mysql'));

        // Set up status webhook URL
        $webhook_url = admin_url('admin-ajax.php') . '?action=vmo_a2p_status_webhook&user_id=' . $user_id;
        update_user_meta($user_id, 'vmo_a2p_webhook_url', $webhook_url);

        $this->maybe_notify_status_change($user_id, array(
            'brand_status' => (string) ($brand['status'] ?? 'pending'),
            'campaign_status' => (string) get_user_meta($user_id, 'vmo_a2p_campaign_status', true),
            'sms_status' => (string) get_user_meta($user_id, 'vmo_sms_activated', true),
            'sms_active' => false,
            'tracked_in_plugin' => true,
            'checked_at' => current_time('mysql'),
        ), 'brand_submitted');

        error_log('[VMO A2P] Brand registered for user #' . $user_id . ': ' . ($brand['sid'] ?? 'unknown'));

        return $brand;
    }

    // ═══════════════════════════════════════════════════════════════════
    // 2. CAMPAIGN REGISTRATION
    // ═══════════════════════════════════════════════════════════════════

    /**
     * Register an A2P Campaign for a user's brand.
     *
     * Prerequisites: Brand must be approved (status = 'APPROVED' or 'VERIFIED').
     *
     * @param int   $user_id
     * @param array $campaign  Campaign details:
     *   - use_case         (string) e.g. 'MIXED', 'MARKETING', 'CUSTOMER_CARE'
     *   - description      (string) Brief campaign description
     *   - sample_message_1 (string) Example message
     *   - sample_message_2 (string) Example message
     *   - opt_in_message   (string) How customers opt in
     *   - opt_in_type      (string) e.g. 'VERBAL', 'WEB_FORM', 'PAPER_FORM'
     *   - messaging_service_sid (string) Messaging Service SID
     * @return array|WP_Error Campaign registration data or error
     */
    public function register_campaign(int $user_id, array $campaign) {
        $brand_sid = get_user_meta($user_id, 'vmo_a2p_brand_sid', true);
        if (empty($brand_sid)) {
            return new WP_Error('no_brand', 'Brand must be registered before creating a campaign.');
        }

        $brand_status = get_user_meta($user_id, 'vmo_a2p_brand_status', true);
        if (!in_array(strtoupper($brand_status), array('APPROVED', 'VERIFIED'), true)) {
            return new WP_Error('brand_not_approved', 'Brand must be approved before creating a campaign. Current status: ' . $brand_status);
        }

        $messaging_service_sid = $campaign['messaging_service_sid'] ?? '';
        if (empty($messaging_service_sid)) {
            return new WP_Error('no_messaging_service', 'A Messaging Service is required for campaign registration.');
        }

        // Determine has_embedded_links and has_embedded_phone based on use case
        $use_case = strtoupper($campaign['use_case'] ?? 'MIXED');

        $camp_result = $this->twilio_request('POST', self::MESSAGING_BASE . '/Services/' . $messaging_service_sid . '/UsAppToPerson', array(
            'BrandRegistrationSid'   => $brand_sid,
            'Description'            => sanitize_text_field($campaign['description'] ?? 'Business communication via VMO'),
            'MessageFlow'            => sanitize_text_field($campaign['opt_in_message'] ?? 'Customers opt in via website contact form or by texting START to the business number.'),
            'MessageSamples'         => wp_json_encode(array(
                sanitize_text_field($campaign['sample_message_1'] ?? 'Hi! This is a reminder about your upcoming appointment tomorrow at 2pm. Reply STOP to opt out.'),
                sanitize_text_field($campaign['sample_message_2'] ?? 'Thank you for your purchase! Your order has shipped. Track it here: https://example.com/track'),
            )),
            'UsAppToPersonUsecase'   => $use_case,
            'HasEmbeddedLinks'       => 'true',
            'HasEmbeddedPhone'       => 'false',
            'OptInType'              => strtoupper($campaign['opt_in_type'] ?? 'VERBAL'),
        ));

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

        // Save campaign data
        update_user_meta($user_id, 'vmo_a2p_campaign_sid', $camp_result['sid'] ?? '');
        update_user_meta($user_id, 'vmo_a2p_campaign_status', $camp_result['campaign_status'] ?? 'PENDING');
        update_user_meta($user_id, 'vmo_a2p_campaign_use_case', $use_case);
        update_user_meta($user_id, 'vmo_a2p_messaging_service_sid', $messaging_service_sid);

        $this->maybe_notify_status_change($user_id, array(
            'brand_status' => (string) get_user_meta($user_id, 'vmo_a2p_brand_status', true),
            'campaign_status' => (string) ($camp_result['campaign_status'] ?? 'PENDING'),
            'sms_status' => (string) get_user_meta($user_id, 'vmo_sms_activated', true),
            'sms_active' => false,
            'tracked_in_plugin' => true,
            'checked_at' => current_time('mysql'),
        ), 'campaign_submitted');

        error_log('[VMO A2P] Campaign registered for user #' . $user_id . ': ' . ($camp_result['sid'] ?? 'unknown'));

        return $camp_result;
    }

    // ═══════════════════════════════════════════════════════════════════
    // 3. MESSAGING SERVICE (auto-create and link phone number)
    // ═══════════════════════════════════════════════════════════════════

    /**
     * Create a Messaging Service and link the user's phone number to it.
     *
     * @param int $user_id
     * @return array|WP_Error Service data with 'sid'
     */
    public function create_messaging_service(int $user_id) {
        $business_name = get_user_meta($user_id, 'vmo_business_name', true) ?: 'VMO Business #' . $user_id;
        $existing_service_sid = (string) get_user_meta($user_id, 'vmo_a2p_messaging_service_sid', true);
        if ($existing_service_sid !== '') {
            $existing_service = $this->fetch_messaging_service($existing_service_sid);
            if (!is_wp_error($existing_service) && !empty($existing_service['sid'])) {
                return $existing_service;
            }
        }

        $discovered = $this->discover_existing_registration($user_id, true);
        $discovered_service_sid = (string) get_user_meta($user_id, 'vmo_a2p_messaging_service_sid', true);
        if ($discovered_service_sid !== '') {
            $existing_service = $this->fetch_messaging_service($discovered_service_sid);
            if (!is_wp_error($existing_service) && !empty($existing_service['sid'])) {
                return $existing_service;
            }
        }

        $phone_sid = get_user_meta($user_id, 'vmo_twilio_phone_sid', true);

        if (empty($phone_sid)) {
            // Try the enhanced wizard's storage
            $phone_data = get_user_meta($user_id, 'vmo_phone_number', true);
            if (is_array($phone_data) && !empty($phone_data['sid'])) {
                $phone_sid = $phone_data['sid'];
            }
        }

        if (empty($phone_sid)) {
            return new WP_Error('no_phone', 'No phone number SID found. User must have a provisioned phone number.');
        }

        // Create Messaging Service
        $service = $this->twilio_request('POST', self::MESSAGING_BASE . '/Services', array(
            'FriendlyName'    => sanitize_text_field($business_name) . ' SMS',
            'InboundRequestUrl' => admin_url('admin-ajax.php') . '?action=vmo_twilio_sms_webhook',
            'StatusCallback'    => admin_url('admin-ajax.php') . '?action=vmo_a2p_status_webhook&user_id=' . $user_id,
            'UseInboundWebhookOnNumber' => 'false',
        ));

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

        $service_sid = $service['sid'];
        update_user_meta($user_id, 'vmo_a2p_messaging_service_sid', $service_sid);

        // Add phone number to the messaging service
        $link = $this->twilio_request('POST',
            self::MESSAGING_BASE . '/Services/' . $service_sid . '/PhoneNumbers',
            array('PhoneNumberSid' => $phone_sid)
        );

        if (is_wp_error($link)) {
            error_log('[VMO A2P] Failed to link phone to messaging service: ' . $link->get_error_message());
        } else {
            error_log('[VMO A2P] Phone ' . $phone_sid . ' linked to messaging service ' . $service_sid);
        }

        return $service;
    }

    // ═══════════════════════════════════════════════════════════════════
    // 4. STATUS CHECKING & WEBHOOKS
    // ═══════════════════════════════════════════════════════════════════

    /**
     * Check and update A2P registration status for a user.
     *
     * @param int $user_id
     * @return array Status data
     */
    public function check_status(int $user_id): array {
        $brand_sid = (string) get_user_meta($user_id, 'vmo_a2p_brand_sid', true);
        $campaign_sid = (string) get_user_meta($user_id, 'vmo_a2p_campaign_sid', true);
        $msg_svc_sid = (string) get_user_meta($user_id, 'vmo_a2p_messaging_service_sid', true);
        $sms_status = (string) get_user_meta($user_id, 'vmo_sms_activated', true);
        $sms_activated_at = (string) get_user_meta($user_id, 'vmo_sms_activated_date', true);
        $result = array(
            'brand_status'    => get_user_meta($user_id, 'vmo_a2p_brand_status', true) ?: 'not_started',
            'campaign_status' => get_user_meta($user_id, 'vmo_a2p_campaign_status', true) ?: 'not_started',
            'sms_status'      => $sms_status ?: 'inactive',
            'sms_active'      => ($sms_status === 'active' || $sms_status === '1'),
            'sms_activated_at' => $sms_activated_at,
            'tracked_in_plugin' => (!empty($brand_sid) || !empty($campaign_sid) || !empty($msg_svc_sid)),
            'checked_at'      => current_time('mysql'),
        );

        if (empty($msg_svc_sid) || empty($campaign_sid)) {
            $discovered = $this->discover_existing_registration($user_id);
            if (!empty($discovered)) {
                $result = array_merge($result, $discovered);
                $brand_sid = (string) get_user_meta($user_id, 'vmo_a2p_brand_sid', true);
                $campaign_sid = (string) get_user_meta($user_id, 'vmo_a2p_campaign_sid', true);
                $msg_svc_sid = (string) get_user_meta($user_id, 'vmo_a2p_messaging_service_sid', true);
            }
        }

        // If brand is pending, poll Twilio for update
        if (!empty($brand_sid) && !in_array(strtoupper($result['brand_status']), array('APPROVED', 'VERIFIED', 'FAILED'), true)) {
            $brand_data = $this->twilio_request('GET', self::MESSAGING_BASE . '/a2p/BrandRegistrations/' . $brand_sid);
            if (!is_wp_error($brand_data) && isset($brand_data['status'])) {
                $result['brand_status'] = $brand_data['status'];
                update_user_meta($user_id, 'vmo_a2p_brand_status', $brand_data['status']);
            }
        }

        // If campaign is pending, poll Twilio for update
        if (!empty($campaign_sid) && !empty($msg_svc_sid) && !in_array(strtoupper($result['campaign_status']), array('VERIFIED', 'FAILED'), true)) {
            $camp_data = $this->twilio_request('GET', self::MESSAGING_BASE . '/Services/' . $msg_svc_sid . '/UsAppToPerson/' . $campaign_sid);
            if (!is_wp_error($camp_data) && isset($camp_data['campaign_status'])) {
                $result['campaign_status'] = $camp_data['campaign_status'];
                update_user_meta($user_id, 'vmo_a2p_campaign_status', $camp_data['campaign_status']);

                // Auto-activate SMS when campaign is verified
                if (strtoupper($camp_data['campaign_status']) === 'VERIFIED') {
                    update_user_meta($user_id, 'vmo_sms_activated', 'active');
                    update_user_meta($user_id, 'vmo_sms_activated_date', current_time('mysql'));
                    $result['sms_active'] = true;
                    $result['sms_status'] = 'active';
                    $result['sms_activated_at'] = (string) get_user_meta($user_id, 'vmo_sms_activated_date', true);
                    error_log('[VMO A2P] SMS auto-activated for user #' . $user_id . ' — campaign VERIFIED');
                }
            }
        }

        if (strtoupper((string) $result['campaign_status']) === 'VERIFIED' && empty($result['sms_active'])) {
            update_user_meta($user_id, 'vmo_sms_activated', 'active');
            if ($result['sms_activated_at'] === '') {
                update_user_meta($user_id, 'vmo_sms_activated_date', current_time('mysql'));
            }
            $result['sms_status'] = 'active';
            $result['sms_active'] = true;
            $result['sms_activated_at'] = (string) get_user_meta($user_id, 'vmo_sms_activated_date', true);
            error_log('[VMO A2P] SMS auto-activated for user #' . $user_id . ' — campaign status already VERIFIED');
        }

        if (!empty($result['campaign_status']) && empty($result['brand_status']) && strtoupper((string) $result['campaign_status']) !== 'NOT_STARTED') {
            $result['brand_status'] = 'APPROVED';
            update_user_meta($user_id, 'vmo_a2p_brand_status', 'APPROVED');
        }

        $result['tracked_in_plugin'] = (!empty($brand_sid) || !empty($campaign_sid) || !empty($msg_svc_sid));
        $this->maybe_notify_status_change($user_id, $result, 'status_check');

        return $result;
    }

    /**
     * Webhook handler for Twilio A2P status updates.
     */
    public function handle_status_webhook() {
        $user_id = isset($_GET['user_id']) ? absint($_GET['user_id']) : 0;
        if (!$user_id) {
            wp_send_json_error('Missing user_id');
        }

        $status = sanitize_text_field($_POST['Status'] ?? $_POST['status'] ?? '');
        $type   = sanitize_text_field($_POST['Type'] ?? $_POST['type'] ?? '');
        $sid    = sanitize_text_field($_POST['Sid'] ?? $_POST['sid'] ?? '');

        error_log('[VMO A2P Webhook] User #' . $user_id . ' | Type: ' . $type . ' | Status: ' . $status . ' | SID: ' . $sid);

        if ($sid === '') {
            wp_send_json_error('Missing SID', 400);
        }

        // Update brand status
        $brand_sid = get_user_meta($user_id, 'vmo_a2p_brand_sid', true);
        $updated = false;
        if ($sid === $brand_sid) {
            update_user_meta($user_id, 'vmo_a2p_brand_status', $status);
            $updated = true;
        }

        // Update campaign status
        $campaign_sid = get_user_meta($user_id, 'vmo_a2p_campaign_sid', true);
        if ($sid === $campaign_sid) {
            update_user_meta($user_id, 'vmo_a2p_campaign_status', $status);
            $updated = true;

            // Auto-activate SMS on campaign verification
            if (strtoupper($status) === 'VERIFIED') {
                update_user_meta($user_id, 'vmo_sms_activated', 'active');
                update_user_meta($user_id, 'vmo_sms_activated_date', current_time('mysql'));
                error_log('[VMO A2P] SMS auto-activated for user #' . $user_id . ' via webhook');
            }
        }

        if (!$updated) {
            status_header(202);
            echo 'IGNORED';
            exit;
        }

        $this->maybe_notify_status_change($user_id, array(
            'brand_status' => (string) get_user_meta($user_id, 'vmo_a2p_brand_status', true),
            'campaign_status' => (string) get_user_meta($user_id, 'vmo_a2p_campaign_status', true),
            'sms_status' => (string) get_user_meta($user_id, 'vmo_sms_activated', true),
            'sms_active' => ((string) get_user_meta($user_id, 'vmo_sms_activated', true) === 'active'),
            'tracked_in_plugin' => true,
            'checked_at' => current_time('mysql'),
        ), 'status_webhook');

        // Respond 200 OK to Twilio
        http_response_code(200);
        echo 'OK';
        exit;
    }

    // ═══════════════════════════════════════════════════════════════════
    // 5. AJAX HANDLERS (Called from dashboard UI)
    // ═══════════════════════════════════════════════════════════════════

    /**
     * AJAX: Register Brand
     */
    public function ajax_register_brand() {
        if (!is_user_logged_in()) {
            wp_send_json_error(array('message' => 'Not logged in'), 403);
        }
        if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
            wp_send_json_error(array('message' => 'Security check failed'), 403);
        }

        $user_id = get_current_user_id();

        // ── Token charge for A2P Brand Registration ──
        // Twilio cost: ~$4 one-time. VMO charges 2.5x = $10 = 15,000 tokens
        $brand_cost = defined('VMO_TOKENS_A2P_BRAND') ? VMO_TOKENS_A2P_BRAND : 15000;
        if (function_exists('vmo_deduct_tokens')) {
            if (!vmo_deduct_tokens($user_id, $brand_cost, 'a2p_brand_registration', 'A2P Brand Registration ($10 — one-time)')) {
                wp_send_json_error(array('message' => 'Insufficient tokens. The carrier brand-registration charge is ' . number_format($brand_cost) . ' tokens (~$10.00). VMO processes that external fee through your balance so you do not need separate billing elsewhere.'));
            }
        }

        $business = array(
            'business_name'    => sanitize_text_field($_POST['business_name'] ?? ''),
            'ein'              => sanitize_text_field($_POST['ein'] ?? ''),
            'business_type'    => sanitize_text_field($_POST['business_type'] ?? 'Corporation'),
            'industry'         => sanitize_text_field($_POST['industry'] ?? 'TECHNOLOGY'),
            'website'          => esc_url_raw($_POST['website'] ?? ''),
            'address_street'   => sanitize_text_field($_POST['address_street'] ?? ''),
            'address_city'     => sanitize_text_field($_POST['address_city'] ?? ''),
            'address_state'    => sanitize_text_field($_POST['address_state'] ?? ''),
            'address_zip'      => sanitize_text_field($_POST['address_zip'] ?? ''),
            'address_country'  => sanitize_text_field($_POST['address_country'] ?? 'US'),
            'contact_first'    => sanitize_text_field($_POST['contact_first'] ?? ''),
            'contact_last'     => sanitize_text_field($_POST['contact_last'] ?? ''),
            'contact_email'    => sanitize_email($_POST['contact_email'] ?? ''),
            'contact_phone'    => sanitize_text_field($_POST['contact_phone'] ?? ''),
        );

        // Validate required fields
        $required = array('business_name', 'ein', 'contact_email', 'contact_phone', 'address_street', 'address_city', 'address_state', 'address_zip');
        foreach ($required as $field) {
            if (empty($business[$field])) {
                // Refund tokens on validation failure
                if (function_exists('vmo_update_token_balance')) {
                    vmo_update_token_balance($user_id, $brand_cost, 'add');
                }
                wp_send_json_error(array('message' => 'Missing required field: ' . str_replace('_', ' ', $field)));
            }
        }

        $result = $this->register_brand($user_id, $business);

        if (is_wp_error($result)) {
            // Refund tokens on API failure
            if (function_exists('vmo_update_token_balance')) {
                vmo_update_token_balance($user_id, $brand_cost, 'add');
            }
            wp_send_json_error(array('message' => $this->get_public_error_message($result, 'Brand registration could not be submitted right now. Please review the form details and try again.')));
        }

        wp_send_json_success(array(
            'message'    => 'Brand registration submitted. VMO will email you when review moves forward and alert you when the campaign step is ready.',
            'brand_sid'  => $result['sid'] ?? '',
            'status'     => $result['status'] ?? 'pending',
        ));
    }

    /**
     * AJAX: Register Campaign
     */
    public function ajax_register_campaign() {
        if (!is_user_logged_in()) {
            wp_send_json_error(array('message' => 'Not logged in'), 403);
        }
        if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
            wp_send_json_error(array('message' => 'Security check failed'), 403);
        }

        $user_id = get_current_user_id();

        // Check brand is approved first
        $brand_status = get_user_meta($user_id, 'vmo_a2p_brand_status', true);
        if (!in_array(strtoupper($brand_status), array('APPROVED', 'VERIFIED'), true)) {
            wp_send_json_error(array('message' => 'Your brand must be approved first. Current status: ' . $brand_status));
        }

        // ── Token charge for A2P Campaign Registration ──
        // Twilio cost: ~$15/month (standard). VMO charges 2.5x = $37.50 = 56,250 tokens first month
        // After that, monthly renewal is handled by cron
        $campaign_cost = defined('VMO_TOKENS_A2P_CAMPAIGN') ? VMO_TOKENS_A2P_CAMPAIGN : 56250;
        if (function_exists('vmo_deduct_tokens')) {
            if (!vmo_deduct_tokens($user_id, $campaign_cost, 'a2p_campaign_registration', 'A2P Campaign Registration ($37.50 — first month)')) {
                wp_send_json_error(array('message' => 'Insufficient tokens. The carrier campaign charge is ' . number_format($campaign_cost) . ' tokens (~$37.50). VMO processes that external fee through your balance so you do not need separate billing elsewhere.'));
            }
        }

        // Auto-create messaging service if not exists
        $msg_svc_sid = get_user_meta($user_id, 'vmo_a2p_messaging_service_sid', true);
        if (empty($msg_svc_sid)) {
            $svc = $this->create_messaging_service($user_id);
            if (is_wp_error($svc)) {
                // Refund
                if (function_exists('vmo_update_token_balance')) {
                    vmo_update_token_balance($user_id, $campaign_cost, 'add');
                }
                wp_send_json_error(array('message' => $this->get_public_error_message($svc, 'VMO could not prepare the messaging service for this number yet. Please try again.')));
            }
            $msg_svc_sid = $svc['sid'];
        }

        $campaign = array(
            'use_case'          => sanitize_text_field($_POST['use_case'] ?? 'MIXED'),
            'description'       => sanitize_text_field($_POST['description'] ?? ''),
            'sample_message_1'  => sanitize_text_field($_POST['sample_message_1'] ?? ''),
            'sample_message_2'  => sanitize_text_field($_POST['sample_message_2'] ?? ''),
            'opt_in_message'    => sanitize_text_field($_POST['opt_in_message'] ?? ''),
            'opt_in_type'       => sanitize_text_field($_POST['opt_in_type'] ?? 'VERBAL'),
            'messaging_service_sid' => $msg_svc_sid,
        );

        $result = $this->register_campaign($user_id, $campaign);

        if (is_wp_error($result)) {
            // Refund
            if (function_exists('vmo_update_token_balance')) {
                vmo_update_token_balance($user_id, $campaign_cost, 'add');
            }
            wp_send_json_error(array('message' => $this->get_public_error_message($result, 'Campaign registration could not be submitted right now. Please review the campaign details and try again.')));
        }

        // Set next campaign billing date (30 days)
        update_user_meta($user_id, 'vmo_a2p_campaign_next_billing', strtotime('+30 days'));

        wp_send_json_success(array(
            'message'      => 'Campaign registration submitted. VMO will email you during review and alert you as soon as SMS is approved.',
            'campaign_sid' => $result['sid'] ?? '',
            'status'       => $result['campaign_status'] ?? 'PENDING',
        ));
    }

    /**
     * AJAX: Check Status
     */
    public function ajax_check_status() {
        if (!is_user_logged_in()) {
            wp_send_json_error(array('message' => 'Not logged in'), 403);
        }

        $status = $this->check_status(get_current_user_id());
        wp_send_json_success($status);
    }

    /**
     * AJAX: Create Messaging Service (standalone)
     */
    public function ajax_create_messaging_service() {
        if (!is_user_logged_in()) {
            wp_send_json_error(array('message' => 'Not logged in'), 403);
        }
        if (!function_exists('vmo_verify_admin_security') || !vmo_verify_admin_security()) {
            wp_send_json_error(array('message' => 'Security check failed'), 403);
        }

        $result = $this->create_messaging_service(get_current_user_id());

        if (is_wp_error($result)) {
            wp_send_json_error(array('message' => $result->get_error_message()));
        }

        wp_send_json_success(array(
            'message' => 'Messaging Service created and phone number linked.',
            'sid'     => $result['sid'] ?? '',
        ));
    }
}
