<?php
/**
 * Zoho CRM Sync Handler
 *
 * @package HVACCommunityEvents
 */

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

/**
 * Zoho Sync Class
 */
class HVAC_Zoho_Sync {
    
    /**
     * Zoho Auth instance
     *
     * @var HVAC_Zoho_CRM_Auth
     */
    private $auth;
    
    /**
     * Staging mode flag
     *
     * @var bool
     */
    private $is_staging;
    
    /**
     * Last contact creation error for debugging
     *
     * @var string
     */
    private $last_contact_error = '';
    
    /**
     * Constructor
     */
    public function __construct() {
        require_once HVAC_PLUGIN_DIR . 'includes/zoho/class-zoho-crm-auth.php';
        $this->auth = new HVAC_Zoho_CRM_Auth();
        
        // Determine if we're in staging mode
        $this->is_staging = HVAC_Zoho_CRM_Auth::is_staging_mode();
    }
    
    /**
     * Check if sync is allowed
     *
     * @return bool
     */
    private function is_sync_allowed() {
        // Use consistent logic from Auth class
        return !HVAC_Zoho_CRM_Auth::is_staging_mode();
    }

    /**
     * Validate a Zoho API response to determine if the operation succeeded
     *
     * @param mixed $response Response from make_api_request()
     * @return array ['success' => bool, 'id' => string|null, 'error' => string|null]
     */
    private function validate_api_response($response) {
        // Check for WP_Error
        if (is_wp_error($response)) {
            return array(
                'success' => false,
                'id' => null,
                'error' => $response->get_error_message()
            );
        }

        // Check for staging mode simulation
        if (isset($response['data'][0]['code']) && $response['data'][0]['code'] === 'STAGING_MODE') {
            return array(
                'success' => false,
                'id' => null,
                'error' => 'Staging mode: write operations blocked'
            );
        }

        // Check for Zoho API error codes FIRST (more specific than generic HTTP errors)
        if (isset($response['data'][0]['code']) && !in_array($response['data'][0]['code'], array('SUCCESS', 'DUPLICATE_DATA'))) {
            $error_msg = isset($response['data'][0]['message']) ? $response['data'][0]['message'] : $response['data'][0]['code'];
            // Include field-level details if available
            if (isset($response['data'][0]['details'])) {
                $details = $response['data'][0]['details'];
                if (isset($details['api_name'])) {
                    $error_msg .= ' (field: ' . $details['api_name'] . ')';
                }
                if (isset($details['expected_data_type'])) {
                    $error_msg .= ' (expected: ' . $details['expected_data_type'] . ')';
                }
                if (isset($details['info'])) {
                    $error_msg .= ' - ' . $details['info'];
                }
            }
            return array(
                'success' => false,
                'id' => null,
                'error' => $error_msg
            );
        }

        // Check for HTTP-level errors (generic fallback)
        if (isset($response['error'])) {
            return array(
                'success' => false,
                'id' => null,
                'error' => $response['error']
            );
        }

        // Check for successful response with ID
        if (isset($response['data'][0]['details']['id'])) {
            return array(
                'success' => true,
                'id' => $response['data'][0]['details']['id'],
                'error' => null
            );
        }

        // Check for duplicate record handling (also a success case)
        if (isset($response['data'][0]['code']) && $response['data'][0]['code'] === 'DUPLICATE_DATA'
            && isset($response['data'][0]['details']['duplicate_record']['id'])) {
            return array(
                'success' => true,
                'id' => $response['data'][0]['details']['duplicate_record']['id'],
                'error' => null
            );
        }

        // Check for success status without ID (e.g., PUT updates)
        if (isset($response['data'][0]['status']) && $response['data'][0]['status'] === 'success') {
            return array(
                'success' => true,
                'id' => isset($response['data'][0]['details']['id']) ? $response['data'][0]['details']['id'] : null,
                'error' => null
            );
        }

        // Unknown response structure - treat as failure
        return array(
            'success' => false,
            'id' => null,
            'error' => 'Unexpected API response: ' . json_encode(array_slice($response, 0, 3))
        );
    }
    
    /**
     * Generate a hash for sync data to detect changes
     *
     * @param array $data Data being synced
     * @return string MD5 hash
     */
    private function generate_sync_hash($data) {
        // Sort keys for consistent hashing
        ksort($data);
        return md5(json_encode($data));
    }
    
    /**
     * Check if a post record should be synced based on hash comparison
     *
     * @param int $post_id Post ID
     * @param array $data Data being synced
     * @param string $meta_key Meta key for storing hash (default: _zoho_sync_hash)
     * @return bool True if data has changed and should be synced
     */
    private function should_sync($post_id, $data, $meta_key = '_zoho_sync_hash') {
        $new_hash = $this->generate_sync_hash($data);
        $old_hash = get_post_meta($post_id, $meta_key, true);
        return $new_hash !== $old_hash;
    }
    
    /**
     * Check if a user should be synced based on hash comparison
     *
     * @param int $user_id User ID
     * @param array $data Data being synced
     * @return bool True if data has changed and should be synced
     */
    private function should_sync_user($user_id, $data) {
        $new_hash = $this->generate_sync_hash($data);
        $old_hash = get_user_meta($user_id, '_zoho_sync_hash', true);
        return $new_hash !== $old_hash;
    }
    
