<?php

class CommandLocker {
    private $pdo;
    private $lockTimeout = 300; // 5 minutos de tiempo de espera para el bloqueo
    private $debug = true;
    private $dbConnection;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
        // Configurar PDO para que lance excepciones
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
        
        // Inicializar la conexión a la base de datos
        $this->getConnection();
    }
    
    /**
     * Obtiene una conexión activa a la base de datos
     * 
     * @return PDO La conexión a la base de datos
     */
    private function getConnection() {
        try {
            // Verificar si la conexión actual es válida
            if ($this->pdo === null || !$this->isConnectionAlive()) {
                $this->log("Estableciendo nueva conexión a la base de datos");
                $this->pdo = $this->createNewConnection();
            }
            return $this->pdo;
        } catch (Exception $e) {
            $this->log("Error al obtener conexión a la base de datos", [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            throw $e;
        }
    }
    
    /**
     * Verifica si la conexión actual está activa
     * 
     * @return bool True si la conexión está activa, false en caso contrario
     */
    private function isConnectionAlive() {
        try {
            $this->pdo->query('SELECT 1');
            return true;
        } catch (Exception $e) {
            $this->log("La conexión a la base de datos no está activa", [
                'error' => $e->getMessage()
            ]);
            return false;
        }
    }
    
    /**
     * Crea una nueva conexión a la base de datos
     * 
     * @return PDO La nueva conexión
     */
    private function createNewConnection() {
        // Obtener la configuración de la base de datos del archivo de configuración
        require_once __DIR__ . '/../includes/db.php';
        
        // Obtener la instancia de la conexión del archivo db.php
        global $pdo;
        
        if (!($pdo instanceof PDO)) {
            throw new Exception("No se pudo establecer una conexión a la base de datos");
        }
        
        return $pdo;
    }
    
    private function log($message, $data = []) {
        if ($this->debug) {
            $logMessage = date('[Y-m-d H:i:s] ') . $message;
            if (!empty($data)) {
                $logMessage .= ' ' . json_encode($data);
            }
            error_log($logMessage);
        }
    }

    /**
     * Intenta adquirir un bloqueo para un comando
     * 
     * @param string $command El comando a bloquear
     * @param int $chatId El ID del chat donde se ejecutó el comando
     * @param string $type Tipo de bloqueo ('command' o 'translation')
     * @param array $data Datos adicionales a almacenar con el bloqueo
     * @return array|false Retorna el ID del bloqueo si se adquiere, false si ya existe un bloqueo activo
     */
    public function acquireLock($command, $chatId, $type = 'command', $data = []) {
        $this->log("Intentando adquirir bloqueo", [
            'command' => $command,
            'chatId' => $chatId,
            'type' => $type,
            'data' => $data
        ]);
        
        $this->cleanupExpiredLocks();
        
        try {
            $this->pdo->beginTransaction();
            
            // Buscar cualquier bloqueo existente para este chat y comando (sin filtrar por estado)
            $query = "
                SELECT id, status, expires_at, data, created_at 
                FROM command_locks 
                WHERE command = ? 
                AND chat_id = ? 
                ORDER BY created_at DESC 
                LIMIT 1 FOR UPDATE
            ";
            
            $stmt = $this->pdo->prepare($query);
            
            $this->log("Ejecutando consulta de bloqueo existente", [
                'query' => $stmt->queryString,
                'params' => [$command, $chatId, $type]
            ]);
            
            $stmt->execute([$command, $chatId]);
            $existingLock = $stmt->fetch(PDO::FETCH_ASSOC);
            
            if ($existingLock) {
                $this->log("Bloqueo existente encontrado", $existingLock);
                
                // Si hay un bloqueo activo y reciente, bloquear nueva ejecución
                if ($existingLock['status'] === 'processing' && strtotime($existingLock['created_at']) > strtotime('-5 minutes')) {
                    $this->log("Bloqueo ya en proceso, rechazando nueva solicitud");
                    $this->pdo->rollBack();
                    return false; // Ya hay un bloqueo activo
                }
                
                // Reutilizar el mismo registro para evitar violar la única (chat_id,command)
                $expiresAt = (new DateTime('+5 minutes'))->format('Y-m-d H:i:s');
                $dataJson = !empty($data) ? json_encode($data) : null;
                $upd = $this->pdo->prepare("UPDATE command_locks SET type = ?, status='processing', data = COALESCE(?, data), message_id = NULL, expires_at = ?, updated_at = NOW() WHERE id = ?");
                $upd->execute([$type, $dataJson, $expiresAt, $existingLock['id']]);
                $this->pdo->commit();
                $this->log("Bloqueo reutilizado y pasado a processing", ['lockId' => $existingLock['id']]);
                return (int)$existingLock['id'];
            } else {
                $this->log("No se encontraron bloqueos existentes");
            }
            
            // Insertar un nuevo bloqueo
            $expiresAt = (new DateTime('+5 minutes'))->format('Y-m-d H:i:s');
            $dataJson = !empty($data) ? json_encode($data) : null;
            
            $query = "
                INSERT INTO command_locks 
                (chat_id, command, type, data, status, expires_at, created_at, updated_at) 
                VALUES (?, ?, ?, ?, 'processing', ?, NOW(), NOW())
            ";
            
            $stmt = $this->pdo->prepare($query);
            
            $this->log("Insertando nuevo bloqueo", [
                'chat_id' => $chatId,
                'command' => $command,
                'type' => $type,
                'has_data' => !empty($data),
                'expires_at' => $expiresAt
            ]);
            
            $stmt->execute([$chatId, $command, $type, $dataJson, $expiresAt]);
            
            $lockId = $this->pdo->lastInsertId();
            $this->pdo->commit();
            
            $this->log("Bloqueo adquirido exitosamente", ['lockId' => $lockId]);
            
            return (int)$lockId;
            
        } catch (Exception $e) {
            $this->log("Error al adquirir bloqueo", [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            
            try {
                if ($this->pdo->inTransaction()) {
                    $this->pdo->rollBack();
                }
            } catch (Exception $rollbackException) {
                $this->log("Error al hacer rollback", [
                    'error' => $rollbackException->getMessage()
                ]);
            }
            
            return false;
        }
    }
    
    /**
     * Actualiza el estado de un bloqueo
     * 
     * @param int $lockId ID del bloqueo a actualizar
     * @param string $status Nuevo estado ('processing', 'completed', 'failed')
     * @param string|null $messageId ID del mensaje asociado (opcional)
     * @return bool True si se actualizó correctamente, false en caso contrario
     */
    public function updateLockStatus($lockId, $status, $messageId = null) {
        $this->log("Actualizando estado de bloqueo", [
            'lockId' => $lockId,
            'status' => $status,
            'messageId' => $messageId
        ]);
        
        try {
            // Asegurarse de que tenemos una conexión válida
            $pdo = $this->getConnection();
            
            $query = "
                UPDATE command_locks 
                SET status = ?, 
                    message_id = COALESCE(?, message_id),
                    expires_at = CASE WHEN ? = 'completed' THEN DATE_ADD(NOW(), INTERVAL 1 MINUTE) ELSE expires_at END,
                    updated_at = NOW()
                WHERE id = ?
            ";
            
            $stmt = $pdo->prepare($query);
            $result = $stmt->execute([$status, $messageId, $status, $lockId]);
            $rowCount = $stmt->rowCount();
            
            $this->log("Resultado de actualización de estado", [
                'lockId' => $lockId,
                'status' => $status,
                'rowCount' => $rowCount,
                'result' => $result
            ]);
            
            // Si no se actualizó ninguna fila, verificar si el bloqueo existe
            if ($rowCount === 0) {
                $checkStmt = $pdo->prepare("SELECT id FROM command_locks WHERE id = ?");
                $checkStmt->execute([$lockId]);
                $exists = $checkStmt->fetch(PDO::FETCH_ASSOC);
                
                $this->log("Verificación de existencia de bloqueo", [
                    'lockId' => $lockId,
                    'exists' => (bool)$exists
                ]);
                
                if (!$exists) {
                    $this->log("Error: El bloqueo no existe", ['lockId' => $lockId]);
                    return false;
                }
            }
            
            return $result && $rowCount > 0;
            
        } catch (Exception $e) {
            $this->log("Error al actualizar estado de bloqueo", [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            return false;
        }
    }
    
    /**
     * Libera un bloqueo marcándolo como completado
     * 
     * @param int $lockId ID del bloqueo a liberar
     * @param string|null $messageId ID del mensaje asociado (opcional)
     * @return bool True si se actualizó correctamente, false en caso contrario
     */
    public function releaseLock($lockId, $messageId = null) {
        $this->log("Liberando bloqueo", [
            'lockId' => $lockId,
            'messageId' => $messageId
        ]);
        
        $result = $this->updateLockStatus($lockId, 'completed', $messageId);
        
        $this->log("Resultado de liberación de bloqueo", [
            'lockId' => $lockId,
            'success' => $result
        ]);
        
        return $result;
    }
    
    /**
     * Marca un bloqueo como fallido
     * 
     * @param int $lockId ID del bloqueo a marcar como fallido
     * @param string $errorMessage Mensaje de error (opcional)
     * @return bool True si se actualizó correctamente, false en caso contrario
     */
    public function failLock($lockId, $errorMessage = '') {
        $this->log("Marcando bloqueo como fallido", [
            'lockId' => $lockId,
            'errorMessage' => $errorMessage
        ]);
        
        try {
            // Asegurarse de que tenemos una conexión válida
            $pdo = $this->getConnection();
            
            $query = "
                UPDATE command_locks 
                SET status = 'failed', 
                    data = JSON_MERGE_PATCH(COALESCE(data, '{}'), ?),
                    expires_at = DATE_ADD(NOW(), INTERVAL 5 MINUTE),
                    updated_at = NOW()
                WHERE id = ?
            ";
            
            $stmt = $pdo->prepare($query);
            $result = $stmt->execute([json_encode(['error' => $errorMessage, 'failed_at' => date('Y-m-d H:i:s')]), $lockId]);
            $rowCount = $stmt->rowCount();
            
            $this->log("Resultado de marcar bloqueo como fallido", [
                'lockId' => $lockId,
                'rowCount' => $rowCount,
                'result' => $result
            ]);
            
            return $result && $rowCount > 0;
            
        } catch (Exception $e) {
            $this->log("Error al marcar bloqueo como fallido", [
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString()
            ]);
            return false;
        }
    }
    
    /**
     * Limpia los bloqueos expirados
     */
    private function cleanupExpiredLocks() {
        try {
            $this->pdo->exec("
                DELETE FROM command_locks 
                WHERE (status = 'completed' AND expires_at <= NOW()) 
                OR (status = 'processing' AND created_at < DATE_SUB(NOW(), INTERVAL 1 HOUR))
            ");
        } catch (Exception $e) {
            error_log("Error al limpiar bloqueos expirados: " . $e->getMessage());
        }
    }
    
    /**
     * Verifica si un comando está siendo procesado actualmente
     * 
     * @param string $command Comando a verificar
     * @param int $chatId ID del chat
     * @param string $type Tipo de bloqueo ('command' o 'translation')
     * @return bool True si el comando está siendo procesado, false en caso contrario
     */
    public function isCommandProcessing($command, $chatId, $type = 'command') {
        $stmt = $this->pdo->prepare("
            SELECT COUNT(*) 
            FROM command_locks 
            WHERE command = ? 
            AND chat_id = ? 
            AND type = ?
            AND status = 'processing'
            AND created_at > DATE_SUB(NOW(), INTERVAL 5 MINUTE)
        ");
        $stmt->execute([$command, $chatId, $type]);
        
        return (int)$stmt->fetchColumn() > 0;
    }
}
