<?php
/**
 * HuSNS - 备份服务类
 * 
 * @author  HYR
 * @QQ      281900864
 * @website https://huyourui.com
 * @license MIT
 */
class BackupService
{
    private $db;
    private $backupDir;
    
    public function __construct()
    {
        require_once ROOT_PATH . 'core/Database.php';
        $this->db = Database::getInstance();
        $this->backupDir = ROOT_PATH . 'backups' . DIRECTORY_SEPARATOR;
        
        if (!file_exists($this->backupDir)) {
            mkdir($this->backupDir, 0755, true);
        }
    }
    
    /**
     * 备份数据库
     * @param string $fileName 文件名（可选）
     * @return bool|string
     */
    public function backupDatabase($fileName = '')
    {
        if (empty($fileName)) {
            $fileName = 'backup_' . date('Ymd_His') . '.sql';
        }
        
        $backupPath = $this->backupDir . $fileName;
        
        // 获取所有表
        $tables = $this->getTables();
        
        $content = "-- HuSNS Database Backup\n";
        $content .= "-- Generated: " . date('Y-m-d H:i:s') . "\n";
        $content .= "-- Database: " . DB_NAME . "\n\n";
        
        foreach ($tables as $table) {
            // 获取表结构
            $createSql = $this->getTableCreate($table);
            $content .= $createSql . ";\n\n";
            
            // 获取表数据
            $data = $this->getTableData($table);
            if (!empty($data)) {
                $content .= $data . "\n";
            }
        }
        
        $result = file_put_contents($backupPath, $content);
        
        if ($result !== false) {
            return $backupPath;
        }
        
        return false;
    }
    
    /**
     * 获取所有表名
     * @return array
     */
    private function getTables()
    {
        $result = $this->db->fetchAll("SHOW TABLES");
        $tables = [];
        
        foreach ($result as $row) {
            foreach ($row as $value) {
                $tables[] = $value;
            }
        }
        
        return $tables;
    }
    
    /**
     * 获取表创建语句
     * @param string $table 表名
     * @return string
     */
    private function getTableCreate($table)
    {
        $result = $this->db->fetch("SHOW CREATE TABLE `{$table}`");
        
        if ($result) {
            foreach ($result as $key => $value) {
                if (strtoupper($key) == 'CREATE TABLE') {
                    return $value;
                }
            }
        }
        
        return '';
    }
    
    /**
     * 获取表数据
     * @param string $table 表名
     * @return string
     */
    private function getTableData($table)
    {
        $rows = $this->db->fetchAll("SELECT * FROM `{$table}`");
        
        if (empty($rows)) {
            return '';
        }
        
        $columns = array_keys($rows[0]);
        $columnList = '`' . implode('`, `', $columns) . '`';
        
        $values = [];
        
        foreach ($rows as $row) {
            $rowValues = [];
            
            foreach ($columns as $column) {
                $value = $row[$column];
                
                if ($value === null) {
                    $rowValues[] = 'NULL';
                } elseif (is_numeric($value)) {
                    $rowValues[] = $value;
                } else {
                    // 转义特殊字符
                    $value = str_replace("\\", "\\\\", $value);
                    $value = str_replace("'", "''", $value);
                    $value = str_replace("\n", "\\n", $value);
                    $value = str_replace("\r", "\\r", $value);
                    $value = str_replace("\t", "\\t", $value);
                    $rowValues[] = "'{$value}'";
                }
            }
            
            $values[] = '(' . implode(', ', $rowValues) . ')';
        }
        
        $sql = "INSERT INTO `{$table}` ({$columnList}) VALUES\n";
        $sql .= implode(",\n", $values) . ";";
        
        return $sql;
    }
    