    /**
     * Sync events to Zoho Campaigns
     *
     * @param int $offset Starting offset for pagination
     * @param int $limit Number of records per batch
     * @param int|null $since_timestamp Optional timestamp to filter modified records
     * @return array Sync results
     */
    public function sync_events($offset = 0, $limit = 50, $since_timestamp = null) {
        $results = array(
            'total' => 0,
            'synced' => 0,
            'skipped' => 0,
            'failed' => 0,
            'errors' => array(),
            'staging_mode' => $this->is_staging,
            'debug_info' => HVAC_Zoho_CRM_Auth::get_debug_mode_info(),
            'has_more' => false,
            'next_offset' => 0,
            'batch_offset' => $offset,
            'incremental_sync' => !is_null($since_timestamp)
        );
        
        // Build base query args
        $base_event_args = array(
            'posts_per_page' => -1,
            'eventDisplay' => 'custom',
            'start_date' => '2020-01-01',
        );
        
        $base_series_args = array(
            'post_type' => 'tribe_event_series',
            'posts_per_page' => -1,
            'post_status' => 'publish',
        );
        
        // Add date filter for incremental sync
        if ($since_timestamp) {
            $since_date = date('Y-m-d H:i:s', $since_timestamp);
            $date_query = array(
                array(
                    'column' => 'post_modified',
                    'after' => $since_date,
                    'inclusive' => true,
                )
            );
            $base_event_args['date_query'] = $date_query;
            $base_series_args['date_query'] = $date_query;
        }
        
        // First, get total count
        $all_events = tribe_get_events($base_event_args);
        $event_series = get_posts($base_series_args);
        $total_count = count($all_events) + count($event_series);
        $results['total'] = $total_count;
        
        // Get paginated events
        $paginated_args = array_merge($base_event_args, array(
            'posts_per_page' => $limit,
            'offset' => $offset,
        ));
        $events = tribe_get_events($paginated_args);
        
        // Also get event series (paginated based on remaining limit)
        $remaining = max(0, $limit - count($events));
        $series_offset = max(0, $offset - count($all_events));
        if ($remaining > 0 && $series_offset >= 0) {
            $event_series_batch = get_posts(array(
                'post_type' => 'tribe_event_series',
                'posts_per_page' => $remaining,
                'offset' => $series_offset,
                'post_status' => 'publish',
            ));
            $events = array_merge($events, $event_series_batch);
        }
        
        // Calculate pagination
        $results['has_more'] = ($offset + count($events)) < $total_count;
        $results['next_offset'] = $offset + count($events);
        
        // If staging mode, simulate the sync
        if ($this->is_staging) {
            $results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.';
            $results['synced'] = $results['total'];
            $results['test_data'] = array();
            
            foreach ($events as $event) {
                $campaign_data = $this->prepare_campaign_data($event);
                $results['test_data'][] = array(
                    'event_id' => $event->ID,
                    'event_title' => get_the_title($event),
                    'zoho_data' => $campaign_data
                );
            }
            
            return $results;
        }
        
        // Production sync
        if (!$this->is_sync_allowed()) {
            $results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.';
            return $results;
        }
        
        foreach ($events as $event) {
            try {
                $campaign_data = $this->prepare_campaign_data($event);

                // Check if data has changed using hash comparison
                if (!$this->should_sync($event->ID, $campaign_data)) {
                    $results['skipped']++;
                    continue;
                }

                $campaign_id = null;
                $sync_succeeded = false;

                // FIRST: Check if we already have a stored Zoho Campaign ID
                $stored_campaign_id = get_post_meta($event->ID, '_zoho_campaign_id', true);

                if (!empty($stored_campaign_id)) {
                    // We have a stored ID - try to update
                    $update_response = $this->auth->make_api_request("/Campaigns/{$stored_campaign_id}", 'PUT', array(
                        'data' => array($campaign_data)
                    ));
                    $validated = $this->validate_api_response($update_response);

                    // Check if update failed due to invalid ID (e.g. campaign deleted in Zoho)
                    if (!$validated['success'] && isset($update_response['code']) && $update_response['code'] === 'INVALID_DATA') {
                        // Fallback: Create new campaign
                        $create_response = $this->auth->make_api_request('/Campaigns', 'POST', array(
                            'data' => array($campaign_data)
                        ));
                        $validated = $this->validate_api_response($create_response);
                        $results['responses'][] = array('type' => 'create_fallback', 'id' => $event->ID, 'response' => $create_response);

                        if ($validated['success'] && $validated['id']) {
                            $campaign_id = $validated['id'];
                            $sync_succeeded = true;
                        }
                    } elseif ($validated['success']) {
                        $campaign_id = $stored_campaign_id;
                        $sync_succeeded = true;
                        $results['responses'][] = array('type' => 'update', 'id' => $event->ID, 'response' => $update_response);
                    }
                } else {
                    // No stored ID - create new campaign
                    $create_response = $this->auth->make_api_request('/Campaigns', 'POST', array(
                        'data' => array($campaign_data)
                    ));
                    $validated = $this->validate_api_response($create_response);
                    $results['responses'][] = array('type' => 'create', 'id' => $event->ID, 'response' => $create_response);

                    if ($validated['success'] && $validated['id']) {
                        $campaign_id = $validated['id'];
                        $sync_succeeded = true;
                    }
                }

                if ($sync_succeeded) {
                    $results['synced']++;

                    // Only update hash and Zoho ID on confirmed success
                    if (!empty($campaign_id)) {
                        update_post_meta($event->ID, '_zoho_campaign_id', $campaign_id);
                    }
                    update_post_meta($event->ID, '_zoho_sync_hash', $this->generate_sync_hash($campaign_data));
                } else {
                    $results['failed']++;
                    $error_msg = isset($validated['error']) ? $validated['error'] : 'Unknown API error';
                    $results['errors'][] = sprintf('Event %s: %s', $event->ID, $error_msg);
                }

            } catch (\Throwable $e) {
                $results['failed']++;
                $results['errors'][] = sprintf('Event %s: [%s] %s', $event->ID, get_class($e), $e->getMessage());
            }
        }
        
        return $results;
    }
    
    /**
     * Sync users to Zoho Contacts
     *
     * @param int $offset Starting offset for pagination
     * @param int $limit Number of records per batch
     * @param int|null $since_timestamp Optional timestamp to filter modified records
     * @return array Sync results
     */
    public function sync_users($offset = 0, $limit = 50, $since_timestamp = null) {
        $results = array(
            'total' => 0,
            'synced' => 0,
            'skipped' => 0,
            'failed' => 0,
            'errors' => array(),
            'staging_mode' => $this->is_staging,
            'has_more' => false,
            'next_offset' => 0,
            'batch_offset' => $offset,
            'incremental_sync' => !is_null($since_timestamp)
        );
        
        // Base query args
        $base_args = array(
            'role__in' => array('hvac_trainer', 'hvac_master_trainer'),
            'meta_query' => array(
                'relation' => 'OR',
                array(
                    'key' => '_sync_to_zoho',
                    'value' => '1',
                    'compare' => '='
                ),
                array(
                    'key' => '_sync_to_zoho',
                    'compare' => 'NOT EXISTS'
                )
            )
        );
        
        // Note: WP_User_Query doesn't support date_query for user_registered
        // For users, we'll sync all and let Zoho handle updates
        // Future enhancement: track _zoho_last_synced per user
        
        // Get total count first
        $total_query = new WP_User_Query(array_merge($base_args, array('count_total' => true, 'number' => -1)));
        $total_count = $total_query->get_total();
        $results['total'] = $total_count;
        
        // Get paginated users
        $users = get_users(array_merge($base_args, array(
            'number' => $limit,
            'offset' => $offset
        )));
        
        // Calculate pagination
        $results['has_more'] = ($offset + count($users)) < $total_count;
        $results['next_offset'] = $offset + count($users);
        
        // If staging mode, simulate the sync
        if ($this->is_staging) {
            $results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.';
            $results['synced'] = $results['total'];
            $results['test_data'] = array();
            
            foreach ($users as $user) {
                $contact_data = $this->prepare_contact_data($user);
                $results['test_data'][] = array(
                    'user_id' => $user->ID,
                    'user_email' => $user->user_email,
                    'user_role' => implode(', ', $user->roles),
                    'zoho_data' => $contact_data
                );
            }
            
            return $results;
        }
        
        // Production sync
        if (!$this->is_sync_allowed()) {
            $results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.';
            return $results;
        }
        
        foreach ($users as $user) {
            try {
                $contact_data = $this->prepare_contact_data($user);

                // Check if data has changed using hash comparison
                if (!$this->should_sync_user($user->ID, $contact_data)) {
                    $results['skipped']++;
                    continue;
                }

                $contact_id = null;
                $sync_succeeded = false;

                // Check if contact already exists in Zoho
                $search_response = $this->auth->make_api_request(
                    '/Contacts/search?criteria=(Email:equals:' . urlencode($contact_data['Email']) . ')',
                    'GET'
                );

                if (!empty($search_response['data'])) {
                    // Update existing contact
                    $contact_id = $search_response['data'][0]['id'];
                    $update_response = $this->auth->make_api_request("/Contacts/{$contact_id}", 'PUT', array(
                        'data' => array($contact_data)
                    ));
                    $validated = $this->validate_api_response($update_response);
                    $sync_succeeded = $validated['success'];
                } else {
                    // Create new contact
                    $create_response = $this->auth->make_api_request('/Contacts', 'POST', array(
                        'data' => array($contact_data)
                    ));
                    $validated = $this->validate_api_response($create_response);
                    $sync_succeeded = $validated['success'];

                    if ($validated['success'] && $validated['id']) {
                        $contact_id = $validated['id'];
                    }
                }

                if ($sync_succeeded) {
                    $results['synced']++;

                    // Only update hash and Zoho ID on confirmed success
                    if (!empty($contact_id)) {
                        update_user_meta($user->ID, '_zoho_contact_id', $contact_id);
                    }
                    update_user_meta($user->ID, '_zoho_sync_hash', $this->generate_sync_hash($contact_data));
                } else {
                    $results['failed']++;
                    $error_msg = isset($validated['error']) ? $validated['error'] : 'Unknown API error';
                    $results['errors'][] = sprintf('User %s: %s', $user->ID, $error_msg);
                }

            } catch (\Throwable $e) {
                $results['failed']++;
                $results['errors'][] = sprintf('User %s: [%s] %s', $user->ID, get_class($e), $e->getMessage());
            }
        }
        
        return $results;
    }
    
