import { Endpoint } from '@rayhanadev/iroh';
import * as crypto from 'crypto';
const IROH_ALPN = 'bolloon/iroh/1';
export class IrohTransport {
    endpoint = null;
    messageHandlers = new Map();
    running = false;
    ownNodeId = null;
    peers = new Map();
    connectTimeoutMs = 10000;
    acceptLoop = null;
    // 新增: 存储层
    messageStore = null;
    offlineDeliveryInterval = null;
    requestTimeoutMs = 30000;
    // 新增: 待响应请求 (内存缓存)
    pendingRequests = new Map();
    requestIdToNodeId = new Map();
    async start(secretKey, enablePersistence = false) {
        if (this.endpoint && this.ownNodeId) {
            // 已启动，返回当前信息
            return {
                nodeId: this.ownNodeId,
                addr: this.ownNodeId // iroh 没有 listenAddresses，用 nodeId 作为 addr
            };
        }
        const options = { alpns: [IROH_ALPN] };
        if (secretKey) {
            options.secretKey = secretKey;
        }
        this.endpoint = await Endpoint.createWithOptions(options);
        this.ownNodeId = this.endpoint.nodeId();
        await this.endpoint.online();
        this.running = true;
        console.log('[IrohTransport] Started, node:', this.ownNodeId.substring(0, 16) + '...');
        // 初始化存储层 (如果启用)
        if (enablePersistence) {
            this.messageStore = await this.createMessageStore();
            await this.messageStore.initialize();
            this.startOfflineDeliveryLoop();
            console.log('[IrohTransport] Persistence enabled');
        }
        this.startAcceptLoop();
        return {
            nodeId: this.endpoint.nodeId(),
            addr: this.ownNodeId // iroh 没有 addr()，用 nodeId
        };
    }
    async createMessageStore() {
        // 动态导入 JSON 存储适配器
        try {
            const { JsonMessageStore } = await import('./storage/adapters/json-adapter.js');
            const path = await import('path');
            const baseDir = path.join(process.env.HOME || '/tmp', '.bolloon', 'messages-iroh');
            return new JsonMessageStore({ baseDir });
        }
        catch (e) {
            console.warn('[IrohTransport] Failed to load JSON adapter, using in-memory store');
            return this.createInMemoryStore();
        }
    }
    createInMemoryStore() {
        const offlineQueues = new Map();
        const pendingResponses = new Map();
        return {
            async saveMessage() { },
            async enqueueOfflineMessage(msg) {
                const id = crypto.randomUUID();
                const offline = { ...msg, id };
                const queue = offlineQueues.get(msg.targetNodeId) || [];
                queue.push(offline);
                offlineQueues.set(msg.targetNodeId, queue);
                return offline;
            },
            async getOfflineMessages(targetNodeId) {
                return offlineQueues.get(targetNodeId) || [];
            },
            async dequeueOfflineMessage(id) {
                for (const [nodeId, queue] of offlineQueues.entries()) {
                    const idx = queue.findIndex(m => m.id === id);
                    if (idx >= 0) {
                        queue.splice(idx, 1);
                        offlineQueues.set(nodeId, queue);
                        return;
                    }
                }
            },
            async incrementOfflineRetry(id) {
                for (const queue of offlineQueues.values()) {
                    const msg = queue.find(m => m.id === id);
                    if (msg) {
                        msg.retryCount++;
                        return;
                    }
                }
            },
            async getPendingOfflineCount() {
                let count = 0;
                for (const queue of offlineQueues.values())
                    count += queue.length;
                return count;
            },
            async getAllOfflineTargets() {
                return Array.from(offlineQueues.keys());
            },
            async savePendingResponse(req) {
                const id = crypto.randomUUID();
                const pending = { ...req, id };
                pendingResponses.set(req.requestId, pending);
                return pending;
            },
            async getPendingResponse(requestId) {
                return pendingResponses.get(requestId) || null;
            },
            async removePendingResponse(requestId) {
                pendingResponses.delete(requestId);
            },
            async initialize() { },
            async shutdown() {
                offlineQueues.clear();
                pendingResponses.clear();
            },
        };
    }
    startOfflineDeliveryLoop() {
        if (!this.messageStore)
            return;
        this.offlineDeliveryInterval = setInterval(async () => {
            // 遍历所有有离线消息的目标节点（不仅是"已连接"的）
            // 这样目标节点一旦在线（accept 连接）就能拿到离线消息
            const allTargets = await this.messageStore.getAllOfflineTargets();
            const connectedPeers = this.getConnectedPeers();
            // 合并：已连接 + 有离线消息但未连接（也会去尝试 connect）
            const targets = new Set([...connectedPeers, ...allTargets]);
            for (const peerId of targets) {
                const offlineMsgs = await this.messageStore.getOfflineMessages(peerId);
                for (const msg of offlineMsgs) {
                    if (msg.retryCount >= 10) {
                        // 超过最大重试次数，丢弃
                        await this.messageStore.dequeueOfflineMessage(msg.id);
                        console.log(`[IrohTransport] Dropped offline message after ${msg.retryCount} retries`);
                        continue;
                    }
                    try {
                        const payload = Uint8Array.from(atob(msg.payload), c => c.charCodeAt(0));
                        const success = await this.sendMessageDirect(peerId, msg.type, payload);
                        if (success) {
                            await this.messageStore.dequeueOfflineMessage(msg.id);
                            console.log(`[IrohTransport] Delivered offline message to ${peerId.substring(0, 12)}...`);
                        }
                        else {
                            await this.messageStore.incrementOfflineRetry(msg.id);
                        }
                    }
                    catch {
                        await this.messageStore.incrementOfflineRetry(msg.id);
                    }
                }
            }
        }, 5000);
    }
    startAcceptLoop() {
        if (!this.endpoint)
            return;
        this.acceptLoop = setInterval(async () => {
            if (!this.endpoint || !this.running)
                return;
            try {
                const conn = await this.endpoint.accept();
                if (conn) {
                    this.handleConnection(conn);
                }
            }
            catch {
                // Silently ignore during shutdown
            }
        }, 100);
    }
    async handleConnection(conn) {
        const remoteNodeId = conn.remoteNodeId();
        this.updatePeer(remoteNodeId, true);
        try {
            const { recv } = await conn.acceptBi();
            const data = await recv.readToEnd(64 * 1024);
            if (data.length > 0) {
                const text = new TextDecoder().decode(data);
                const colonIdx = text.indexOf(':');
                let type;
                let payload;
                let requestId;
                // 检查是否是请求/响应消息
                if (text.startsWith('REQ:')) {
                    // Request: REQ:<requestId>:<type>:<payload>
                    const parts = text.substring(4).split(':');
                    requestId = parts[0];
                    type = parts[1] || 'request';
                    const payloadStr = parts.slice(2).join(':');
                    payload = new TextEncoder().encode(payloadStr);
                    // 保存请求信息，以便稍后响应
                    if (requestId) {
                        this.requestIdToNodeId.set(requestId, remoteNodeId);
                    }
                }
                else if (text.startsWith('RESP:')) {
                    // Response: RESP:<requestId>:<payload>
                    const parts = text.substring(5).split(':');
                    requestId = parts[0];
                    type = 'RESPONSE';
                    payload = new TextEncoder().encode(parts.slice(1).join(':'));
                    // 处理响应 - 解决 pending request
                    this.handleResponse(requestId, payload);
                }
                else {
                    type = colonIdx > 0 ? text.substring(0, colonIdx) : 'raw';
                    payload = new TextEncoder().encode(text.substring(colonIdx + 1));
                }
                const handler = this.messageHandlers.get(type);
                if (handler) {
                    handler({ type, payload, from: remoteNodeId, requestId });
                }
                else if (this.messageHandlers.has('*')) {
                    this.messageHandlers.get('*')({ type, payload, from: remoteNodeId, requestId });
                }
            }
        }
        catch {
            // Connection closed
        }
        finally {
            try {
                conn.close();
            }
            catch { }
        }
    }
    handleResponse(requestId, responseData) {
        const pending = this.pendingRequests.get(requestId);
        if (pending) {
            clearTimeout(pending.timeout);
            pending.resolve(responseData);
            this.pendingRequests.delete(requestId);
            console.log(`[IrohTransport] Response received for request ${requestId}`);
        }
        // 如果有持久化存储，也从那里删除
        if (this.messageStore) {
            this.messageStore.removePendingResponse(requestId);
        }
    }
    updatePeer(nodeId, connected) {
        const existing = this.peers.get(nodeId);
        this.peers.set(nodeId, {
            nodeId,
            lastSeen: Date.now(),
            connected: existing?.connected || connected,
        });
    }
    async connect(targetNodeId) {
        if (!this.endpoint) {
            throw new Error('IrohTransport not started');
        }
        try {
            const conn = await this.connectWithTimeout(targetNodeId);
            if (!conn) {
                console.warn('[IrohTransport] Connection timeout to', targetNodeId.substring(0, 12) + '...');
                return false;
            }
            this.updatePeer(targetNodeId, true);
            conn.close();
            return true;
        }
        catch (e) {
            console.warn('[IrohTransport] Connect failed:', e);
            return false;
        }
    }
    async connectWithTimeout(nodeId) {
        if (!this.endpoint)
            return null;
        const connectPromise = this.endpoint.connect(nodeId, IROH_ALPN);
        const timeout = new Promise((resolve) => setTimeout(() => resolve(null), this.connectTimeoutMs));
        return Promise.race([connectPromise, timeout]);
    }
    async sendMessage(targetNodeId, type, payload) {
        const success = await this.sendMessageDirect(targetNodeId, type, payload);
        // 如果发送失败且启用了持久化，存入离线队列
        if (!success && this.messageStore) {
            console.log(`[IrohTransport] Message send failed, enqueuing for offline delivery`);
            const payloadBase64 = btoa(String.fromCharCode(...payload));
            await this.messageStore.enqueueOfflineMessage({
                targetNodeId,
                type,
                payload: payloadBase64,
                createdAt: Date.now(),
                transport: 'iroh',
                retryCount: 0,
            });
        }
        return success;
    }
    async sendMessageDirect(targetNodeId, type, payload) {
        if (!this.endpoint) {
            throw new Error('IrohTransport not started');
        }
        try {
            const conn = await this.connectWithTimeout(targetNodeId);
            if (!conn) {
                return false;
            }
            const { send } = await conn.openBi();
            const message = type + ':' + new TextDecoder().decode(payload);
            await send.writeAll(Buffer.from(message));
            await send.finish();
            conn.close();
            this.updatePeer(targetNodeId, true);
            return true;
        }
        catch (e) {
            console.warn('[IrohTransport] Send failed:', e);
            return false;
        }
    }
    async requestResponse(targetNodeId, type, payload, timeoutMs) {
        if (!this.endpoint) {
            throw new Error('IrohTransport not started');
        }
        const timeout = timeoutMs || this.requestTimeoutMs;
        const requestId = crypto.randomUUID();
        return new Promise(async (resolve, reject) => {
            const timer = setTimeout(() => {
                this.pendingRequests.delete(requestId);
                if (this.messageStore) {
                    this.messageStore.removePendingResponse(requestId);
                }
                reject(new Error(`Request ${requestId} timed out after ${timeout}ms`));
            }, timeout);
            this.pendingRequests.set(requestId, {
                requestId,
                type,
                payload,
                timestamp: Date.now(),
                resolve,
                reject,
                timeout: timer,
            });
            // 保存到持久化存储
            if (this.messageStore) {
                await this.messageStore.savePendingResponse({
                    requestId,
                    type,
                    payload: new TextDecoder().decode(payload),
                    fromNodeId: this.ownNodeId,
                    timestamp: Date.now(),
                    timeout,
                });
            }
            // 尝试发送请求
            try {
                const conn = await this.connectWithTimeout(targetNodeId);
                if (!conn) {
                    clearTimeout(timer);
                    this.pendingRequests.delete(requestId);
                    resolve(null);
                    return;
                }
                const { send, recv } = await conn.openBi();
                const requestMsg = `REQ:${requestId}:${type}:${new TextDecoder().decode(payload)}`;
                await send.writeAll(Buffer.from(requestMsg));
                await send.finish();
                // 等待响应，带超时
                // 注意: server sendResponse 后会关闭连接，导致 readToEnd 以 "connection lost" 错误 reject
                // 这里我们把 readToEnd 的错误吞掉（视为流结束），只有超时才视为失败
                const response = await Promise.race([
                    recv.readToEnd(64 * 1024).catch(() => new Uint8Array(0)),
                    new Promise((_, rejectTimeout) => setTimeout(() => rejectTimeout(new Error('timeout')), timeout)),
                ]);
                conn.close();
                clearTimeout(timer);
                this.pendingRequests.delete(requestId);
                if (this.messageStore) {
                    this.messageStore.removePendingResponse(requestId);
                }
                if (response) {
                    resolve(response);
                }
                else {
                    resolve(null);
                }
            }
            catch (e) {
                clearTimeout(timer);
                this.pendingRequests.delete(requestId);
                reject(e);
            }
        });
    }
    async sendResponse(requestId, type, responsePayload) {
        const targetNodeId = this.requestIdToNodeId.get(requestId);
        if (!targetNodeId) {
            console.warn(`[IrohTransport] No target node for request ${requestId}`);
            return false;
        }
        try {
            const conn = await this.connectWithTimeout(targetNodeId);
            if (!conn)
                return false;
            const { send } = await conn.openBi();
            const responseMsg = `RESP:${requestId}:${responsePayload}`;
            await send.writeAll(Buffer.from(responseMsg));
            await send.finish();
            conn.close();
            this.requestIdToNodeId.delete(requestId);
            console.log(`[IrohTransport] Sent response for request ${requestId}`);
            return true;
        }
        catch (e) {
            console.warn(`[IrohTransport] Failed to send response:`, e);
            return false;
        }
    }
    async broadcast(type, payload) {
        const peers = Array.from(this.peers.keys());
        const promises = peers.map((peerId) => this.sendMessage(peerId, type, payload).catch(() => false));
        await Promise.all(promises);
    }
    onMessage(type, handler) {
        this.messageHandlers.set(type, handler);
    }
    getNodeId() {
        return this.ownNodeId;
    }
    getPeers() {
        return Array.from(this.peers.values());
    }
    getConnectedPeers() {
        return Array.from(this.peers.values())
            .filter((p) => p.connected)
            .map((p) => p.nodeId);
    }
    isRunning() {
        return this.running;
    }
    setConnectTimeout(ms) {
        this.connectTimeoutMs = ms;
    }
    getPendingOfflineCount() {
        if (!this.messageStore)
            return 0;
        return this.messageStore.getPendingOfflineCount();
    }
    async shutdown() {
        this.running = false;
        if (this.offlineDeliveryInterval) {
            clearInterval(this.offlineDeliveryInterval);
        }
        if (this.acceptLoop) {
            clearInterval(this.acceptLoop);
            this.acceptLoop = null;
        }
        // 清理 pending requests
        for (const [id, pending] of this.pendingRequests) {
            clearTimeout(pending.timeout);
            pending.reject(new Error('Shutting down'));
        }
        this.pendingRequests.clear();
        if (this.messageStore) {
            await this.messageStore.shutdown();
            this.messageStore = null;
        }
        if (this.endpoint) {
            try {
                await this.endpoint.close();
            }
            catch { }
            this.endpoint = null;
        }
        this.peers.clear();
        this.ownNodeId = null;
        console.log('[IrohTransport] Shut down');
    }
}
export const irohTransport = new IrohTransport();