    /**
     * 恢复数据库
     * @param string $backupPath 备份文件路径
     * @param bool $dropExisting 是否删除现有表
     * @return bool
     */
    public function restoreDatabase($backupPath, $dropExisting = false)
    {
        if (!file_exists($backupPath)) {
            return false;
        }
        
        $content = file_get_contents($backupPath);
        
        // 按分号分割SQL语句（处理多行语句）
        $statements = $this->splitSql($content);
        
        foreach ($statements as $statement) {
            $statement = trim($statement);
            
            if (empty($statement) || preg_match('/^--/', $statement)) {
                continue;
            }
            
            try {
                $this->db->query($statement);
            } catch (Exception $e) {
                error_log('Restore error: ' . $e->getMessage());
                return false;
            }
        }
        
        return true;
    }
    
    /**
     * 分割SQL语句
     * @param string $sql SQL内容
     * @return array
     */
    private function splitSql($sql)
    {
        $statements = [];
        $current = '';
        $inString = false;
        $stringChar = '';
        $inComment = false;
        
        for ($i = 0; $i < strlen($sql); $i++) {
            $char = $sql[$i];
            $nextChar = isset($sql[$i + 1]) ? $sql[$i + 1] : '';
            
            // 处理注释
            if (!$inString && $char == '-' && $nextChar == '-') {
                $inComment = true;
            }
            
            if ($inComment) {
                if ($char == "\n") {
                    $inComment = false;
                }
                continue;
            }
            
            // 处理字符串
            if ($char == "'" || $char == '"') {
                if (!$inString) {
                    $inString = true;
                    $stringChar = $char;
                } elseif ($stringChar == $char) {
                    // 检查是否是转义的引号
                    if ($i > 0 && $sql[$i - 1] != '\\') {
                        $inString = false;
                    }
                }
            }
            
            // 处理分号（语句结束）
            if (!$inString && $char == ';') {
                $statements[] = $current;
                $current = '';
                continue;
            }
            
            $current .= $char;
        }
        
        // 添加最后一条语句
        if (trim($current)) {
            $statements[] = $current;
        }
        
        return $statements;
    }
    
    /**
     * 获取备份文件列表
     * @return array
     */
    public function getBackupFiles()
    {
        $files = [];
        
        if ($handle = opendir($this->backupDir)) {
            while (($file = readdir($handle)) !== false) {
                if ($file != '.' && $file != '..' && pathinfo($file, PATHINFO_EXTENSION) == 'sql') {
                    $path = $this->backupDir . $file;
                    $files[] = [
                        'name' => $file,
                        'path' => $path,
                        'size' => filesize($path),
                        'size_text' => $this->formatSize(filesize($path)),
                        'created_at' => filemtime($path),
                        'created_at_text' => date('Y-m-d H:i:s', filemtime($path))
                    ];
                }
            }
            closedir($handle);
        }
        
        // 按创建时间降序排列
        usort($files, function($a, $b) {
            return $b['created_at'] - $a['created_at'];
        });
        
        return $files;
    }
    
    /**
     * 删除备份文件
     * @param string $fileName 文件名
     * @return bool
     */
    public function deleteBackup($fileName)
    {
        // 安全检查：防止路径遍历
        if (strpos($fileName, '..') !== false) {
            return false;
        }
        
        $path = $this->backupDir . $fileName;
        
        if (file_exists($path)) {
            return unlink($path);
        }
        
        return false;
    }
    
    /**
     * 格式化文件大小
     * @param int $bytes 字节数
     * @return string
     */
    private function formatSize($bytes)
    {
        if ($bytes < 1024) {
            return $bytes . ' B';
        } elseif ($bytes < 1024 * 1024) {
            return round($bytes / 1024, 2) . ' KB';
        } else {
            return round($bytes / (1024 * 1024), 2) . ' MB';
        }
    }
    
