<?php

/**
 * Buffer Manager
 *
 * Manages log buffering to SQLite database with automatic cleanup
 * and batch processing capabilities.
 *
 * @package CloudXM\SDK\Buffer
 * @since 1.0.0
 */

namespace CloudXM\SDK\Buffer;

use CloudXM\SDK\Utilities\Config;

/**
 * Buffer Manager
 */
final class BufferManager
{
    /**
     * Singleton instance
     */
    private static ?BufferManager $instance = null;

    /**
     * SQLite database connection
     */
    private ?\PDO $db = null;

    /**
     * Database file path
     */
    private string $dbPath;

    /**
     * Configuration instance
     */
    private ?Config $config = null;

    /**
     * Lock file path for batch operations
     */
    private string $lockFilePath;

    /**
     * Lock file handle
     */
    private $lockHandle = null;

    /**
     * Memory buffer for immediate writes
     */
    private array $memoryBuffer = [];

    /**
     * Get singleton instance
     */
    public static function getInstance(): BufferManager
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Private constructor
     */
    private function __construct()
    {
        $this->dbPath = WC_CXM_LOGGER_BUFFER_DIR . '/logs.db';
        $this->lockFilePath = WC_CXM_LOGGER_BUFFER_DIR . '/.batch_lock';

        // Initialize config if available
        if (class_exists('\\CloudXM\\SDK\\Utilities\\Config')) {
            $this->config = Config::getInstance();
        }
    }

    /**
     * Initialize the buffer manager
     */
    public function initialize(): void
    {
        $this->ensureDatabaseExists();
        $this->setupTables();

        // Register the log buffering hook
        add_action('cxm_log_enriched', [$this, 'bufferLogEntry'], 10, 1);

        // Register cleanup cron
        if (!wp_next_scheduled('cxm_logger_buffer_cleanup')) {
            wp_schedule_event(time(), 'daily', 'cxm_logger_buffer_cleanup');
        }
        add_action('cxm_logger_buffer_cleanup', [$this, 'cleanupOldEntries']);
    }

    /**
     * Buffer a log entry
     */
    public function bufferLogEntry(array $logEntry): void
    {
        // Add to memory buffer first for immediate availability
        $this->memoryBuffer[] = $logEntry;

        // Limit memory buffer size
        $maxMemorySize = $this->config ? $this->config->get('buffer.max_memory_entries', 1000) : 1000;
        if (count($this->memoryBuffer) > $maxMemorySize) {
            array_shift($this->memoryBuffer);
        }

        // Ensure database connection is fresh
        $this->ensureConnectionFresh();

        // Persist to database
        $this->persistToDatabase($logEntry);

        // Check if we should process immediately based on buffer size
        $immediateThreshold = $this->config ? $this->config->get('buffer.immediate_batch_threshold', 50) : 50;
        $memoryBufferSize = count($this->memoryBuffer);

        // Get database buffer size for comparison
        $dbBufferSize = 0;
        try {
            if ($this->db) {
                $stmt = $this->db->query("SELECT COUNT(*) as total FROM log_entries WHERE sent = 0");
                $result = $stmt->fetch(\PDO::FETCH_ASSOC);
                $dbBufferSize = (int) ($result['total'] ?? 0);
            }
        } catch (\Exception $e) {
            // Log locking errors specifically
            $errorCode = $this->db->errorCode();
            if ($errorCode === 'HY000' && strpos($e->getMessage(), 'database is locked') !== false) {
                $this->logError('Database locking detected during buffer size check', [
                    'error_code' => $errorCode,
                    'process_id' => getmypid(),
                    'correlation_id' => uniqid('db-lock-count-', true)
                ]);
            }
            // Continue with dbBufferSize = 0
        }

        // Throttled buffer size logging - only log every 100th check to avoid recursive loops
        static $logCounter = 0;
        $logCounter++;
        if ($logCounter % 100 === 0) {
            $this->logInfo('Buffer size check (throttled)', [
                'memory_buffer_size' => $memoryBufferSize,
                'database_buffer_size' => $dbBufferSize,
                'immediate_threshold' => $immediateThreshold,
                'correlation_id' => uniqid('buffer-check-', true)
            ]);
        }

        if ($memoryBufferSize >= $immediateThreshold) {
            // Trigger immediate batch processing for better log delivery
            // Use a short delay to batch multiple logs that might come in rapid succession
            if (!wp_next_scheduled('cxm_logger_immediate_batch')) {
                wp_schedule_single_event(time() + 5, 'cxm_logger_immediate_batch');
            }
        }

        // Also check database buffer size for immediate processing
        $dbThreshold = $this->config ? $this->config->get('buffer.database_immediate_threshold', 1000) : 1000;
        if ($dbBufferSize >= $dbThreshold) {
            // Use a short delay to batch multiple logs that might come in rapid succession
            if (!wp_next_scheduled('cxm_logger_immediate_batch')) {
                wp_schedule_single_event(time() + 5, 'cxm_logger_immediate_batch');
            }
        }
    }