    /**
     * Sync Event Tickets orders to Zoho Invoices
     *
     * @param int $offset Starting offset for pagination
     * @param int $limit Number of records per batch
     * @param int|null $since_timestamp Optional timestamp to filter modified records
     * @return array Sync results
     */
    public function sync_purchases($offset = 0, $limit = 50, $since_timestamp = null) {
        $results = array(
            'total' => 0,
            'synced' => 0,
            'skipped' => 0,
            'failed' => 0,
            'errors' => array(),
            'staging_mode' => $this->is_staging,
            'has_more' => false,
            'next_offset' => 0,
            'batch_offset' => $offset,
            'incremental_sync' => !is_null($since_timestamp)
        );

        // Build query args
        $query_args = array(
            'post_type' => 'tec_tc_order',
            'post_status' => 'tec-tc-completed',
            'posts_per_page' => -1,
            'fields' => 'ids'
        );
        
        // Add date filter for incremental sync
        if ($since_timestamp) {
            $query_args['date_query'] = array(
                array(
                    'column' => 'post_modified',
                    'after' => date('Y-m-d H:i:s', $since_timestamp),
                    'inclusive' => true,
                )
            );
        }

        // Get total count first
        $count_query = new WP_Query($query_args);
        $total_count = $count_query->found_posts;
        $results['total'] = $total_count;

        if ($total_count === 0) {
            $results['message'] = 'No completed ticket orders found.';
            return $results;
        }

        // Get paginated orders
        $paginated_args = array_merge($query_args, array(
            'posts_per_page' => $limit,
            'offset' => $offset,
            'fields' => 'all',
        ));
        $orders = get_posts($paginated_args);
        
        // Calculate pagination
        $results['has_more'] = ($offset + count($orders)) < $total_count;
        $results['next_offset'] = $offset + count($orders);

        // If staging mode, simulate the sync
        if ($this->is_staging) {
            $results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.';
            $results['synced'] = $results['total'];
            $results['test_data'] = array();

            foreach ($orders as $order) {
                $invoice_data = $this->prepare_tc_invoice_data($order);
                $results['test_data'][] = array(
                    'order_id' => $order->ID,
                    'purchaser_email' => get_post_meta($order->ID, '_tec_tc_order_purchaser_email', true),
                    'gateway' => get_post_meta($order->ID, '_tec_tc_order_gateway', true),
                    'date' => $order->post_date,
                    'zoho_data' => $invoice_data
                );
            }

            return $results;
        }

        // Production sync
        if (!$this->is_sync_allowed()) {
            $results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.';
            return $results;
        }

        foreach ($orders as $order) {
            try {
                $invoice_data = $this->prepare_tc_invoice_data($order);

                // Check if data has changed using hash comparison
                if (!$this->should_sync($order->ID, $invoice_data)) {
                    $results['skipped']++;
                    continue;
                }

                $invoice_id = null;
                $sync_succeeded = false;

                // Check if invoice already exists in Zoho (by WordPress Order ID)
                $search_response = $this->auth->make_api_request(
                    '/Invoices/search?criteria=(WordPress_Order_ID:equals:' . $order->ID . ')',
                    'GET'
                );

                if (!empty($search_response['data'])) {
                    // Update existing invoice
                    $invoice_id = $search_response['data'][0]['id'];
                    $update_response = $this->auth->make_api_request("/Invoices/{$invoice_id}", 'PUT', array(
                        'data' => array($invoice_data)
                    ));
                    $validated = $this->validate_api_response($update_response);
                    $sync_succeeded = $validated['success'];
                } else {
                    // Create new invoice
                    $create_response = $this->auth->make_api_request('/Invoices', 'POST', array(
                        'data' => array($invoice_data)
                    ));
                    $validated = $this->validate_api_response($create_response);
                    $sync_succeeded = $validated['success'];

                    if ($validated['success'] && $validated['id']) {
                        $invoice_id = $validated['id'];
                    }
                }

                if ($sync_succeeded) {
                    $results['synced']++;

                    // Only update hash and Zoho ID on confirmed success
                    if (!empty($invoice_id)) {
                        update_post_meta($order->ID, '_zoho_invoice_id', $invoice_id);
                    }
                    update_post_meta($order->ID, '_zoho_sync_hash', $this->generate_sync_hash($invoice_data));
                } else {
                    $results['failed']++;
                    $error_msg = isset($validated['error']) ? $validated['error'] : 'Unknown API error';
                    $results['errors'][] = sprintf('Order %s: %s', $order->ID, $error_msg);
                }

            } catch (\Throwable $e) {
                $results['failed']++;
                $results['errors'][] = sprintf('Order %s: [%s] %s', $order->ID, get_class($e), $e->getMessage());
            }
        }

        return $results;
    }

