/**
 * JSON File Adapter for MessageStore
 * 基于 JSON 文件的消息存储实现
 */
import * as fs from 'fs/promises';
import * as path from 'path';
import * as crypto from 'crypto';
const DEFAULT_CONFIG = {
    baseDir: '',
    maxFileSize: 10 * 1024 * 1024, // 10MB
    maxMessagesPerFile: 1000,
    fileNamingStrategy: 'daily',
    maxAge: 30 * 24 * 60 * 60 * 1000, // 30 days
};
export class JsonMessageStore {
    config;
    initialized = false;
    offlineMessages = new Map();
    pendingResponses = new Map();
    locks = new Map();
    constructor(config) {
        // 设置默认基础路径
        const baseDir = config.baseDir || path.join(process.env.HOME || '/tmp', '.bolloon', 'messages');
        this.config = { ...DEFAULT_CONFIG, ...config, baseDir };
    }
    async initialize() {
        // 创建必要的目录
        await fs.mkdir(this.config.baseDir, { recursive: true });
        await fs.mkdir(path.join(this.config.baseDir, 'offline'), { recursive: true });
        await fs.mkdir(path.join(this.config.baseDir, 'pending'), { recursive: true });
        // 加载离线消息
        await this.loadOfflineMessages();
        // 加载待响应请求
        await this.loadPendingResponses();
        this.initialized = true;
        console.log(`[JsonMessageStore] Initialized at ${this.config.baseDir}`);
    }
    // ============================================================================
    // 消息持久化
    // ============================================================================
    async saveMessage(msg) {
        const id = crypto.randomUUID();
        const stored = { ...msg, id };
        const filePath = this.getMessageFilePath(new Date(msg.timestamp));
        await this.withLock(filePath, async () => {
            let messages = await this.readJsonFile(filePath) || [];
            messages.push(stored);
            // 如果文件过大，拆分
            if (messages.length > this.config.maxMessagesPerFile) {
                const newFile = this.getMessageFilePath(new Date(), true);
                await this.writeJsonFile(newFile, messages.slice(this.config.maxMessagesPerFile / 2));
                messages = messages.slice(0, this.config.maxMessagesPerFile / 2);
            }
            await this.writeJsonFile(filePath, messages);
        });
        return stored;
    }
    async getMessage(id) {
        const allMessages = await this.getAllMessageFiles();
        for (const file of allMessages) {
            const messages = await this.readJsonFile(file) || [];
            const found = messages.find(m => m.id === id);
            if (found)
                return found;
        }
        return null;
    }
    async updateMessageStatus(id, status) {
        const allFiles = await this.getAllMessageFiles();
        for (const file of allFiles) {
            const messages = await this.readJsonFile(file);
            if (!messages)
                continue;
            const idx = messages.findIndex(m => m.id === id);
            if (idx >= 0) {
                messages[idx].status = status;
                await this.writeJsonFile(file, messages);
                return;
            }
        }
    }
    async getMessages(options) {
        const allMessages = [];
        const files = await this.getAllMessageFiles();
        for (const file of files) {
            const messages = await this.readJsonFile(file) || [];
            allMessages.push(...messages);
        }
        // 过滤
        let filtered = allMessages;
        if (options?.direction) {
            filtered = filtered.filter(m => m.direction === options.direction);
        }
        if (options?.type) {
            filtered = filtered.filter(m => m.type === options.type);
        }
        if (options?.from) {
            filtered = filtered.filter(m => m.from === options.from);
        }
        if (options?.to) {
            filtered = filtered.filter(m => m.to === options.to);
        }
        if (options?.startTime) {
            filtered = filtered.filter(m => m.timestamp >= options.startTime);
        }
        if (options?.endTime) {
            filtered = filtered.filter(m => m.timestamp <= options.endTime);
        }
        if (options?.status) {
            filtered = filtered.filter(m => m.status === options.status);
        }
        // 排序和分页
        filtered.sort((a, b) => b.timestamp - a.timestamp);
        if (options?.offset) {
            filtered = filtered.slice(options.offset);
        }
        if (options?.limit) {
            filtered = filtered.slice(0, options.limit);
        }
        return filtered;
    }
    async deleteMessage(id) {
        const allFiles = await this.getAllMessageFiles();
        for (const file of allFiles) {
            const messages = await this.readJsonFile(file);
            if (!messages)
                continue;
            const idx = messages.findIndex(m => m.id === id);
            if (idx >= 0) {
                messages.splice(idx, 1);
                await this.writeJsonFile(file, messages);
                return;
            }
        }
    }
    // ============================================================================
    // 离线消息队列
    // ============================================================================
    async enqueueOfflineMessage(msg) {
        const id = crypto.randomUUID();
        const offline = { ...msg, id };
        // 更新内存缓存
        const queue = this.offlineMessages.get(msg.targetNodeId) || [];
        queue.push(offline);
        this.offlineMessages.set(msg.targetNodeId, queue);
        // 持久化到文件
        const filePath = this.getOfflineFilePath(msg.targetNodeId);
        await this.writeJsonFile(filePath, queue);
        console.log(`[JsonMessageStore] Enqueued offline message for ${msg.targetNodeId}`);
        return offline;
    }
    async getOfflineMessages(targetNodeId) {
        // 优先从内存返回
        if (this.offlineMessages.has(targetNodeId)) {
            return this.offlineMessages.get(targetNodeId);
        }
        // 从文件加载
        const filePath = this.getOfflineFilePath(targetNodeId);
        const messages = await this.readJsonFile(filePath) || [];
        this.offlineMessages.set(targetNodeId, messages);
        return messages;
    }
    async dequeueOfflineMessage(id) {
        for (const [targetId, queue] of this.offlineMessages.entries()) {
            const idx = queue.findIndex(m => m.id === id);
            if (idx >= 0) {
                queue.splice(idx, 1);
                this.offlineMessages.set(targetId, queue);
                await this.writeJsonFile(this.getOfflineFilePath(targetId), queue);
                return;
            }
        }
    }
    async incrementOfflineRetry(id) {
        for (const [targetId, queue] of this.offlineMessages.entries()) {
            const idx = queue.findIndex(m => m.id === id);
            if (idx >= 0) {
                queue[idx].retryCount++;
                this.offlineMessages.set(targetId, queue);
                await this.writeJsonFile(this.getOfflineFilePath(targetId), queue);
                return;
            }
        }
    }
    async getPendingOfflineCount() {
        let count = 0;
        for (const queue of this.offlineMessages.values()) {
            count += queue.length;
        }
        return count;
    }
    async getAllOfflineTargets() {
        // 重新从磁盘加载最新状态（避免内存 vs 磁盘不一致）
        const baseDir = path.join(this.config.baseDir, 'offline');
        let files = [];
        try {
            files = await fs.readdir(baseDir);
        }
        catch {
            return Array.from(this.offlineMessages.keys());
        }
        return files
            .filter((f) => f.endsWith('.json'))
            .map((f) => f.replace(/\.json$/, ''))
            .filter((id) => id.length > 0);
    }
    // ============================================================================
    // 待响应请求
    // ============================================================================
    async savePendingResponse(req) {
        const id = crypto.randomUUID();
        const pending = { ...req, id };
        this.pendingResponses.set(req.requestId, pending);
        await this.savePendingResponsesToFile();
        console.log(`[JsonMessageStore] Saved pending response ${req.requestId}`);
        return pending;
    }
    async getPendingResponse(requestId) {
        // 优先从内存返回
        if (this.pendingResponses.has(requestId)) {
            return this.pendingResponses.get(requestId);
        }
        // 尝试从文件恢复
        await this.loadPendingResponses();
        return this.pendingResponses.get(requestId) || null;
    }
    async removePendingResponse(requestId) {
        this.pendingResponses.delete(requestId);
        await this.savePendingResponsesToFile();
    }
    // ============================================================================
    // 统计与清理
    // ============================================================================
    async getMessageCount() {
        const files = await this.getAllMessageFiles();
        let count = 0;
        for (const file of files) {
            const messages = await this.readJsonFile(file);
            if (messages)
                count += messages.length;
        }
        return count;
    }
    async getOfflineMessageCount() {
        return this.getPendingOfflineCount();
    }
    async pruneOldMessages(maxAgeMs) {
        const cutoff = Date.now() - maxAgeMs;
        let pruned = 0;
        const files = await this.getAllMessageFiles();
        for (const file of files) {
            const messages = await this.readJsonFile(file);
            if (!messages)
                continue;
            const originalLen = messages.length;
            const filtered = messages.filter(m => m.timestamp >= cutoff || m.status === 'pending');
            if (filtered.length < originalLen) {
                pruned += originalLen - filtered.length;
                await this.writeJsonFile(file, filtered);
            }
        }
        console.log(`[JsonMessageStore] Pruned ${pruned} old messages`);
        return pruned;
    }
    // ============================================================================
    // 生命周期
    // ============================================================================
    async shutdown() {
        // 保存所有待处理数据
        await this.savePendingResponsesToFile();
        for (const [targetId, queue] of this.offlineMessages.entries()) {
            if (queue.length > 0) {
                await this.writeJsonFile(this.getOfflineFilePath(targetId), queue);
            }
        }
        this.initialized = false;
        console.log('[JsonMessageStore] Shutdown complete');
    }
    // ============================================================================
    // 私有辅助方法
    // ============================================================================
    getMessageFilePath(date, useNewFile = false) {
        const dateStr = this.config.fileNamingStrategy === 'daily'
            ? date.toISOString().split('T')[0]
            : `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
        const filename = useNewFile
            ? `messages-${dateStr}-${Date.now()}.json`
            : `messages-${dateStr}.json`;
        return path.join(this.config.baseDir, filename);
    }
    getOfflineFilePath(nodeId) {
        // 清理 nodeId 中的非法字符
        const safeId = nodeId.replace(/[^a-zA-Z0-9_-]/g, '_');
        return path.join(this.config.baseDir, 'offline', `${safeId}.json`);
    }
    getPendingFilePath() {
        return path.join(this.config.baseDir, 'pending', 'responses.json');
    }
    async getAllMessageFiles() {
        try {
            const files = await fs.readdir(this.config.baseDir);
            return files
                .filter(f => f.startsWith('messages-') && f.endsWith('.json'))
                .map(f => path.join(this.config.baseDir, f));
        }
        catch {
            return [];
        }
    }
    async readJsonFile(filePath) {
        try {
            const content = await fs.readFile(filePath, 'utf-8');
            return JSON.parse(content);
        }
        catch {
            return null;
        }
    }
    async writeJsonFile(filePath, data) {
        const dir = path.dirname(filePath);
        await fs.mkdir(dir, { recursive: true });
        await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
    }
    async loadOfflineMessages() {
        const offlineDir = path.join(this.config.baseDir, 'offline');
        try {
            const files = await fs.readdir(offlineDir);
            for (const file of files) {
                if (!file.endsWith('.json'))
                    continue;
                const nodeId = file.replace('.json', '');
                const messages = await this.readJsonFile(path.join(offlineDir, file));
                if (messages) {
                    this.offlineMessages.set(nodeId, messages);
                }
            }
        }
        catch {
            // 目录不存在或为空
        }
    }
    async loadPendingResponses() {
        const filePath = this.getPendingFilePath();
        const data = await this.readJsonFile(filePath);
        if (data) {
            for (const req of data) {
                this.pendingResponses.set(req.requestId, req);
            }
        }
    }
    async savePendingResponsesToFile() {
        const data = Array.from(this.pendingResponses.values());
        await this.writeJsonFile(this.getPendingFilePath(), data);
    }
    async withLock(filePath, fn) {
        // 简化的文件锁实现
        const lockKey = filePath;
        // 等待现有锁
        while (this.locks.has(lockKey)) {
            await this.locks.get(lockKey);
        }
        // 获取新锁
        let releaseLock;
        const lockPromise = new Promise((resolve) => {
            releaseLock = () => {
                this.locks.delete(lockKey);
                resolve({ release: () => { } });
            };
        });
        this.locks.set(lockKey, lockPromise);
        try {
            return await fn();
        }
        finally {
            releaseLock();
        }
    }
}
