const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const { Connection, PublicKey, Keypair, VersionedTransaction, TransactionInstruction, ComputeBudgetProgram, SystemProgram } = require('@solana/web3.js');
const { getAssociatedTokenAddress, createAssociatedTokenAccountInstruction, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } = require('@solana/spl-token');
const { Metadata } = require('@metaplex-foundation/mpl-token-metadata');
const bs58 = require('bs58').default || require('bs58');
const BN = require('bn.js');
const axios = require('axios'); // 🚀 Axios handles the network traffic seamlessly

const app = express();
const server = http.createServer(app);
const io = new Server(server);
app.use(express.static('public'));

const WSOL_ADDRESS = 'So11111111111111111111111111111111111111112';
const RAYDIUM_AMM_PROGRAM = new PublicKey('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8');

const fetchHeaders = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
    'Accept': 'application/json'
};

let connection = null;
let config = { wallet: null, buyAmount: 0.05, priorityFee: 0.005, slippage: 15, tp: 2.0 }; 
let stats = { balance: 0.0000, trades: 0, wins: 0, losses: 0, pnl: 0.00, history: [] };
let sessions = {};
let sessionCounter = 1;

let scannerActive = false;
let scannerSubscriptionId = null;

function logToSession(id, msg, type = '') { io.emit('session_log', { id, msg, type }); }

function pushUIUpdate() {
    io.emit('update_ui', { balance: stats.balance, trades: stats.trades, wins: stats.wins, losses: stats.losses, pnl: stats.pnl, history: stats.history });
    io.emit('sync_sessions', sessions);
}

async function confirmTransactionWithPolling(txid, latestBlockhash) {
    let confirmed = false;
    let expired = false;
    while (!confirmed && !expired) {
        try {
            const status = await connection.getSignatureStatus(txid, { searchTransactionHistory: true });
            if (status && status.value && (status.value.confirmationStatus === 'confirmed' || status.value.confirmationStatus === 'finalized')) {
                if (status.value.err) throw new Error("Reverted by Network.");
                return true; 
            }
            const currentBlockHeight = await connection.getBlockHeight('confirmed');
            if (currentBlockHeight > latestBlockhash.lastValidBlockHeight) {
                expired = true;
                throw new Error("Transaction Expired (Network Congestion).");
            }
            await new Promise(r => setTimeout(r, 1500)); 
        } catch (e) {
            if (e.message.includes("Reverted") || e.message.includes("Expired")) throw e;
        }
    }
}