    /**
     * Sync ticket attendees to Zoho Contacts + Campaign Members
     *
     * @param int $offset Starting offset for pagination
     * @param int $limit Number of records per batch
     * @param int|null $since_timestamp Optional timestamp to filter modified records
     * @return array Sync results
     */
    public function sync_attendees($offset = 0, $limit = 50, $since_timestamp = null) {
        $results = array(
            'total' => 0,
            'synced' => 0,
            'skipped' => 0,
            'failed' => 0,
            'errors' => array(),
            'staging_mode' => $this->is_staging,
            'contacts_created' => 0,
            'campaign_members_created' => 0,
            'responses' => array(),
            'debug_info' => HVAC_Zoho_CRM_Auth::get_debug_mode_info(),
            'version' => 'BATCH_V1',
            'has_more' => false,
            'next_offset' => 0,
            'batch_offset' => $offset,
            'incremental_sync' => !is_null($since_timestamp)
        );

        // Build base query args
        $base_tc_args = array(
            'post_type' => 'tec_tc_attendee',
            'post_status' => 'any',
            'posts_per_page' => -1,
            'fields' => 'ids'
        );
        $base_tpp_args = array(
            'post_type' => 'tribe_tpp_attendees',
            'post_status' => 'any',
            'posts_per_page' => -1,
            'fields' => 'ids'
        );
        
        // Add date filter for incremental sync
        if ($since_timestamp) {
            $date_query = array(
                array(
                    'column' => 'post_date',
                    'after' => date('Y-m-d H:i:s', $since_timestamp),
                    'inclusive' => true,
                )
            );
            $base_tc_args['date_query'] = $date_query;
            $base_tpp_args['date_query'] = $date_query;
        }

        // Get total count for all attendee types
        $tc_count_query = new WP_Query($base_tc_args);
        $tpp_count_query = new WP_Query($base_tpp_args);
        $total_count = $tc_count_query->found_posts + $tpp_count_query->found_posts;
        $results['total'] = $total_count;

        if ($total_count === 0) {
            $results['message'] = 'No ticket attendees found.';
            return $results;
        }

        // Get paginated attendees - first from tec_tc_attendee
        $tc_paginated_args = array_merge($base_tc_args, array(
            'posts_per_page' => $limit,
            'offset' => $offset,
            'fields' => 'all',
        ));
        $tc_attendees = get_posts($tc_paginated_args);

        // If we need more to fill the batch, get from PayPal attendees
        $remaining = max(0, $limit - count($tc_attendees));
        $tpp_offset = max(0, $offset - $tc_count_query->found_posts);
        $tpp_attendees = array();
        if ($remaining > 0 && $tpp_offset >= 0) {
            $tpp_paginated_args = array_merge($base_tpp_args, array(
                'posts_per_page' => $remaining,
                'offset' => $tpp_offset,
                'fields' => 'all',
            ));
            $tpp_attendees = get_posts($tpp_paginated_args);
        }

        $all_attendees = array_merge($tc_attendees, $tpp_attendees);
        
        // Calculate pagination
        $results['has_more'] = ($offset + count($all_attendees)) < $total_count;
        $results['next_offset'] = $offset + count($all_attendees);

        if (count($all_attendees) === 0) {
            $results['message'] = 'No ticket attendees found.';
            return $results;
        }

        // If staging mode, simulate the sync
        if ($this->is_staging) {
            $results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.';
            $results['synced'] = $results['total'];
            $results['test_data'] = array();

            foreach ($all_attendees as $attendee) {
                $attendee_data = $this->prepare_attendee_data($attendee);
                $results['test_data'][] = $attendee_data;
            }

            return $results;
        }

        // Production sync
        if (!$this->is_sync_allowed()) {
            $results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.';
            return $results;
        }

        $results['version'] = 'PRODUCTION_BATCH_V1';

        // Wrap entire loop in try-catch for safety (catches PHP 7+ errors)
        try {
            foreach ($all_attendees as $attendee) {
                
                try {
                    $attendee_data = $this->prepare_attendee_data($attendee);
                    
                    // Check if data has changed using hash comparison
                    if (!$this->should_sync($attendee->ID, $attendee_data)) {
                        $results['skipped']++;
                        continue;
                    }

                    // Check for any usable email (fallback to mq_email if email is empty)
                    $has_email = !empty($attendee_data['email']) || !empty($attendee_data['mq_email']);
                    if (!$has_email) {
                        $results['failed']++;
                        $results['errors'][] = sprintf('Attendee %s: No email address', $attendee->ID);
                        continue;
                    }

                    // Step 1: Create/Update Contact
                    $contact_id = $this->ensure_contact_exists($attendee_data);
                    if ($contact_id) {
                        $results['contacts_created']++;
                    }

                    if (count($results['responses']) < 5) {
                         $cid_debug = $contact_id ? $contact_id : 'NULL';
                         $results['responses'][] = array('type' => 'debug', 'msg' => "Debug: Attendee {$attendee->ID} found Contact ID: " . $cid_debug);
                    }

                    // If contact creation failed, count as failed and skip hash update
                    if (!$contact_id) {
                        $results['failed']++;
                        $error_detail = $this->last_contact_error ?: 'Unknown error';
                        $results['errors'][] = sprintf('Attendee %s: No contact_id created. %s', $attendee->ID, $error_detail);
                        continue;
                    }

                    $attendee_sync_ok = true;

                    // Step 2: Create Campaign Member (link Contact to Campaign)
                    if (!empty($attendee_data['event_id'])) {
                        $campaign_id = get_post_meta($attendee_data['event_id'], '_zoho_campaign_id', true);

                        // Debug: Log event_id and campaign_id for troubleshooting
                        if (count($results['responses']) < 10) {
                            $results['responses'][] = array(
                                'type' => 'campaign_lookup',
                                'attendee_id' => $attendee->ID,
                                'event_id' => $attendee_data['event_id'],
                                'campaign_id' => $campaign_id ?: 'NOT_SET'
                            );
                        }

                        if ($campaign_id) {
                            $assoc_response = $this->create_campaign_member($contact_id, $campaign_id, 'Attended');

                            // ALWAYS capture the first link attempt for debugging
                            if (!isset($results['first_link_attempt'])) {
                                $results['first_link_attempt'] = array(
                                    'attendee_id' => $attendee->ID,
                                    'contact_id' => $contact_id,
                                    'campaign_id' => $campaign_id,
                                    'response' => $assoc_response
                                );
                            }

                            // Debug: Add responses (increased limit to 20)
                            if (count($results['responses']) < 20) {
                                $results['responses'][] = array('type' => 'link_attempt', 'id' => $attendee->ID, 'response' => $assoc_response);
                            }

                            if (isset($assoc_response['status']) && $assoc_response['status'] === 'success') {
                                $results['campaign_members_created']++;
                            } elseif (isset($assoc_response['data'][0]['status']) && $assoc_response['data'][0]['status'] === 'success') {
                                $results['campaign_members_created']++;
                            } else {
                                $results['errors'][] = sprintf('Attendee %s: Failed to link to campaign. Response: %s', $attendee->ID, json_encode($assoc_response));
                                if (isset($assoc_response['data'])) {
                                    $results['responses'][] = array('type' => 'link_error', 'id' => $attendee->ID, 'response' => $assoc_response);
                                }
                            }
                        } else {
                            $results['errors'][] = sprintf('Attendee %s: Event %s has no Zoho Campaign ID', $attendee->ID, $attendee_data['event_id']);
                        }
                    } elseif (empty($attendee_data['event_id'])) {
                        // Debug: Log when event_id is missing
                        if (count($results['responses']) < 10) {
                            $results['responses'][] = array('type' => 'missing_event_id', 'attendee_id' => $attendee->ID);
                        }
                    }

                    // Contact was created/found successfully - count as synced
                    $results['synced']++;

                    // Only update hash on confirmed contact success
                    update_post_meta($attendee->ID, '_zoho_contact_id', $contact_id);
                    update_post_meta($attendee->ID, '_zoho_sync_hash', $this->generate_sync_hash($attendee_data));

                } catch (\Throwable $e) {
                    // Catch both Exception and Error (PHP 7+)
                    $results['failed']++;
                    $results['errors'][] = sprintf('Attendee %s: [%s] %s', $attendee->ID, get_class($e), $e->getMessage());
                }
            }
        } catch (\Throwable $e) {
            // Outer catch for any loop-level errors
            $results['errors'][] = 'Loop error: [' . get_class($e) . '] ' . $e->getMessage();
        }

        return $results;
    }

