const http = require('http');

class CDPClient {
    constructor(wsUrl) {
        this.wsUrl = wsUrl;
        this.ws = null;
        this.id = 0;
        this.handlers = new Map();
    }

    connect() {
        return new Promise((resolve, reject) => {
            this.ws = new WebSocket(this.wsUrl);
            this.ws.onopen = () => resolve();
            this.ws.onerror = () => reject(new Error('WebSocket connection error'));
            this.ws.onmessage = (event) => {
                try {
                    const msg = JSON.parse(event.data.toString());
                    if (!msg.id) return;
                    const cb = this.handlers.get(msg.id);
                    if (cb) cb(msg);
                } catch (_) {}
            };
        });
    }

    call(method, params = {}) {
        return new Promise((resolve, reject) => {
            const id = ++this.id;
            const timeout = setTimeout(() => {
                this.handlers.delete(id);
                reject(new Error(`CDP timeout: ${method}`));
            }, 10000);

            this.handlers.set(id, (res) => {
                clearTimeout(timeout);
                this.handlers.delete(id);
                if (res.error) reject(new Error(res.error.message));
                else resolve(res.result || {});
            });

            this.ws.send(JSON.stringify({ id, method, params }));
        });
    }

    close() {
        if (this.ws) this.ws.close();
    }
}

function getJson(url) {
    return new Promise((resolve) => {
        const req = http.get(url, (res) => {
            let data = '';
            res.on('data', (c) => (data += c));
            res.on('end', () => {
                try {
                    resolve(JSON.parse(data));
                } catch (_) {
                    resolve(null);
                }
            });
        });
        req.on('error', () => resolve(null));
        req.setTimeout(2000, () => {
            req.destroy();
            resolve(null);
        });
    });
}

async function getApiBlockInfo() {
    const root = await getJson('http://127.0.0.1:31401/');
    if (!root || typeof root !== 'object') return null;

    const coreLatest = Number(root.core_latest_ledger || 0);
    const historyLatest = Number(root.history_latest_ledger || 0);
    const ingestLatest = Number(root.ingest_latest_ledger || 0);

    if (!Number.isFinite(coreLatest) || coreLatest <= 0) return null;

    let latestBlock = 0;
    let latestSource = 'unknown';

    if (historyLatest > 0) {
        latestBlock = historyLatest;
        latestSource = 'history_latest_ledger';
    } else if (ingestLatest > 0) {
        latestBlock = ingestLatest;
        latestSource = 'ingest_latest_ledger';
    } else {
        latestBlock = coreLatest;
        latestSource = 'core_latest_ledger';
    }

    const diff = Math.abs(latestBlock - coreLatest);
    const syncKnown = historyLatest > 0 || ingestLatest > 0;

    return {
        mode: 'api_31401',
        localBlock: coreLatest,
        latestBlock,
        latestSource,
        diff,
        isSynced: syncKnown ? diff < 20 : null,
        raw: {
            core_latest_ledger: coreLatest,
            history_latest_ledger: historyLatest,
            ingest_latest_ledger: ingestLatest,
        },
    };
}

async function findLocalTarget() {
    const ports = [8888, 9222];
    const endpoints = ['/json', '/json/list'];
    for (const port of ports) {
        for (const ep of endpoints) {
            const targets = await getJson(`http://127.0.0.1:${port}${ep}`);
            if (!Array.isArray(targets)) continue;

            const local = targets.find((t) =>
                (t.url && t.url.startsWith('file://') && t.title === 'Pi Desktop') ||
                (t.type === 'page' && t.title === 'Pi Desktop')
            );

            if (local && local.webSocketDebuggerUrl) {
                return { target: local, port, endpoint: ep };
            }
        }
    }
    return null;
}

function parseBlockText(text) {
    const local = text.match(/Local Block number:?\s*(\d+)/i)?.[1] || null;
    const latest = text.match(/Latest Block number:?\s*(\d+)/i)?.[1] || null;
    if (!local || !latest) return null;
    const localNum = parseInt(local, 10);
    const latestNum = parseInt(latest, 10);
    const diff = Math.abs(latestNum - localNum);
    return {
        localBlock: localNum,
        latestBlock: latestNum,
        diff,
        isSynced: diff < 20,
    };
}

async function main() {
    const apiInfo = await getApiBlockInfo();
    if (apiInfo) {
        console.log('[Block][API] 已命中 http://localhost:31401/');
        console.log(`[Block][API] local=${apiInfo.localBlock} latest=${apiInfo.latestBlock} source=${apiInfo.latestSource} diff=${apiInfo.diff}`);
        if (apiInfo.isSynced === null) {
            console.log('[Block][API] synced=unknown (history/ingest 账本尚未提供，无法严格判定)');
        } else {
            console.log(`[Block][API] synced=${apiInfo.isSynced}`);
        }
        return;
    }

    console.log('[Block] 正在定位 Pi Desktop 本地页面...');
    const found = await findLocalTarget();
    if (!found) {
        console.error('[Block] 未找到本地 Pi Desktop target，请确认 Pi 客户端已开启调试端口。');
        process.exit(1);
    }

    const { target, port, endpoint } = found;
    console.log(`[Block] 调试端口: ${port}${endpoint}`);
    console.log(`[Block] 目标: ${target.title} (${target.url})`);

    const client = new CDPClient(target.webSocketDebuggerUrl);
    try {
        await client.connect();
        await client.call('Page.enable');
        await client.call('Runtime.enable');

        await client.call('Runtime.evaluate', { expression: "window.location.hash='/node-switch'" });
        await new Promise((r) => setTimeout(r, 4000));

        const evalRes = await client.call('Runtime.evaluate', {
            expression: '(function(){try{return document.body.innerText||"";}catch(e){return "";}})()',
            returnByValue: true,
        });

        const bodyText = evalRes?.result?.value || '';
        const parsed = parseBlockText(bodyText);
        if (!parsed) {
            console.error('[Block] 未解析到区块高度，请确认页面已加载到 node-switch。');
            process.exit(2);
        }

        console.log(`[Block] Local=${parsed.localBlock} Latest=${parsed.latestBlock} Diff=${parsed.diff} Synced=${parsed.isSynced}`);
    } catch (e) {
        console.error('[Block] 执行失败:', e.message || e);
        process.exit(1);
    } finally {
        client.close();
    }
}

main();