io.on('connection', (socket) => {
    pushUIUpdate();

    socket.on('save_settings', async (data) => {
        config.buyAmount = parseFloat(data.buyAmount);
        config.priorityFee = parseFloat(data.priorityFee);
        config.slippage = parseFloat(data.slippage);
        config.tp = parseFloat(data.tp);
        
        try {
            if (!data.rpc || !data.rpc.startsWith('http')) return socket.emit('alert', 'Please enter a valid HTTP RPC URL.');
            
            const rpcUrl = data.rpc.trim();
            const wsUrl = rpcUrl.replace('https://', 'wss://').replace('http://', 'ws://');
            connection = new Connection(rpcUrl, { commitment: 'confirmed', wsEndpoint: wsUrl });

            if (data.pk && data.pk.trim() !== '') {
                const pkString = data.pk.trim();
                let secretKey = pkString.startsWith('[') ? Uint8Array.from(JSON.parse(pkString)) : bs58.decode(pkString);
                config.wallet = Keypair.fromSecretKey(secretKey);
                
                stats.balance = (await connection.getBalance(config.wallet.publicKey)) / 1e9;
                console.log(`\n✅ WALLET CONNECTED`);
                console.log(`💳 Address: ${config.wallet.publicKey.toString()}`);
            }
            pushUIUpdate();
        } catch (e) { console.error(`❌ Config Error: ${e.message}`); }
    });

    socket.on('create_session', () => {
        const id = sessionCounter++;
        sessions[id] = { id, status: 'IDLE', mint: null, tokenName: '', buyAmount: 0 };
        pushUIUpdate();
        logToSession(id, '> Terminal Online. Awaiting Request.', 'log-info');
    });

    socket.on('delete_session', (id) => { delete sessions[id]; pushUIUpdate(); });
    socket.on('sell_session', (data) => { executeJupiterSell(data.id, data.percent); });
    socket.on('panic_sell_all', async () => { Object.values(sessions).filter(s => s.status === 'HOLDING').forEach(s => executeJupiterSell(s.id, 100)); });

    socket.on('toggle_scanner', (state) => {
        if (!connection) return socket.emit('alert', 'RPC Not Configured.');
        scannerActive = state;
        
        if (scannerActive) {
            console.log("🟢 RAYDIUM SCANNER ACTIVE");
            scannerSubscriptionId = connection.onLogs(RAYDIUM_AMM_PROGRAM, async (logs) => {
                if (logs.logs.some(log => log.includes('initialize2') || log.includes('InitializeInstruction2'))) {
                    try {
                        await new Promise(r => setTimeout(r, 2000)); 
                        const txParsed = await connection.getParsedTransaction(logs.signature, { maxSupportedTransactionVersion: 0, commitment: 'confirmed' });
                        let targetMintStr = null;
                        if (txParsed && txParsed.meta) {
                            for (const account of txParsed.transaction.message.accountKeys) {
                                const pkStr = account.pubkey.toString();
                                if (pkStr !== RAYDIUM_AMM_PROGRAM.toString() && pkStr !== '11111111111111111111111111111111' && pkStr !== WSOL_ADDRESS && pkStr !== 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') {
                                    targetMintStr = pkStr;
                                    break;
                                }
                            }
                        }
                        if (targetMintStr) {
                            let idleSessionId = Object.keys(sessions).find(id => sessions[id].status === 'IDLE');
                            if (idleSessionId) {
                                logToSession(idleSessionId, `🚨 NEW RAYDIUM POOL CA: ${targetMintStr}`, 'log-alert');
                                logToSession(idleSessionId, `Copy CA and paste into Target Acquisition box.`, 'log-info');
                            }
                        }
                    } catch(e) {}
                }
            }, 'confirmed');
        } else {
            console.log("🔴 RAYDIUM SCANNER OFFLINE");
            if (scannerSubscriptionId) {
                connection.removeOnLogsListener(scannerSubscriptionId);
                scannerSubscriptionId = null;
            }
        }
    });

    socket.on('fetch_live_data', async (targetMintStr) => {
        try {
            const response = await axios.get(`https://api.dexscreener.com/latest/dex/tokens/${targetMintStr}`, { headers: fetchHeaders });
            const data = response.data;
            
            if (!data.pairs || data.pairs.length === 0) return socket.emit('live_target_data', { error: true });

            const pair = data.pairs.find(p => p.chainId === 'solana') || data.pairs[0];
            const diffMins = Math.round((new Date() - new Date(pair.pairCreatedAt)) / 60000);
            
            socket.emit('live_target_data', {
                mint: targetMintStr,
                name: pair.baseToken.name,
                symbol: pair.baseToken.symbol,
                priceUsd: parseFloat(pair.priceUsd).toFixed(6),
                m5: pair.priceChange?.m5 || 0,
                h1: pair.priceChange?.h1 || 0,
                h6: pair.priceChange?.h6 || 0,
                h24: pair.priceChange?.h24 || 0,
                liquidity: pair.liquidity?.usd ? Math.floor(pair.liquidity.usd).toLocaleString() : 'N/A',
                fdv: pair.fdv ? Math.floor(pair.fdv).toLocaleString() : 'N/A',
                volume5m: pair.volume?.m5 ? Math.floor(pair.volume.m5).toLocaleString() : '0',
                pairAge: diffMins < 60 ? `${diffMins} mins` : `${(diffMins/60).toFixed(1)} hrs`
            });
        } catch (err) { }
    });

    socket.on('execute_snipe', async (targetMintStr) => {
        if (!config.wallet || !connection) return socket.emit('alert', 'Configure settings first!');
        
        let sessionId = Object.keys(sessions).find(id => sessions[id].status === 'IDLE');
        if (!sessionId) {
            sessionId = sessionCounter++;
            sessions[sessionId] = { id: sessionId, status: 'IDLE', mint: null, tokenName: 'Unknown', buyAmount: 0 };
        }

        const session = sessions[sessionId];
        session.status = 'PENDING';
        session.mint = targetMintStr;
        session.buyAmount = config.buyAmount;
        pushUIUpdate();
        logToSession(sessionId, `⚙️ Compiling Jupiter Routing Array...`, 'log-info');

        let txid = null;
        try {
            const amountLamports = Math.floor(config.buyAmount * 1e9);
            const slippageBps = Math.floor(config.slippage * 100);
            
            const quoteUrl = `https://quote-api.jup.ag/v6/quote?inputMint=${WSOL_ADDRESS}&outputMint=${targetMintStr}&amount=${amountLamports}&slippageBps=${slippageBps}`;
            const quoteResponseRaw = await axios.get(quoteUrl, { headers: fetchHeaders });
            const quoteResponse = quoteResponseRaw.data;
            
            if (quoteResponse.error) throw new Error(`Jupiter Route Error: ${quoteResponse.error}`);
            
            logToSession(sessionId, `📦 Payload Constructed. Forwarding...`, 'log-info');

            const swapPayload = {
                quoteResponse,
                userPublicKey: config.wallet.publicKey.toString(),
                wrapAndUnwrapSol: true,
                dynamicComputeUnitLimit: true,
                prioritizationFeeLamports: Math.floor(config.priorityFee * 1e9)
            };

            const swapResponseRaw = await axios.post('https://quote-api.jup.ag/v6/swap', swapPayload, { headers: fetchHeaders });
            const swapResponse = swapResponseRaw.data;

            if (swapResponse.error) throw new Error(`Jupiter Swap Error: ${swapResponse.error}`);

            const swapTransactionBuf = Buffer.from(swapResponse.swapTransaction, 'base64');
            const transaction = VersionedTransaction.deserialize(swapTransactionBuf);
            transaction.sign([config.wallet]);

            const latestBlockhash = await connection.getLatestBlockhash('confirmed');
            txid = await connection.sendRawTransaction(transaction.serialize(), { skipPreflight: true, maxRetries: 5 });
            
            logToSession(sessionId, `🚀 Executing on Chain. Polling Confirmation...`, 'log-info');

            await confirmTransactionWithPolling(txid, latestBlockhash);

            session.status = 'HOLDING'; 
            logToSession(sessionId, `✅ SECURED VIA JUPITER! TXID: ${txid}`, 'log-buy');
            
            try {
                const dsResponse = await axios.get(`https://api.dexscreener.com/latest/dex/tokens/${targetMintStr}`, { headers: fetchHeaders });
                const pair = dsResponse.data.pairs.find(p => p.chainId === 'solana') || dsResponse.data.pairs[0];
                session.tokenName = pair.baseToken.name;
            } catch(e) { session.tokenName = "Token"; }

            stats.trades++;
            stats.balance = (await connection.getBalance(config.wallet.publicKey)) / 1e9;
            pushUIUpdate();

        } catch (err) {
            let errorMsg = err.message;
            if (err.response && err.response.data) {
                errorMsg = typeof err.response.data === 'object' ? JSON.stringify(err.response.data) : err.response.data.toString().substring(0, 100);
            }
            console.error(`\n🚨 JUPITER BUY ERROR: ${errorMsg}`);
            logToSession(sessionId, `❌ FAILED: ${errorMsg.substring(0,40)}...`, 'log-alert');
            session.status = 'IDLE'; 
            pushUIUpdate();
        }
    });
});

