import { promises as fs } from 'fs';
import path from 'path';
import * as tar from 'tar';
import { createReadStream, createWriteStream } from 'fs';
import * as zlib from 'zlib';
import { createTarSecurityFilter } from '../../utils/tarSecurityFilter.js';
export class BackupManager {
    constructor() {
        const baseDir = process.cwd();
        const possiblePaths = [
            path.join(baseDir, 'data', 'backupdata'),
            path.join(baseDir, 'server', 'data', 'backupdata')
        ];
        this.backupRoot = possiblePaths[0];
        for (const p of possiblePaths) {
            this.backupRoot = p;
            break;
        }
    }
    async ensureDir(dir) {
        await fs.mkdir(dir, { recursive: true });
    }
    async listBackups() {
        await this.ensureDir(this.backupRoot);
        const entries = await fs.readdir(this.backupRoot, { withFileTypes: true });
        const result = [];
        for (const e of entries) {
            if (!e.isDirectory())
                continue;
            const backupDir = path.join(this.backupRoot, e.name);
            const files = await fs.readdir(backupDir);
            const detailed = [];
            for (const f of files) {
                if (f === 'data.json')
                    continue;
                const full = path.join(backupDir, f);
                const stat = await fs.stat(full);
                if (stat.isFile()) {
                    detailed.push({ fileName: f, size: stat.size, modified: stat.mtime.toISOString() });
                }
            }
            let meta = {};
            try {
                const metaRaw = await fs.readFile(path.join(backupDir, 'data.json'), 'utf-8');
                meta = JSON.parse(metaRaw);
            }
            catch { }
            const totalSize = detailed.reduce((sum, file) => sum + file.size, 0);
            result.push({
                name: e.name,
                baseDir: backupDir,
                files: detailed.sort((a, b) => a.fileName.localeCompare(b.fileName)),
                totalSize,
                meta
            });
        }
        return result;
    }
    formatTimestamp(date) {
        const pad = (n) => n.toString().padStart(2, '0');
        return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}_${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`;
    }
    async createBackup(backupName, sourcePath, maxKeep) {
        if (!backupName || !sourcePath)
            throw new Error('参数缺失: backupName 或 sourcePath');
        const backupDir = path.join(this.backupRoot, backupName);
        await this.ensureDir(backupDir);
        await fs.writeFile(path.join(backupDir, 'data.json'), JSON.stringify({ sourcePath }, null, 2));
        const timestamp = this.formatTimestamp(new Date());
        const archivePath = path.join(backupDir, `${timestamp}.tar.xz`);
        const tempTarPath = archivePath.replace('.tar.xz', '.tar');
        const sourceStat = await fs.stat(sourcePath);
        const cwd = sourceStat.isDirectory() ? path.dirname(sourcePath) : path.dirname(sourcePath);
        const entries = [path.basename(sourcePath)];
        await tar.create({ f: tempTarPath, cwd }, entries);
        await new Promise((resolve, reject) => {
            const readStream = createReadStream(tempTarPath);
            const writeStream = createWriteStream(archivePath);
            const gzip = zlib.createGzip({ level: 6 });
            readStream.pipe(gzip).pipe(writeStream)
                .on('finish', async () => {
                try {
                    await fs.unlink(tempTarPath);
                }
                catch { }
                resolve();
            })
                .on('error', reject);
        });
        if (Number.isFinite(maxKeep) && maxKeep > 0) {
            const items = (await fs.readdir(backupDir))
                .filter(f => f.endsWith('.tar.xz'))
                .sort();
            if (items.length > maxKeep) {
                const removeCount = items.length - maxKeep;
                for (let i = 0; i < removeCount; i++) {
                    const toRemove = path.join(backupDir, items[i]);
                    try {
                        await fs.unlink(toRemove);
                    }
                    catch { }
                }
            }
        }
        return { archivePath };
    }
    async restoreBackup(backupName, fileName) {
        const backupDir = path.join(this.backupRoot, backupName);
        const metaRaw = await fs.readFile(path.join(backupDir, 'data.json'), 'utf-8');
        const meta = JSON.parse(metaRaw);
        const targetPath = meta.sourcePath;
        if (!path.isAbsolute(targetPath)) {
            throw new Error('恢复目标路径不是绝对路径，已阻止操作');
        }
        if (targetPath.trim().length < 2) {
            throw new Error('恢复目标路径不合法，已阻止操作');
        }
        try {
            await fs.rm(targetPath, { recursive: true, force: true });
        }
        catch { }
        await this.ensureDir(path.dirname(targetPath));
        const archivePath = path.join(backupDir, fileName);
        const tempTarPath = archivePath.replace(/\.(tar\.xz|txz)$/i, '.tar');
        await new Promise((resolve, reject) => {
            const readStream = createReadStream(archivePath);
            const gunzip = zlib.createGunzip();
            const writeStream = createWriteStream(tempTarPath);
            readStream.pipe(gunzip).pipe(writeStream)
                .on('finish', () => resolve())
                .on('error', reject);
        });
        const extractCwd = path.dirname(targetPath);
        await tar.extract({
            file: tempTarPath,
            cwd: extractCwd,
            filter: createTarSecurityFilter({ cwd: extractCwd })
        });
        try {
            await fs.unlink(tempTarPath);
        }
        catch { }
        return { targetPath };
    }
    async deleteBackupFile(backupName, fileName) {
        const backupDir = path.join(this.backupRoot, backupName);
        const target = path.join(backupDir, fileName);
        await fs.unlink(target);
    }
    async deleteBackupFolder(backupName) {
        const dir = path.join(this.backupRoot, backupName);
        await fs.rm(dir, { recursive: true, force: true });
    }
    async getBackupFilePath(backupName, fileName) {
        const backupDir = path.join(this.backupRoot, backupName);
        const filePath = path.join(backupDir, fileName);
        try {
            await fs.access(filePath);
            return filePath;
        }
        catch (error) {
            throw new Error('备份文件不存在');
        }
    }
}
export const backupManager = new BackupManager();