    /**
     * Acquire file lock for batch operations
     */
    private function acquireBatchLock(): bool
    {
        if ($this->lockHandle !== null) {
            return true; // Already locked
        }

        $this->lockHandle = fopen($this->lockFilePath, 'w');
        if ($this->lockHandle === false) {
            $this->logError('Failed to open lock file', [
                'lock_file' => $this->lockFilePath,
                'correlation_id' => uniqid('lock-open-fail-', true)
            ]);
            return false;
        }

        // Try to acquire exclusive lock with timeout (10 seconds)
        $lockAcquired = false;
        $startTime = time();

        while (!$lockAcquired && (time() - $startTime) < 10) {
            $lockAcquired = flock($this->lockHandle, LOCK_EX | LOCK_NB);
            if (!$lockAcquired) {
                usleep(100000); // Wait 0.1 seconds before retrying
            }
        }

        if (!$lockAcquired) {
            fclose($this->lockHandle);
            $this->lockHandle = null;
            $this->logError('Batch lock timeout - could not acquire lock within 10 seconds', [
                'lock_file' => $this->lockFilePath,
                'process_id' => getmypid(),
                'correlation_id' => uniqid('lock-timeout-', true)
            ]);
            return false;
        }

        // Write process info to lock file
        fwrite($this->lockHandle, json_encode([
            'process_id' => getmypid(),
            'timestamp' => time(),
            'operation' => 'batch_processing'
        ]));

        $this->logInfo('Acquired batch lock', [
            'lock_file' => $this->lockFilePath,
            'process_id' => getmypid(),
            'correlation_id' => uniqid('lock-acquired-', true)
        ]);

        return true;
    }

    /**
     * Release file lock for batch operations
     */
    private function releaseBatchLock(): void
    {
        if ($this->lockHandle === null) {
            return;
        }

        flock($this->lockHandle, LOCK_UN);
        fclose($this->lockHandle);
        $this->lockHandle = null;

        // Clean up lock file
        if (file_exists($this->lockFilePath)) {
            unlink($this->lockFilePath);
        }

        $this->logInfo('Released batch lock', [
            'lock_file' => $this->lockFilePath,
            'process_id' => getmypid(),
            'correlation_id' => uniqid('lock-released-', true)
        ]);
    }