    /**
     * 自动备份（保留最近N个备份）
     * @param int $keepCount 保留备份数量
     * @return bool|string
     */
    public function autoBackup($keepCount = 7)
    {
        // 创建新备份
        $result = $this->backupDatabase();
        
        if (!$result) {
            return false;
        }
        
        // 删除旧备份
        $files = $this->getBackupFiles();
        
        if (count($files) > $keepCount) {
            $oldFiles = array_slice($files, $keepCount);
            
            foreach ($oldFiles as $file) {
                $this->deleteBackup($file['name']);
            }
        }
        
        return $result;
    }
    
    /**
     * 备份项目文件
     * @param string $type 备份类型: 'full'(完整) / 'code'(仅代码) / 'uploads'(仅上传)
     * @return bool|string
     */
    public function backupFiles($type = 'code')
    {
        $timestamp = date('Ymd_His');
        $fileName = '';
        
        switch ($type) {
            case 'full':
                $fileName = "fullbackup_{$timestamp}.zip";
                break;
            case 'uploads':
                $fileName = "uploads_{$timestamp}.zip";
                break;
            default:
                $fileName = "codebackup_{$timestamp}.zip";
        }
        
        $backupPath = $this->backupDir . $fileName;
        
        // 需要排除的目录和文件
        $excludePatterns = [
            'cache', 'temp', 'logs', 'backups',
            '.git', '.svn', 'node_modules', 'vendor',
            '*.log', '.htaccess', '*.bak', 'Thumbs.db',
            '.DS_Store', '__pycache__'
        ];
        
        try {
            if (!class_exists('ZipArchive')) {
                error_log('ZipArchive class not available');
                return false;
            }
            
            $zip = new ZipArchive();
            if ($zip->open($backupPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
                return false;
            }
            
            $rootPath = ROOT_PATH;
            
            if ($type === 'full' || $type === 'uploads') {
                // 备份上传文件
                $this->addDirectoryToZip($zip, $rootPath . 'uploads', 'uploads', $excludePatterns);
            }
            
            if ($type === 'full' || $type === 'code') {
                // 备份核心代码文件
                $codeDirs = [
                    'admin', 'api', 'config.php', 'config.secret.php',
                    'content', 'controller', 'core', 'languages',
                    'plugins', 'repository', 'service', 'static',
                    'templates', 'index.php', 'queue.php'
                ];
                
                foreach ($codeDirs as $item) {
                    $itemPath = $rootPath . $item;
                    if (is_dir($itemPath)) {
                        $this->addDirectoryToZip($zip, $itemPath, $item, $excludePatterns);
                    } elseif (is_file($itemPath)) {
                        $zip->addFile($itemPath, $item);
                    }
                }
            }
            
            // 添加数据库备份到完整备份
            if ($type === 'full') {
                $sqlFileName = "database_{$timestamp}.sql";
                $sqlContent = $this->generateSqlBackup();
                $zip->addFromString($sqlFileName, $sqlContent);
            }
            
            $zip->close();
            return file_exists($backupPath) ? $backupPath : false;
            
        } catch (Exception $e) {
            error_log('Backup files error: ' . $e->getMessage());
            if (file_exists($backupPath)) {
                @unlink($backupPath);
            }
            return false;
        }
    }
    
    /**
     * 递归添加目录到 ZIP
     */
    private function addDirectoryToZip($zip, $sourcePath, $zipPath, $excludePatterns, $basePath = '')
    {
        if ($basePath === '') {
            $basePath = $sourcePath;
        }
        
        if (!is_dir($sourcePath)) {
            return;
        }
        
        $iterator = new RecursiveIteratorIterator(
            new RecursiveDirectoryIterator($sourcePath, RecursiveDirectoryIterator::SKIP_DOTS),
            RecursiveIteratorIterator::SELF_FIRST
        );
        
        foreach ($iterator as $file) {
            $relativePath = $zipPath . '/' . substr($file->getPathname(), strlen($basePath) + 1);
            $relativePath = str_replace('\\', '/', $relativePath);
            
            // 检查是否应该排除
            $shouldExclude = false;
            foreach ($excludePatterns as $pattern) {
                if ($this->matchExcludePattern($file->getFilename(), $pattern) || 
                    $this->matchExcludePattern($relativePath, $pattern)) {
                    $shouldExclude = true;
                    break;
                }
            }
            
            if ($shouldExclude) {
                continue;
            }
            
            if ($file->isDir()) {
                $zip->addEmptyDir($relativePath);
            } else {
                $zip->addFile($file->getPathname(), $relativePath);
            }
        }
    }
    
    /**
     * 匹配排除模式
     */
    private function matchExcludePattern($name, $pattern)
    {
        if (strpos($pattern, '*') === false) {
            return $name === $pattern;
        }
        $pattern = '/^' . str_replace(['*', '/'], ['.*', '\/'], $pattern) . '$/i';
        return (bool) preg_match($pattern, $name);
    }
    
    /**
     * 生成 SQL 备份内容（内部使用）
     */
    private function generateSqlBackup()
    {
        $tables = $this->getTables();
        $content = "-- HuSNS Database Backup\n";
        $content .= "-- Generated: " . date('Y-m-d H:i:s') . "\n";
        $content .= "-- Database: " . DB_NAME . "\n\n";
        
        foreach ($tables as $table) {
            $createSql = $this->getTableCreate($table);
            $content .= $createSql . ";\n\n";
            $data = $this->getTableData($table);
            if (!empty($data)) {
                $content .= $data . "\n";
            }
        }
        
        return $content;
    }
    
    /**
     * 获取备份文件列表（支持所有类型）
     * @param string $type 文件类型: 'sql', 'zip', 'all'
     * @return array
     */
    public function getBackupFiles($type = 'all')
    {
        $files = [];
        
        if ($handle = opendir($this->backupDir)) {
            while (($file = readdir($handle)) !== false) {
                if ($file == '.' || $file == '..') {
                    continue;
                }
                
                $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
                
                if ($type === 'sql' && $ext !== 'sql') {
                    continue;
                }
                if ($type === 'zip' && $ext !== 'zip') {
                    continue;
                }
                
                $path = $this->backupDir . $file;
                $files[] = [
                    'name' => $file,
                    'path' => $path,
                    'type' => $ext,
                    'size' => filesize($path),
                    'size_text' => $this->formatSize(filesize($path)),
                    'created_at' => filemtime($path),
                    'created_at_text' => date('Y-m-d H:i:s', filemtime($path))
                ];
            }
            closedir($handle);
        }
        
        usort($files, function($a, $b) {
            return $b['created_at'] - $a['created_at'];
        });
        
        return $files;
    }
    
    /**
     * 获取备份统计信息
     * @return array
     */
    public function getBackupStats()
    {
        $sqlBackups = $this->getBackupFiles('sql');
        $zipBackups = $this->getBackupFiles('zip');
        
        $totalSize = 0;
        foreach ($sqlBackups as $b) { $totalSize += $b['size']; }
        foreach ($zipBackups as $b) { $totalSize += $b['size']; }
        
        return [
            'sql_count' => count($sqlBackups),
            'zip_count' => count($zipBackups),
            'total_count' => count($sqlBackups) + count($zipBackups),
            'total_size' => $totalSize,
            'total_size_text' => $this->formatSize($totalSize),
            'latest_sql' => !empty($sqlBackups) ? $sqlBackups[0]['created_at_text'] : null,
            'latest_zip' => !empty($zipBackups) ? $zipBackups[0]['created_at_text'] : null,
        ];
    }
    
    /**
     * 删除备份文件（支持任意类型）
     * @param string $fileName 文件名
     * @return bool
     */
    public function deleteBackup($fileName)
    {
        if (strpos($fileName, '..') !== false) {
            return false;
        }
        $path = $this->backupDir . $fileName;
        if (file_exists($path)) {
            return unlink($path);
        }
        return false;
    }
}
?>