    /**
     * Sync RSVPs to Zoho Leads + Campaign Members
     *
     * @param int $offset Starting offset for pagination
     * @param int $limit Number of records per batch
     * @param int|null $since_timestamp Optional timestamp to filter modified records
     * @return array Sync results
     */
    public function sync_rsvps($offset = 0, $limit = 50, $since_timestamp = null) {
        $results = array(
            'total' => 0,
            'synced' => 0,
            'skipped' => 0,
            'failed' => 0,
            'errors' => array(),
            'staging_mode' => $this->is_staging,
            'leads_created' => 0,
            'campaign_members_created' => 0,
            'debug_info' => HVAC_Zoho_CRM_Auth::get_debug_mode_info(),
            'has_more' => false,
            'next_offset' => 0,
            'batch_offset' => $offset,
            'incremental_sync' => !is_null($since_timestamp)
        );

        // Build query args
        $query_args = array(
            'post_type' => 'tribe_rsvp_attendees',
            'post_status' => 'any',
            'posts_per_page' => -1,
            'fields' => 'ids'
        );
        
        // Add date filter for incremental sync
        if ($since_timestamp) {
            $query_args['date_query'] = array(
                array(
                    'column' => 'post_date',
                    'after' => date('Y-m-d H:i:s', $since_timestamp),
                    'inclusive' => true,
                )
            );
        }

        // Get total count first
        $count_query = new WP_Query($query_args);
        $total_count = $count_query->found_posts;
        $results['total'] = $total_count;

        if ($total_count === 0) {
            $results['message'] = 'No RSVP attendees found.';
            return $results;
        }

        // Get paginated RSVPs
        $paginated_args = array_merge($query_args, array(
            'posts_per_page' => $limit,
            'offset' => $offset,
            'fields' => 'all',
        ));
        $rsvps = get_posts($paginated_args);

        // Calculate pagination
        $results['has_more'] = ($offset + count($rsvps)) < $total_count;
        $results['next_offset'] = $offset + count($rsvps);

        // If staging mode, simulate the sync
        if ($this->is_staging) {
            $results['message'] = 'STAGING MODE: Sync simulation only. No data sent to Zoho.';
            $results['synced'] = count($rsvps);
            $results['test_data'] = array();

            foreach ($rsvps as $rsvp) {
                $rsvp_data = $this->prepare_rsvp_data($rsvp);
                $results['test_data'][] = $rsvp_data;
            }

            return $results;
        }

        // Production sync
        if (!$this->is_sync_allowed()) {
            $results['errors'][] = 'Sync not allowed on this domain. Only upskillhvac.com can sync to production.';
            return $results;
        }

        foreach ($rsvps as $rsvp) {
            try {
                $rsvp_data = $this->prepare_rsvp_data($rsvp);

                // Check if data has changed using hash comparison
                if (!$this->should_sync($rsvp->ID, $rsvp_data)) {
                    $results['skipped']++;
                    continue;
                }

                if (empty($rsvp_data['email'])) {
                    $results['failed']++;
                    $results['errors'][] = sprintf('RSVP %s: No email address', $rsvp->ID);
                    continue;
                }

                // Step 1: Create/Update Lead
                $lead_id = $this->ensure_lead_exists($rsvp_data);

                if (!$lead_id) {
                    $results['failed']++;
                    $results['errors'][] = sprintf('RSVP %s: Failed to create/find lead', $rsvp->ID);
                    continue;
                }

                $results['leads_created']++;

                // Step 2: Create Campaign Member (link Lead to Campaign)
                if (!empty($rsvp_data['event_id'])) {
                    $campaign_id = get_post_meta($rsvp_data['event_id'], '_zoho_campaign_id', true);
                    if ($campaign_id) {
                        $assoc_response = $this->create_campaign_member($lead_id, $campaign_id, 'Responded', 'Leads');
                        if (isset($assoc_response['status']) && $assoc_response['status'] === 'success') {
                            $results['campaign_members_created']++;
                        } elseif (isset($assoc_response['data'][0]['status']) && $assoc_response['data'][0]['status'] === 'success') {
                            $results['campaign_members_created']++;
                        } else {
                            $results['errors'][] = sprintf('RSVP %s: Failed to link to campaign. Response: %s', $rsvp->ID, json_encode($assoc_response));
                            if (isset($assoc_response['data'])) {
                                $results['responses'][] = array('type' => 'link_error', 'id' => $rsvp->ID, 'response' => $assoc_response);
                            }
                        }
                    } else {
                        $results['errors'][] = sprintf('RSVP %s: Event %s has no Zoho Campaign ID', $rsvp->ID, $rsvp_data['event_id']);
                    }
                }

                $results['synced']++;

                // Only update hash on confirmed lead creation success
                update_post_meta($rsvp->ID, '_zoho_lead_id', $lead_id);
                update_post_meta($rsvp->ID, '_zoho_sync_hash', $this->generate_sync_hash($rsvp_data));

            } catch (\Throwable $e) {
                $results['failed']++;
                $results['errors'][] = sprintf('RSVP %s: [%s] %s', $rsvp->ID, get_class($e), $e->getMessage());
            }
        }

        return $results;
    }

