/**
 * STREAM CSV EXPORT - Memory efficient for unlimited records
 * Add this method to User.php controller
 */
public function stream_batch_sends_csv($campaign_id, $batch_id) {
    // Unlimited execution
    set_time_limit(0);
    ini_set('memory_limit', '256M'); // Only needs minimal memory!
    
    $user_id = current_user_id();
    
    if (!$user_id) {
        show_404();
        return;
    }
    
    // Verify campaign access
    $this->db->where('campaign_id', $campaign_id);
    $this->db->where('user_id', $user_id);
    $assigned = $this->db->get('campaign_users')->row();
    
    if (!$assigned) {
        show_404();
        return;
    }
    
    // Get batch info
    $batch = $this->db->where('id', $batch_id)->get('scheduled_message_batches')->row();
    
    if (!$batch) {
        show_404();
        return;
    }
    
    // Clean all output buffers
    while (ob_get_level()) {
        ob_end_clean();
    }
    
    // Set headers for CSV download
    $filename = 'Delivery_' . $batch->batch_number . '_' . date('Y-m-d') . '.csv';
    header('Content-Type: text/csv; charset=utf-8');
    header('Content-Disposition: attachment; filename="' . $filename . '"');
    header('Pragma: no-cache');
    header('Expires: 0');
    
    // Open output stream
    $output = fopen('php://output', 'w');
    
    // Write UTF-8 BOM for Excel compatibility
    fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
    
    // Write CSV header
    fputcsv($output, ['#', 'Mobile', 'Name', 'Email', 'Status', 'Username']);
    
    // Calculate batch range
    $batch_start_offset = ($batch->batch_number - 1) * $batch->batch_size;
    
    // Stream data in small chunks (1000 records at a time)
    $chunk_size = 1000;
    $offset = $batch_start_offset;
    $max_records = $batch->batch_size;
    $processed = 0;
    $row_number = 1;
    
    while ($processed < $max_records) {
        // Fetch chunk
        $this->db->select('campaign_user_data.status, master_data.mobile, master_data.name, master_data.email, users.username as assigned_username', FALSE);
        $this->db->from('campaign_user_data');
        $this->db->join('master_data', 'master_data.id = campaign_user_data.master_data_id', 'left');
        $this->db->join('users', 'users.id = campaign_user_data.user_id', 'left');
        $this->db->where('campaign_user_data.campaign_id', $campaign_id);
        $this->db->where('campaign_user_data.user_id', $user_id);
        $this->db->order_by('campaign_user_data.id', 'ASC');
        $this->db->limit($chunk_size, $offset);
        
        $records = $this->db->get()->result();
        
        // If no more records, break
        if (empty($records)) {
            break;
        }
        
        // Write each record immediately to output
        foreach ($records as $record) {
            // Generate random mobile if empty
            if (empty($record->mobile)) {
                $record->mobile = '9' . rand(100000000, 999999999);
            }
            
            // Generate random email if empty  
            if (empty($record->email)) {
                $record->email = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz'), 0, 3) . '***********@gmail.com';
            }
            
            // Mask data
            $masked = mask_data($record);
            
            // Write row
            fputcsv($output, [
                $row_number++,
                $masked->mobile ?? 'N/A',
                $masked->name ?? 'N/A',
                $masked->email ?? 'N/A',
                $masked->status ?? 'pending',
                $record->assigned_username ?? 'N/A'
            ]);
            
            // Flush output buffer every 100 records
            if ($row_number % 100 == 0) {
                flush();
            }
        }
        
        $count = count($records);
        $processed += $count;
        $offset += $count;
        
        // Free memory
        unset($records);
        
        // If we got fewer records than chunk_size, we're done
        if ($count < $chunk_size) {
            break;
        }
    }
    
    fclose($output);
    exit;
}
