#!/usr/bin/env node
/**
 * Monero wallet-RPC proxy server.
 * Avoids browser CORS by forwarding server-side requests.
 *
 * Start: node api/monero-balance-node.js
 * Or: node api/monero-balance-node.js --port 3002
 *
 * POST /json_rpc (or / or /balance) -> forwards JSON-RPC call
 * GET /health -> health check
 */
'use strict';

const http = require('http');
const https = require('https');

const PORT = parseInt(process.env.PORT || process.argv.find((a, i) => a === '--port' && process.argv[i + 1]) ? process.argv[process.argv.indexOf('--port') + 1] : 3001, 10);
const REMOTE_RPC_URL = 'https://monero.nationalelectricwatercooler.store/json_rpc';
const NETWORK_HEIGHT_URL = 'https://api.moneroocean.stream/network/stats';

function callRemoteRpc(payload) {
    return new Promise((resolve, reject) => {
        const req = https.request(REMOTE_RPC_URL, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Content-Length': Buffer.byteLength(payload)
            },
            timeout: 30000
        }, (res) => {
            let body = '';
            res.on('data', chunk => { body += chunk; });
            res.on('end', () => {
                resolve({
                    statusCode: res.statusCode || 500,
                    body: body || '{}'
                });
            });
        });

        req.on('timeout', () => {
            req.destroy(new Error('Remote RPC timeout'));
        });
        req.on('error', reject);
        req.write(payload);
        req.end();
    });
}

function fetchNetworkHeightFromInternet() {
    return new Promise((resolve, reject) => {
        const req = https.get(NETWORK_HEIGHT_URL, { timeout: 20000 }, (res) => {
            let body = '';
            res.on('data', chunk => { body += chunk; });
            res.on('end', () => {
                if (res.statusCode < 200 || res.statusCode >= 300) {
                    reject(new Error('Network height API failed with status ' + res.statusCode));
                    return;
                }
                try {
                    const data = JSON.parse(body || '{}');
                    const heights = Object.keys(data).map((k) => {
                        const item = data[k];
                        return item && (typeof item.height === 'number' || typeof item.height === 'string')
                            ? parseInt(item.height, 10)
                            : NaN;
                    }).filter((h) => !Number.isNaN(h) && h > 0);
                    if (!heights.length) {
                        reject(new Error('No valid height in network stats response'));
                        return;
                    }
                    resolve(Math.max.apply(null, heights));
                } catch (e) {
                    reject(new Error('Invalid network height response'));
                }
            });
        });

        req.on('timeout', () => {
            req.destroy(new Error('Network height request timeout'));
        });
        req.on('error', reject);
    });
}

const server = http.createServer(async (req, res) => {
    // CORS
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');

    if (req.method === 'OPTIONS') {
        res.writeHead(204);
        res.end();
        return;
    }

    if (req.method === 'GET' && (req.url === '/' || req.url === '/health')) {
        res.setHeader('Content-Type', 'application/json');
        res.writeHead(200);
        res.end(JSON.stringify({ ok: true, service: 'monero-balance-node', version: '1.0' }));
        return;
    }

    if (req.method === 'GET' && req.url === '/network_height') {
        try {
            const height = await fetchNetworkHeightFromInternet();
            res.setHeader('Content-Type', 'application/json');
            res.writeHead(200);
            res.end(JSON.stringify({
                ok: true,
                height: height,
                restore_height: Math.max(0, height - 50000)
            }));
        } catch (err) {
            res.setHeader('Content-Type', 'application/json');
            res.writeHead(502);
            res.end(JSON.stringify({ error: 'Failed to fetch network height', detail: err.message }));
        }
        return;
    }

    if (req.method !== 'POST' || (req.url !== '/' && req.url !== '/balance' && req.url !== '/json_rpc')) {
        res.setHeader('Content-Type', 'application/json');
        res.writeHead(404);
        res.end(JSON.stringify({ error: 'Not found' }));
        return;
    }

    let body = '';
    req.on('data', chunk => { body += chunk; });
    req.on('end', async () => {
        let payload;
        try {
            payload = body ? JSON.parse(body) : { jsonrpc: '2.0', id: '0', method: 'get_balance' };
        } catch (e) {
            res.setHeader('Content-Type', 'application/json');
            res.writeHead(400);
            res.end(JSON.stringify({ error: 'Invalid JSON body' }));
            return;
        }

        const allowedMethods = ['get_balance', 'get_transfers', 'transfer', 'open_wallet', 'get_address', 'close_wallet', 'get_height', 'generate_from_keys'];
        if (!payload || allowedMethods.indexOf(payload.method) === -1) {
            res.setHeader('Content-Type', 'application/json');
            res.writeHead(400);
            res.end(JSON.stringify({ error: 'Unsupported method. Allowed: get_balance, get_transfers, transfer, open_wallet, get_address, close_wallet, get_height, generate_from_keys' }));
            return;
        }

        if (payload.method === 'get_transfers' && !payload.params) {
            payload.params = { in: true, out: true, pending: true, failed: true };
        }

        try {
            const remote = await callRemoteRpc(JSON.stringify(payload));
            res.setHeader('Content-Type', 'application/json');
            res.writeHead(remote.statusCode >= 200 && remote.statusCode < 300 ? 200 : remote.statusCode);
            res.end(remote.body);
        } catch (err) {
            console.error('RPC proxy error:', err.message);
            res.setHeader('Content-Type', 'application/json');
            res.writeHead(502);
            res.end(JSON.stringify({ error: 'RPC proxy failed', detail: err.message }));
        }
    });
});

server.listen(PORT, () => {
    console.log('Monero wallet-RPC proxy listening on http://localhost:' + PORT);
    console.log('POST /json_rpc (or /balance) -> remote get_balance/get_transfers');
});