    /**
     * Ensure a Contact exists in Zoho (create or get existing, update fields)
     *
     * @param array $data Attendee data
     * @return string|null Zoho Contact ID
     */
    private function ensure_contact_exists($data) {
        // Determine primary email for lookup
        // Prefer measureQuick email, fallback to attendee email if mq_email is empty
        // If both are empty, we can't create a contact
        $attendee_email = !empty($data['email']) ? $data['email'] : '';
        $mq_email = !empty($data['mq_email']) ? $data['mq_email'] : '';
        
        // Use mq_email as primary if available, otherwise use attendee_email
        $primary_email = !empty($mq_email) ? $mq_email : $attendee_email;
        
        // Set secondary email (only if different from primary and not empty)
        $other_email = '';
        if (!empty($mq_email) && !empty($attendee_email) && $mq_email !== $attendee_email) {
            $other_email = $attendee_email;
        }
        
        // Build contact data for create or update
        $contact_data = array(
            'First_Name' => $data['first_name'] ?: 'Unknown',
            'Last_Name' => $data['last_name'] ?: 'Attendee',
            'Email' => $primary_email,
            'Lead_Source' => 'Event Tickets',
            'Contact_Type' => 'Attendee',
            'WordPress_Attendee_ID' => $data['attendee_id'],
        );
        
        // Add Other Email if we have both emails
        if (!empty($other_email)) {
            $contact_data['Secondary_Email'] = $other_email;
        }
        
        // Add Mobile phone if available
        if (!empty($data['phone'])) {
            $contact_data['Mobile'] = $data['phone'];
        }
        
        // Add Primary Role if available
        if (!empty($data['company_role'])) {
            $contact_data['Primary_Role'] = $data['company_role'];
        }
        
        // Search for existing contact by primary email
        $search_response = $this->auth->make_api_request(
            '/Contacts/search?criteria=(Email:equals:' . urlencode($primary_email) . ')',
            'GET'
        );

        if (!empty($search_response['data'])) {
            $contact_id = $search_response['data'][0]['id'];
            
            // UPDATE existing contact with new field data
            $this->auth->make_api_request("/Contacts/{$contact_id}", 'PUT', array(
                'data' => array($contact_data)
            ));
            
            return $contact_id;
        }

        // Create new contact
        $create_response = $this->auth->make_api_request('/Contacts', 'POST', array(
            'data' => array($contact_data)
        ));

        if (!empty($create_response['data'][0]['details']['id'])) {
            return $create_response['data'][0]['details']['id'];
        }
        
        // Handle DUPLICATE_DATA: Contact exists but with different email
        // Extract existing contact ID from the error response and use it
        if (!empty($create_response['data'][0]['code']) && 
            $create_response['data'][0]['code'] === 'DUPLICATE_DATA' &&
            !empty($create_response['data'][0]['details']['duplicate_record']['id'])) {
            
            $existing_id = $create_response['data'][0]['details']['duplicate_record']['id'];
            
            // Update the existing contact with new data
            $this->auth->make_api_request("/Contacts/{$existing_id}", 'PUT', array(
                'data' => array($contact_data)
            ));
            
            return $existing_id;
        }
        
        // Store error for debugging and log it
        $this->last_contact_error = "Email: {$primary_email}, Response: " . json_encode($create_response);
        $this->auth->log_debug("Failed to create contact: " . $this->last_contact_error);

        return null;
    }

    /**
     * Ensure a Lead exists in Zoho (create or get existing)
     *
     * @param array $data RSVP data
     * @return string|null Zoho Lead ID
     */
    private function ensure_lead_exists($data) {
        // Search for existing lead by email
        $search_response = $this->auth->make_api_request(
            '/Leads/search?criteria=(Email:equals:' . urlencode($data['email']) . ')',
            'GET'
        );

        if (!empty($search_response['data'])) {
            return $search_response['data'][0]['id'];
        }

        // Create new lead
        $lead_data = array(
            'First_Name' => $data['first_name'] ?: 'Unknown',
            'Last_Name' => $data['last_name'] ?: 'RSVP',
            'Email' => $data['email'],
            'Lead_Source' => 'Event RSVP',
            'Lead_Status' => 'Contacted',
            'WordPress_RSVP_ID' => $data['rsvp_id'],
        );

        $create_response = $this->auth->make_api_request('/Leads', 'POST', array(
            'data' => array($lead_data)
        ));

        if (!empty($create_response['data'][0]['details']['id'])) {
            return $create_response['data'][0]['details']['id'];
        }

        return null;
    }

    /**
     * Create a Campaign Member (link Contact/Lead to Campaign)
     *
     * @param string $record_id Contact or Lead ID
     * @param string $campaign_id Campaign ID
     * @param string $status Member status (Invited, Responded, Attended, etc.)
     * @param string $type 'Contacts' or 'Leads'
     */
    private function create_campaign_member($record_id, $campaign_id, $status = 'Attended', $type = 'Contacts') {
        // v6 API: Associate Contact/Lead with Campaign via related list
        // PUT /{Contacts|Leads}/{record_id}/Campaigns
        // The Contact/Lead is the parent, Campaigns is the related list
        // Campaign ID goes in the request body, not the URL
        
        $endpoint = "/{$type}/{$record_id}/Campaigns";
        
        // Debug: Log the association attempt
        $this->auth->log_debug("Attempting to associate {$type} {$record_id} with Campaign {$campaign_id} via {$endpoint}");

        $response = $this->auth->make_api_request($endpoint, 'PUT', array(
            'data' => array(
                array(
                    'id' => $campaign_id,
                    'Member_Status' => $status
                )
            )
        ));
        
        return $response;
    }
    