    /**
     * Process batch of logs for transport
     */
    public function processBatch(): void
    {
        // Try to acquire batch lock
        if (!$this->acquireBatchLock()) {
            $this->logInfo('Skipping batch processing - another process is already processing', [
                'process_id' => getmypid(),
                'correlation_id' => uniqid('batch-skip-locked-', true)
            ]);
            return;
        }

        try {
            // Ensure database connection is fresh
            $this->ensureConnectionFresh();

            $batchSize = $this->config ? $this->config->get('buffer.batch_size', 100) : 100;

            // Memory safeguard: Reduce batch size if memory usage is high
            $memoryLimit = ini_get('memory_limit');
            if ($memoryLimit) {
                $memoryLimitBytes = $this->parseMemoryLimit($memoryLimit);
                $currentMemoryUsage = memory_get_usage(true);
                $memoryUsagePercent = ($currentMemoryUsage / $memoryLimitBytes) * 100;

                if ($memoryUsagePercent > 75) {
                    $batchSize = max(10, (int)($batchSize * 0.5)); // Reduce to 50% or minimum 10
                    $this->logInfo('Memory usage high, reducing batch size', [
                        'original_batch_size' => $batchSize * 2,
                        'reduced_batch_size' => $batchSize,
                        'memory_usage_percent' => round($memoryUsagePercent, 2),
                        'correlation_id' => uniqid('memory-safeguard-', true)
                    ]);
                }
            }

            $this->logInfo('Starting batch processing', [
                'batch_size_limit' => $batchSize,
                'timestamp' => time(),
                'process_id' => getmypid(),
                'correlation_id' => uniqid('batch-start-', true)
            ]);

            $maxRetries = 3;
            $retryDelay = 500000; // 0.5 seconds

            for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
                try {
                    // Use transaction for atomic batch processing
                    $this->db->beginTransaction();

                    $entries = $this->getBatchFromDatabase($batchSize);

                    if (empty($entries)) {
                        $this->db->rollBack();
                        $this->logInfo('No entries to process in batch', [
                            'correlation_id' => uniqid('batch-empty-', true)
                        ]);
                        return;
                    }

                    $this->logInfo('Processing batch', [
                        'entry_count' => count($entries),
                        'oldest_entry_age_seconds' => time() - ($entries[0]['created_at'] ?? time()),
                        'attempt' => $attempt,
                        'correlation_id' => uniqid('batch-process-', true)
                    ]);

                    // Group by labels for efficient transport
                    $groupedEntries = $this->groupEntriesByLabels($entries);

                    // Send each group to transport
                    foreach ($groupedEntries as $labelHash => $group) {
                        do_action('cxm_transport_send', $group['labels'], $group['entries']);
                    }

                    // Mark as sent
                    $this->markBatchAsSent(array_column($entries, 'id'));

                    // Commit transaction
                    $this->db->commit();

                    $this->logInfo('Batch processed successfully', [
                        'entries_sent' => count($entries),
                        'groups_sent' => count($groupedEntries),
                        'attempt' => $attempt,
                        'correlation_id' => uniqid('batch-complete-', true)
                    ]);

                    return; // Success

                } catch (\Exception $e) {
                    // Rollback on any error
                    if ($this->db->inTransaction()) {
                        $this->db->rollBack();
                    }

                    $errorCode = $this->db->errorCode();
                    $isLockingError = ($errorCode === 'HY000' && strpos($e->getMessage(), 'database is locked') !== false);

                    $this->logError('Batch processing failed', [
                        'attempt' => $attempt,
                        'max_retries' => $maxRetries,
                        'error_message' => $e->getMessage(),
                        'error_code' => $errorCode,
                        'is_locking_error' => $isLockingError,
                        'correlation_id' => uniqid('batch-error-', true)
                    ]);

                    if ($isLockingError && $attempt < $maxRetries) {
                        usleep($retryDelay * $attempt); // Exponential backoff
                        continue;
                    }

                    // For non-locking errors or exhausted retries, break
                    break;
                }
            }
        } finally {
            // Always release the lock
            $this->releaseBatchLock();
        }
    }

    /**
     * Flush all critical logs immediately
     */
    public function flushCriticalLogs(): void
    {
        $criticalEntries = $this->getCriticalEntries();

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

        $groupedEntries = $this->groupEntriesByLabels($criticalEntries);

        foreach ($groupedEntries as $group) {
            do_action('cxm_transport_send', $group['labels'], $group['entries']);
        }

        $this->markBatchAsSent(array_column($criticalEntries, 'id'));
    }

    /**
     * Flush entire buffer
     */
    public function flushBuffer(): bool
    {
        try {
            $allEntries = $this->getAllEntries();

            if (empty($allEntries)) {
                return true;
            }

            $totalEntries = count($allEntries);
            $this->logInfo('Starting full buffer flush', [
                'total_entries' => $totalEntries,
                'correlation_id' => uniqid('flush-start-', true)
            ]);

            // Process in chunks to avoid memory issues with large buffers
            $chunkSize = 1000; // Process 1000 entries at a time
            $processedCount = 0;

            for ($i = 0; $i < $totalEntries; $i += $chunkSize) {
                $chunk = array_slice($allEntries, $i, $chunkSize);
                $groupedEntries = $this->groupEntriesByLabels($chunk);

                foreach ($groupedEntries as $group) {
                    do_action('cxm_transport_send', $group['labels'], $group['entries']);
                }

                $processedCount += count($chunk);

                $this->logInfo('Buffer flush progress', [
                    'processed_entries' => $processedCount,
                    'total_entries' => $totalEntries,
                    'progress_percent' => round(($processedCount / $totalEntries) * 100, 2),
                    'correlation_id' => uniqid('flush-progress-', true)
                ]);

                // Yield control briefly to prevent blocking
                if ($processedCount < $totalEntries) {
                    usleep(10000); // 10ms pause between chunks
                }
            }

            $this->clearBuffer();

            $this->logInfo('Buffer flush completed successfully', [
                'total_entries_processed' => $totalEntries,
                'correlation_id' => uniqid('flush-complete-', true)
            ]);

            return true;

        } catch (\Exception $e) {
            $this->logError('Buffer flush failed: ' . $e->getMessage(), [
                'error_details' => $e->getTraceAsString(),
                'correlation_id' => uniqid('flush-error-', true)
            ]);
            return false;
        }
    }

    /**
     * Get recent logs for admin display (memory buffer + recent database entries)
     */
    public function getRecentLogs(): array
    {
        $recent = [];

        // Get from memory buffer (most recent)
        $logs = array_slice(array_reverse($this->memoryBuffer), 0, 25);

        foreach ($logs as $logEntry) {
            $recent[] = [
                'timestamp' => date('Y-m-d H:i:s', (int)$logEntry['created_at']),
                'level' => strtoupper($logEntry['level']),
                'component' => $logEntry['labels']['client'] ?? 'unknown',
                'message' => $logEntry['context']['message'] ?? '',
                'context' => $logEntry['context'],
                'source' => 'memory',
            ];
        }

        // Also get recent database entries (last 25 unsent)
        try {
            $this->ensureConnectionFresh();
            $stmt = $this->db->prepare("
                SELECT labels, data, level, created_at
                FROM log_entries
                WHERE sent = 0
                ORDER BY created_at DESC
                LIMIT 25
            ");
            $stmt->execute();
            $dbLogs = $stmt->fetchAll();

            foreach ($dbLogs as $entry) {
                $labels = json_decode($entry['labels'], true);
                $context = json_decode($entry['data'], true);

                $recent[] = [
                    'timestamp' => date('Y-m-d H:i:s', (int)$entry['created_at']),
                    'level' => strtoupper($entry['level']),
                    'component' => $labels['client'] ?? 'unknown',
                    'message' => $context['message'] ?? '',
                    'context' => $context,
                    'source' => 'database',
                ];
            }
        } catch (\Exception $e) {
            // Skip database logs if error
        }

        // Sort by timestamp descending (most recent first)
        usort($recent, function ($a, $b) {
            return strtotime($b['timestamp']) - strtotime($a['timestamp']);
        });

        return array_slice($recent, 0, 50);
    }

    /**
     * Get buffer statistics
     */
    public function getStats(): array
    {
        // Ensure database is initialized
        if ($this->db === null) {
            try {
                $this->ensureDatabaseExists();
                $this->setupTables();
            } catch (\Exception $e) {
                return ['entries' => 0, 'size_bytes' => 0, 'size_mb' => 0, 'memory_entries' => count($this->memoryBuffer)];
            }
        }

        try {
            $stmt = $this->db->query("SELECT COUNT(*) as total, SUM(LENGTH(data)) as size_bytes FROM log_entries WHERE sent = 0");
            $stats = $stmt->fetch(\PDO::FETCH_ASSOC);

            return [
                'entries' => (int) ($stats['total'] ?? 0),
                'size_bytes' => (int) ($stats['size_bytes'] ?? 0),
                'size_mb' => round(($stats['size_bytes'] ?? 0) / 1024 / 1024, 2),
                'memory_entries' => count($this->memoryBuffer),
            ];
        } catch (\Exception $e) {
            return ['entries' => 0, 'size_bytes' => 0, 'size_mb' => 0, 'memory_entries' => count($this->memoryBuffer)];
        }
    }

    /**
     * Ensure SQLite database exists
     */
    private function ensureDatabaseExists(): void
    {
        // Ensure directory exists
        $dir = dirname($this->dbPath);
        if (!is_dir($dir)) {
            wp_mkdir_p($dir);
        }

        // Create PDO connection
        $this->db = new \PDO('sqlite:' . $this->dbPath);
        $this->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
        $this->db->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);

        // Enable WAL mode for better performance
        $this->db->exec('PRAGMA journal_mode=WAL');
        $this->db->exec('PRAGMA synchronous=NORMAL');
        $this->db->exec('PRAGMA cache_size=1000');
    }

    /**
     * Set up database tables
     */
    private function setupTables(): void
    {
        $this->db->exec("
            CREATE TABLE IF NOT EXISTS log_entries (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                labels TEXT NOT NULL,
                data TEXT NOT NULL,
                level TEXT NOT NULL,
                created_at REAL NOT NULL,
                sent INTEGER DEFAULT 0,
                retry_count INTEGER DEFAULT 0,
                last_retry REAL
            )
        ");

        // Create indexes for performance
        $this->db->exec("CREATE INDEX IF NOT EXISTS idx_sent_created ON log_entries(sent, created_at)");
        $this->db->exec("CREATE INDEX IF NOT EXISTS idx_level ON log_entries(level)");
        $this->db->exec("CREATE INDEX IF NOT EXISTS idx_labels ON log_entries(labels)");
    }

    /**
     * Persist log entry to database
     */
    private function persistToDatabase(array $logEntry): void
    {
        $maxRetries = 3;
        $retryDelay = 100000; // 0.1 seconds in microseconds

        for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
            try {
                $stmt = $this->db->prepare("
                    INSERT INTO log_entries (labels, data, level, created_at)
                    VALUES (?, ?, ?, ?)
                ");

                $labelsJson = json_encode($logEntry['labels']);
                $dataJson = json_encode($logEntry['context']);
                $level = $logEntry['level'];
                $createdAt = microtime(true);

                $stmt->execute([$labelsJson, $dataJson, $level, $createdAt]);
                return; // Success, exit retry loop

            } catch (\Exception $e) {
                $errorCode = $this->db->errorCode();
                $errorInfo = $this->db->errorInfo();

                // Check if it's a locking error
                if ($errorCode === 'HY000' && strpos($e->getMessage(), 'database is locked') !== false) {
                    $this->logError('Database locking detected during INSERT', [
                        'attempt' => $attempt,
                        'max_retries' => $maxRetries,
                        'error_code' => $errorCode,
                        'error_info' => $errorInfo,
                        'process_id' => getmypid(),
                        'correlation_id' => uniqid('db-lock-insert-', true)
                    ]);

                    if ($attempt < $maxRetries) {
                        usleep($retryDelay * $attempt); // Exponential backoff
                        continue;
                    }
                }

                // For non-locking errors or exhausted retries, log and break
                $this->logError('Failed to persist log entry: ' . $e->getMessage(), [
                    'attempt' => $attempt,
                    'error_code' => $errorCode,
                    'error_info' => $errorInfo,
                    'correlation_id' => uniqid('db-error-insert-', true)
                ]);
                break;
            }
        }
    }

    /**
     * Get batch of unsent entries
     */
    private function getBatchFromDatabase(int $limit): array
    {
        try {
            $stmt = $this->db->prepare("
                SELECT id, labels, data, level, created_at
                FROM log_entries
                WHERE sent = 0
                ORDER BY created_at ASC
                LIMIT ?
            ");
            $stmt->execute([$limit]);

            return $stmt->fetchAll();
        } catch (\Exception $e) {
            $errorCode = $this->db->errorCode();
            if ($errorCode === 'HY000' && strpos($e->getMessage(), 'database is locked') !== false) {
                $this->logError('Database locking detected during batch retrieval', [
                    'batch_limit' => $limit,
                    'error_code' => $errorCode,
                    'process_id' => getmypid(),
                    'correlation_id' => uniqid('db-lock-batch-get-', true)
                ]);
            } else {
                $this->logError('Failed to retrieve batch from database: ' . $e->getMessage(), [
                    'batch_limit' => $limit,
                    'error_code' => $errorCode,
                    'correlation_id' => uniqid('db-error-batch-get-', true)
                ]);
            }
            return [];
        }
    }

    /**
     * Get critical entries that should be sent immediately
     */
    private function getCriticalEntries(): array
    {
        $stmt = $this->db->prepare("
            SELECT id, labels, data, level, created_at
            FROM log_entries
            WHERE sent = 0 AND level IN ('critical', 'error', 'emergency', 'alert')
            ORDER BY created_at ASC
            LIMIT 50
        ");
        $stmt->execute();

        return $stmt->fetchAll();
    }

    /**
     * Get all unsent entries
     */
    private function getAllEntries(): array
    {
        $stmt = $this->db->query("
            SELECT id, labels, data, level, created_at
            FROM log_entries
            WHERE sent = 0
            ORDER BY created_at ASC
        ");

        return $stmt->fetchAll();
    }

    /**
     * Group entries by labels for efficient transport
     */
    private function groupEntriesByLabels(array $entries): array
    {
        $groups = [];

        foreach ($entries as $entry) {
            $labels = json_decode($entry['labels'], true);
            $labelHash = md5(serialize($labels));

            if (!isset($groups[$labelHash])) {
                $groups[$labelHash] = [
                    'labels' => $labels,
                    'entries' => []
                ];
            }

            $groups[$labelHash]['entries'][] = [
                'timestamp' => $this->formatTimestampForLoki(microtime(true)), // Use current time for Loki timestamp
                'line' => $this->formatLogLine($entry)
            ];
        }

        return $groups;
    }

    /**
     * Mark batch as sent
     */
    private function markBatchAsSent(array $ids): void
    {
        if (empty($ids)) {
            return;
        }

        try {
            $placeholders = str_repeat('?,', count($ids) - 1) . '?';
            $stmt = $this->db->prepare("UPDATE log_entries SET sent = 1 WHERE id IN ($placeholders)");
            $stmt->execute($ids);
        } catch (\Exception $e) {
            $errorCode = $this->db->errorCode();
            if ($errorCode === 'HY000' && strpos($e->getMessage(), 'database is locked') !== false) {
                $this->logError('Database locking detected during batch mark-as-sent', [
                    'entries_count' => count($ids),
                    'error_code' => $errorCode,
                    'process_id' => getmypid(),
                    'correlation_id' => uniqid('db-lock-batch-update-', true)
                ]);
            } else {
                $this->logError('Failed to mark batch as sent: ' . $e->getMessage(), [
                    'entries_count' => count($ids),
                    'error_code' => $errorCode,
                    'correlation_id' => uniqid('db-error-batch-update-', true)
                ]);
            }
        }
    }

    /**
     * Clear entire buffer (sent entries only)
     */
    private function clearBuffer(): void
    {
        $this->db->exec("DELETE FROM log_entries WHERE sent = 1");
        $this->db->exec("VACUUM");
        $this->memoryBuffer = [];
    }

    /**
     * Purge all buffered log data (both sent and unsent)
     */
    public function purgeAll(): bool
    {
        try {
            // Get stats before purge for logging
            $statsBefore = $this->getStats();
            $totalEntriesBefore = ($statsBefore['entries'] ?? 0) + ($statsBefore['memory_entries'] ?? 0);

            $this->logInfo('Starting buffer purge', [
                'entries_before' => $statsBefore['entries'] ?? 0,
                'memory_entries_before' => $statsBefore['memory_entries'] ?? 0,
                'total_entries_before' => $totalEntriesBefore,
                'correlation_id' => uniqid('purge-start-', true)
            ]);

            // Ensure database connection is fresh
            $this->ensureConnectionFresh();

            // Truncate the log_entries table
            $this->db->exec("DELETE FROM log_entries");

            // Reset any auto-increment counters
            $this->db->exec("DELETE FROM sqlite_sequence WHERE name='log_entries'");

            // Vacuum to reclaim space and reset database
            $this->db->exec("VACUUM");

            // Clear memory buffer
            $this->memoryBuffer = [];

            // Reset retry counters and other state if needed
            // (The table is completely empty now, so no need to reset individual counters)

            $this->logInfo('Buffer purge completed successfully', [
                'entries_purged' => $statsBefore['entries'] ?? 0,
                'memory_entries_cleared' => $statsBefore['memory_entries'] ?? 0,
                'total_entries_purged' => $totalEntriesBefore,
                'correlation_id' => uniqid('purge-complete-', true)
            ]);

            return true;

        } catch (\Exception $e) {
            $this->logError('Buffer purge failed: ' . $e->getMessage(), [
                'error_details' => $e->getTraceAsString(),
                'correlation_id' => uniqid('purge-error-', true)
            ]);
            return false;
        }
    }

    /**
     * Clean up old entries
     */
    public function cleanupOldEntries(): void
    {
        $maxAgeDays = $this->config ? $this->config->get('buffer.max_age_days', 7) : 7;
        $maxAge = time() - ($maxAgeDays * 24 * 60 * 60);

        // Delete old sent entries
        $stmt = $this->db->prepare("DELETE FROM log_entries WHERE sent = 1 AND created_at < ?");
        $stmt->execute([$maxAge]);

        // Delete old unsent entries (failed retries)
        $stmt = $this->db->prepare("DELETE FROM log_entries WHERE sent = 0 AND retry_count > 10 AND created_at < ?");
        $stmt->execute([$maxAge]);

        // Vacuum to reclaim space
        $this->db->exec("VACUUM");

        $this->logInfo("Buffer cleanup completed, removed entries older than {$maxAgeDays} days");
    }

    /**
     * Format timestamp for Loki (nanoseconds)
     */
    private function formatTimestampForLoki(float $timestamp): string
    {
        return (string) ((int) ($timestamp * 1000000000));
    }

    /**
     * Format log line for Loki
     */
    private function formatLogLine(array $entry): string
    {
        $data = json_decode($entry['data'], true);
        $logLine = [
            'level' => $entry['level'],
            'message' => $data['message'] ?? '',
        ];

        // Add context without message
        unset($data['message']);
        $logLine = array_merge($logLine, $data);

        return json_encode($logLine);
    }

    /**
     * Log error message
     */
    private function logError(string $message): void
    {
        if (class_exists('\\CloudXM\\SDK\\Utilities\\Logger')) {
            \CloudXM\SDK\Logger::error($message, [
                'component' => 'buffer_manager',
                'correlation_id' => uniqid('buffer-error-', true)
            ]);
        }
    }

    /**
     * Parse PHP memory limit string to bytes
     */
    private function parseMemoryLimit(string $memoryLimit): int
    {
        $unit = strtolower(substr($memoryLimit, -1));
        $value = (int) substr($memoryLimit, 0, -1);

        switch ($unit) {
            case 'g':
                return $value * 1024 * 1024 * 1024;
            case 'm':
                return $value * 1024 * 1024;
            case 'k':
                return $value * 1024;
            default:
                return (int) $memoryLimit; // Assume bytes if no unit
        }
    }

    /**
     * Log info message
     */
    private function logInfo(string $message): void
    {
        if (class_exists('\\CloudXM\\SDK\\Utilities\\Logger')) {
            \CloudXM\SDK\Logger::info($message, [
                'component' => 'buffer_manager',
                'correlation_id' => uniqid('buffer-info-', true)
            ]);
        }
    }

    /**
     * Ensure database connection is fresh
     */
    private function ensureConnectionFresh(): void
    {
        if ($this->db) {
            try {
                // Test connection with a simple query
                $this->db->query('SELECT 1');
            } catch (\Exception $e) {
                $this->logInfo('Database connection stale, refreshing', [
                    'error' => $e->getMessage(),
                    'correlation_id' => uniqid('conn-refresh-', true)
                ]);
                $this->db = null;
                $this->ensureDatabaseExists();
            }
        }
    }

    /**
     * Destructor to ensure locks are released
     */
    public function __destruct()
    {
        $this->releaseBatchLock();
        if ($this->db) {
            $this->db = null;
        }
    }

    /**
     * Prevent cloning
     */
    private function __clone()
    {
    }
}