async function executeJupiterSell(id, percent) {
    const session = sessions[id];
    if (!session || session.status !== 'HOLDING') return;
    logToSession(id, `⚡ Routing ${percent}% Sell via Jupiter...`, 'log-sell');

    let txid = null;
    try {
        const mintPubkey = new PublicKey(session.mint);
        const userATA = await getAssociatedTokenAddress(mintPubkey, config.wallet.publicKey);
        
        const balanceInfo = await connection.getTokenAccountBalance(userATA);
        let rawTokenAmount = parseFloat(balanceInfo.value.amount);
        if (rawTokenAmount === 0) throw new Error("0 Tokens in wallet");

        let tokenAmtToSell = Math.floor(parseInt(balanceInfo.value.amount) * (percent / 100));
        const slippageBps = Math.floor(config.slippage * 100); 

        const quoteUrl = `https://quote-api.jup.ag/v6/quote?inputMint=${session.mint}&outputMint=${WSOL_ADDRESS}&amount=${tokenAmtToSell}&slippageBps=${slippageBps}`;
        const quoteResponseRaw = await axios.get(quoteUrl, { headers: fetchHeaders });
        const quoteResponse = quoteResponseRaw.data;
        
        if (quoteResponse.error) throw new Error(`Jupiter Route Error: ${quoteResponse.error}`);

        const swapPayload = {
            quoteResponse,
            userPublicKey: config.wallet.publicKey.toString(),
            wrapAndUnwrapSol: true,
            dynamicComputeUnitLimit: true,
            prioritizationFeeLamports: Math.floor(config.priorityFee * 1e9)
        };

        const swapResponseRaw = await axios.post('https://quote-api.jup.ag/v6/swap', swapPayload, { headers: fetchHeaders });
        const swapResponse = swapResponseRaw.data;

        if (swapResponse.error) throw new Error(`Jupiter Swap Error: ${swapResponse.error}`);

        const swapTransactionBuf = Buffer.from(swapResponse.swapTransaction, 'base64');
        const transaction = VersionedTransaction.deserialize(swapTransactionBuf);
        transaction.sign([config.wallet]);

        const latestBlockhash = await connection.getLatestBlockhash('confirmed');
        const txid = await connection.sendRawTransaction(transaction.serialize(), { skipPreflight: true, maxRetries: 5 });
        
        logToSession(id, `⏳ Payload Sent. Polling...`, 'log-info');
        await confirmTransactionWithPolling(txid, latestBlockhash);

        logToSession(id, `✅ LIQUIDATED VIA JUPITER!`, 'log-buy');

        if (percent === 100) {
            const solReceived = quoteResponse.outAmount / 1e9;
            const profit = solReceived - session.buyAmount;
            const pnlPercent = (profit / session.buyAmount) * 100;
            
            stats.pnl += profit;
            const isWin = profit > 0;
            if (isWin) stats.wins++; else stats.losses++;

            stats.history.push({ tokenName: session.tokenName || 'Token', buyAmt: session.buyAmount, sellAmt: solReceived.toFixed(4), pnlPercent: pnlPercent, isWin: isWin });
            if (stats.history.length > 50) stats.history.shift(); 
            session.status = 'CLOSED';
        }
        
        stats.balance = (await connection.getBalance(config.wallet.publicKey)) / 1e9;
        pushUIUpdate();

    } catch (err) { 
        let errorMsg = err.message;
        if (err.response && err.response.data) errorMsg = JSON.stringify(err.response.data);
        console.error(`\n🚨 SELL ERROR: ${errorMsg}`);
        logToSession(id, `❌ SELL FAILED: ${errorMsg.substring(0,30)}...`, 'log-alert');
    }
}

setInterval(() => { io.emit('sync_sessions', sessions); }, 3000);
server.listen(process.env.PORT || 5000, () => console.log(`🌐 JUPITER TERMINAL LIVE ON PORT 5000`));