    /**
     * Prepare campaign data for Zoho
     *
     * @param WP_Post $event Event post object
     * @return array Campaign data
     */
    private function prepare_campaign_data($event) {
        $trainer_id = get_post_meta($event->ID, '_trainer_id', true);
        $trainer = get_user_by('id', $trainer_id);
        $venue_id = tribe_get_venue_id($event->ID);
        
        // Get venue details
        $venue_name = '';
        $venue_city = '';
        $venue_state = '';
        $venue_details = '';
        
        if ($venue_id) {
            $venue_name = html_entity_decode(get_the_title($venue_id), ENT_QUOTES | ENT_HTML5, 'UTF-8');
            $venue_city = tribe_get_city($event->ID);
            $venue_state = tribe_get_state($event->ID);
            $venue_address = tribe_get_address($event->ID);
            
            // Build venue details string
            $venue_parts = array_filter(array($venue_name, $venue_address, $venue_city, $venue_state));
            $venue_details = implode(', ', $venue_parts);
        }
        
        // Sanitize description: strip tags and limit length, then decode entities
        $description = wp_strip_all_tags(get_the_content(null, false, $event));
        $description = html_entity_decode($description, ENT_QUOTES | ENT_HTML5, 'UTF-8');
        
        if (strlen($description) > 30000) {
            $description = substr($description, 0, 30000) . '...';
        }

        $campaign_name = html_entity_decode(get_the_title($event->ID), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $trainer_name = $trainer ? html_entity_decode($trainer->display_name, ENT_QUOTES | ENT_HTML5, 'UTF-8') : '';
        
        // Get trainer's Zoho Contact ID (if they've been synced)
        $instructor_zoho_id = '';
        if ($trainer) {
            $instructor_zoho_id = get_user_meta($trainer->ID, '_zoho_contact_id', true);
        }
        
        // Calculate ticket counts
        $total_capacity = intval(get_post_meta($event->ID, '_stock', true));
        $tickets_sold = $this->get_attendee_count($event->ID);
        $tickets_available = max(0, $total_capacity - $tickets_sold);
        
        // Build campaign data array
        $data = array(
            'Campaign_Name' => substr($campaign_name, 0, 200),
            'Start_Date' => tribe_get_start_date($event->ID, false, 'Y-m-d'),
            'End_Date' => tribe_get_end_date($event->ID, false, 'Y-m-d'),
            'Status' => (tribe_get_end_date($event->ID, false, 'U') < time()) ? 'Completed' : 'Active',
            'Description' => $description,
            'Type' => 'Training Event',
            'Expected_Revenue' => floatval(get_post_meta($event->ID, '_price', true)),
            'Total_Capacity' => $total_capacity,
            'Tickets_Sold' => $tickets_sold,
            'Tickets_Available' => $tickets_available,
            'Event_City' => substr($venue_city, 0, 100),
            'Event_State' => substr($venue_state, 0, 100),
            'Event_URL' => get_permalink($event->ID),
            'Venue_Details' => substr($venue_details, 0, 250),
            'Instructor_Email' => $trainer ? $trainer->user_email : '',
        );
        
        // Add Instructor lookup only if we have a valid Zoho Contact ID
        if (!empty($instructor_zoho_id)) {
            $data['Instructor'] = $instructor_zoho_id;
        }
        
        return $data;
    }
    
    /**
     * Get count of attendees for an event
     *
     * @param int $event_id Event post ID
     * @return int Attendee count
     */
    private function get_attendee_count($event_id) {
        // Check Tickets Commerce attendees
        $tc_attendees = get_posts(array(
            'post_type' => 'tec_tc_attendee',
            'posts_per_page' => -1,
            'post_status' => 'any',
            'meta_query' => array(
                array(
                    'key' => '_tec_tickets_commerce_event',
                    'value' => $event_id,
                    'compare' => '='
                )
            ),
            'fields' => 'ids'
        ));
        
        // Also check PayPal attendees
        $tpp_attendees = get_posts(array(
            'post_type' => 'tribe_tpp_attendees',
            'posts_per_page' => -1,
            'post_status' => 'any',
            'meta_query' => array(
                array(
                    'key' => '_tribe_tpp_event',
                    'value' => $event_id,
                    'compare' => '='
                )
            ),
            'fields' => 'ids'
        ));
        
        return count($tc_attendees) + count($tpp_attendees);
    }
    
    /**
     * Prepare contact data for Zoho
     *
     * @param WP_User $user User object
     * @return array Contact data
     */
    private function prepare_contact_data($user) {
        // Map WordPress roles to Zoho Contact_Type
        if (in_array('hvac_master_trainer', $user->roles)) {
            $role = 'Master Trainer';
        } elseif (in_array('hvac_trainer', $user->roles)) {
            $role = 'Trainer';
        } else {
            $role = 'Trainee';
        }

        // Last_Name is required by Zoho - fallback to display_name or username
        $last_name = html_entity_decode(get_user_meta($user->ID, 'last_name', true), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        if (empty(trim($last_name))) {
            $last_name = $user->display_name ?: $user->user_login;
        }

        // Sanitize phone: strip to digits and leading +, require 10+ digits for validity
        $raw_phone = get_user_meta($user->ID, 'phone_number', true);
        $phone_digits = preg_replace('/\D/', '', $raw_phone);
        $phone = '';
        if (strlen($phone_digits) >= 10) {
            // Preserve leading + if present, otherwise just use digits
            $phone = (strpos(trim($raw_phone), '+') === 0 ? '+' : '') . $phone_digits;
        }

        $data = array(
            'First_Name' => html_entity_decode(get_user_meta($user->ID, 'first_name', true), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
            'Last_Name' => $last_name,
            'Email' => $user->user_email,
            'Title' => html_entity_decode(get_user_meta($user->ID, 'hvac_professional_title', true), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
            'Company' => html_entity_decode(get_user_meta($user->ID, 'hvac_company_name', true), ENT_QUOTES | ENT_HTML5, 'UTF-8'),
            'Lead_Source' => 'HVAC Community Events',
            'Contact_Type' => $role,
            'WordPress_User_ID' => $user->ID,
            'License_Number' => get_user_meta($user->ID, 'hvac_license_number', true),
            'Years_Experience' => get_user_meta($user->ID, 'hvac_years_experience', true),
            'Certification' => get_user_meta($user->ID, 'hvac_certifications', true)
        );

        // Only include Phone if we have a valid value (10+ digits)
        if (!empty($phone)) {
            $data['Phone'] = $phone;
        }

        return $data;
    }
    
    /**
     * Prepare invoice data for Tickets Commerce order
     *
     * @param WP_Post $order TC Order post object
     * @return array Invoice data
     */
    private function prepare_tc_invoice_data($order) {
        $purchaser_email = get_post_meta($order->ID, '_tec_tc_order_purchaser_email', true);
        $purchaser_name = get_post_meta($order->ID, '_tec_tc_order_purchaser_name', true);
        $gateway = get_post_meta($order->ID, '_tec_tc_order_gateway', true);
        $gateway_order_id = get_post_meta($order->ID, '_tec_tc_order_gateway_order_id', true);
        $order_items = get_post_meta($order->ID, '_tec_tc_order_items', true);

        // Parse order items to get event info and line items
        $items = array();
        $event_titles = array();
        $total = 0;

        if (is_array($order_items)) {
            foreach ($order_items as $item) {
                $event_id = isset($item['event_id']) ? $item['event_id'] : 0;
                $ticket_id = isset($item['ticket_id']) ? $item['ticket_id'] : 0;
                $quantity = isset($item['quantity']) ? intval($item['quantity']) : 1;
                $price = isset($item['price']) ? floatval($item['price']) : 0;

                if ($event_id) {
                    $event_titles[] = get_the_title($event_id);
                }

                $ticket_name = $ticket_id ? get_the_title($ticket_id) : 'Event Ticket';

                $items[] = array(
                    'product' => array('name' => $ticket_name),
                    'quantity' => $quantity,
                    'list_price' => $price,
                    'total' => $price * $quantity
                );

                $total += $price * $quantity;
            }
        }

        $event_summary = !empty($event_titles) ? implode(', ', array_unique($event_titles)) : 'Event Tickets';

        return array(
            'Subject' => "Ticket Purchase - {$event_summary}",
            'Invoice_Date' => date('Y-m-d', strtotime($order->post_date)),
            'Status' => 'Paid',
            'Account_Name' => $purchaser_name,
            'Description' => "Payment via {$gateway}. Transaction: {$gateway_order_id}",
            'Grand_Total' => $total,
            'WordPress_Order_ID' => $order->ID,
            'Product_Details' => $items
        );
    }

    /**
     * Prepare attendee data from Event Tickets attendee post
     *
     * @param WP_Post $attendee Attendee post object
     * @return array Attendee data
     */
    private function prepare_attendee_data($attendee) {
        $post_type = $attendee->post_type;

        // Handle different attendee post types
        if ($post_type === 'tec_tc_attendee') {
            // Tickets Commerce attendee (meta keys: _tec_tickets_commerce_*)
            $event_id = get_post_meta($attendee->ID, '_tec_tickets_commerce_event', true);
            $ticket_id = get_post_meta($attendee->ID, '_tec_tickets_commerce_ticket', true);
            $order_id = get_post_meta($attendee->ID, '_tec_tickets_commerce_order', true);
            $full_name = get_post_meta($attendee->ID, '_tec_tickets_commerce_full_name', true);
            $email = get_post_meta($attendee->ID, '_tec_tickets_commerce_email', true);
            $checkin = get_post_meta($attendee->ID, '_tec_tickets_commerce_checked_in', true);
            
            // Custom attendee fields stored in _tec_tickets_commerce_attendee_fields (serialized array)
            // Field names use hyphens: attendee-cell-phone, company-role, measurequick-email
            $attendee_fields = get_post_meta($attendee->ID, '_tec_tickets_commerce_attendee_fields', true);
            
            $phone = '';
            $company_role = '';
            $mq_email = '';
            
            if (is_array($attendee_fields)) {
                $phone = isset($attendee_fields['attendee-cell-phone']) ? $attendee_fields['attendee-cell-phone'] : '';
                $company_role = isset($attendee_fields['company-role']) ? $attendee_fields['company-role'] : '';
                $mq_email = isset($attendee_fields['measurequick-email']) ? $attendee_fields['measurequick-email'] : '';
            }
            
        } else {
            // PayPal or other attendee types (tribe_tpp_attendees)
            $event_id = get_post_meta($attendee->ID, '_tribe_tpp_event', true);
            $ticket_id = get_post_meta($attendee->ID, '_tribe_tpp_product', true);
            $order_id = get_post_meta($attendee->ID, '_tribe_tpp_order', true);
            $full_name = get_post_meta($attendee->ID, '_tribe_tickets_full_name', true);
            $email = get_post_meta($attendee->ID, '_tribe_tickets_email', true);
            $checkin = get_post_meta($attendee->ID, '_tribe_tpp_checkin', true);
            
            // Legacy attendee meta for custom fields (hyphenated keys)
            $attendee_meta = get_post_meta($attendee->ID, '_tribe_tickets_meta', true);
            $phone = is_array($attendee_meta) && isset($attendee_meta['attendee-cell-phone']) ? $attendee_meta['attendee-cell-phone'] : '';
            $company_role = is_array($attendee_meta) && isset($attendee_meta['company-role']) ? $attendee_meta['company-role'] : '';
            $mq_email = is_array($attendee_meta) && isset($attendee_meta['measurequick-email']) ? $attendee_meta['measurequick-email'] : '';
        }

        // Parse full name into first/last
        $name_parts = explode(' ', trim($full_name), 2);
        $first_name = isset($name_parts[0]) ? $name_parts[0] : '';
        $last_name = isset($name_parts[1]) ? $name_parts[1] : '';

        $event_title = $event_id ? get_the_title($event_id) : '';
        $ticket_name = $ticket_id ? get_the_title($ticket_id) : '';

        return array(
            'attendee_id' => $attendee->ID,
            'post_type' => $post_type,
            'event_id' => $event_id,
            'event_title' => $event_title,
            'ticket_id' => $ticket_id,
            'ticket_name' => $ticket_name,
            'order_id' => $order_id,
            'full_name' => $full_name,
            'first_name' => $first_name,
            'last_name' => $last_name,
            'email' => $email,
            'phone' => $phone,
            'company_role' => $company_role,
            'mq_email' => $mq_email,
            'checked_in' => !empty($checkin),
            'zoho_contact' => array(
                'First_Name' => $first_name ?: 'Unknown',
                'Last_Name' => $last_name ?: 'Attendee',
                'Email' => $email,
                'Lead_Source' => 'Event Tickets',
                'Contact_Type' => 'Attendee',
            ),
            'zoho_campaign_member' => array(
                'Member_Status' => !empty($checkin) ? 'Attended' : 'Registered',
            )
        );
    }

    /**
     * Prepare RSVP data from Event Tickets RSVP attendee post
     *
     * @param WP_Post $rsvp RSVP attendee post object
     * @return array RSVP data
     */
    private function prepare_rsvp_data($rsvp) {
        $event_id = get_post_meta($rsvp->ID, '_tribe_rsvp_event', true);
        $ticket_id = get_post_meta($rsvp->ID, '_tribe_rsvp_product', true);
        $full_name = get_post_meta($rsvp->ID, '_tribe_rsvp_full_name', true);
        $email = get_post_meta($rsvp->ID, '_tribe_rsvp_email', true);
        $rsvp_status = get_post_meta($rsvp->ID, '_tribe_rsvp_status', true); // yes, no

        // Parse full name into first/last
        $name_parts = explode(' ', trim($full_name), 2);
        $first_name = isset($name_parts[0]) ? $name_parts[0] : '';
        $last_name = isset($name_parts[1]) ? $name_parts[1] : '';

        $event_title = $event_id ? get_the_title($event_id) : '';

        return array(
            'rsvp_id' => $rsvp->ID,
            'event_id' => $event_id,
            'event_title' => $event_title,
            'ticket_id' => $ticket_id,
            'full_name' => $full_name,
            'first_name' => $first_name,
            'last_name' => $last_name,
            'email' => $email,
            'rsvp_status' => $rsvp_status,
            'zoho_lead' => array(
                'First_Name' => $first_name ?: 'Unknown',
                'Last_Name' => $last_name ?: 'RSVP',
                'Email' => $email,
                'Lead_Source' => 'Event RSVP',
                'Lead_Status' => $rsvp_status === 'yes' ? 'Contacted' : 'Not Interested',
            ),
            'zoho_campaign_member' => array(
                'Member_Status' => $rsvp_status === 'yes' ? 'Responded' : 'Not Responded',
            )
        );
    }
}