import fs from 'fs/promises';
import path from 'path';
import os from 'os';
import { exec } from 'child_process';
import util from 'util';

const execAsync = util.promisify(exec);

const SERVER_URL = 'http://localhost:8990/translate';
const PROCESS_NAME = 'mtranserver';
const LOOPS = 2; // Reduced loops but heavier load per request

async function getPidByName(name: string): Promise<number | null> {
    try {
        const { stdout } = await execAsync(`pgrep -f "${name}"`);
        const pids = stdout.trim().split('\n').map(Number);
        const myPid = process.pid;
        const validPids = pids.filter(p => p !== myPid);
        
        if (validPids.length > 0) {
            return Math.min(...validPids);
        }
    } catch (e) {
    }

    try {
        const { stdout } = await execAsync('ps aux');
        const lines = stdout.split('\n');
        for (const line of lines) {
            if (line.includes(name) && !line.includes('stress_test') && !line.includes(String(process.pid))) {
                const parts = line.trim().split(/\s+/);
                const pid = parseInt(parts[1], 10);
                if (!isNaN(pid)) return pid;
            }
        }
    } catch (e) {
        console.error('Failed to find PID:', e);
    }
    return null;
}

async function getProcessMemory(pid: number) {
    try {
        const { stdout } = await execAsync(`ps -o rss,vsz -p ${pid} | tail -n 1`);
        const [rss, vsz] = stdout.trim().split(/\s+/).map(Number);
        return { 
            rssMB: (rss / 1024).toFixed(2), 
            vszMB: (vsz / 1024).toFixed(2) 
        };
    } catch (e) {
        return { rssMB: '?', vszMB: '?' };
    }
}

async function main() {
    console.log(`Starting stress test against ${SERVER_URL}`);
    
    // 1. Find PID
    let serverPid = await getPidByName(PROCESS_NAME);
    if (!serverPid) {
        console.warn(`Could not find process named "${PROCESS_NAME}". Trying to find generic node/bun process listening on 8990...`);
        try {
             const { stdout } = await execAsync("lsof -t -i:8990");
             const pid = parseInt(stdout.trim(), 10);
             if (!isNaN(pid)) serverPid = pid;
        } catch (e) {
             console.error("Could not find any process on port 8990.");
        }
    }

    if (serverPid) {
        console.log(`Monitoring PID: ${serverPid}`);
    } else {
        console.warn("WARNING: Could not determine Server PID. Memory monitoring will be skipped.");
    }

    // 2. Fetch corpus to Temp Dir
    console.log('Fetching corpus...');
    const corpusUrl = 'https://www.gutenberg.org/cache/epub/1661/pg1661.txt';
    const tempDir = os.tmpdir();
    const corpusPath = path.join(tempDir, `mtranserver_stress_test_${Date.now()}.txt`);
    let corpusText = '';

    try {
        console.log(`Downloading from ${corpusUrl} to ${corpusPath}...`);
        const res = await fetch(corpusUrl);
        if (!res.ok) throw new Error(`Failed to fetch corpus: ${res.statusText}`);
        corpusText = await res.text();
        await fs.writeFile(corpusPath, corpusText);
        console.log(`Corpus loaded. Length: ${corpusText.length} chars.`);

        // 3. Prepare chunks - UPDATED for long texts
        // We want to test the long-text splitting logic, so we need chunks significantly larger than 128 chars.
        // We will join sentences until they reach ~1500-2000 chars.
        const rawSentences = corpusText.split(/[.!?]\s+/).filter(s => s.trim().length > 0);
        const longChunks: string[] = [];
        let currentChunk = "";
        
        for (const s of rawSentences) {
            if (currentChunk.length + s.length < 2000) {
                currentChunk += s + ". ";
            } else {
                longChunks.push(currentChunk);
                currentChunk = s + ". ";
            }
        }
        if (currentChunk.length > 0) longChunks.push(currentChunk);
        
        console.log(`Prepared ${longChunks.length} large chunks (up to 2000 chars each) from ${rawSentences.length} sentences.`);

        // 4. Stress Loop
        let totalChars = 0;
        let iterations = 0;
        let errors = 0;
        const startTime = Date.now();

        let initialMem = {rssMB: '0', vszMB: '0' };
        if (serverPid) {
            initialMem = await getProcessMemory(serverPid);
            console.log(`[Start] Server Memory: RSS=${initialMem.rssMB}MB, VSZ=${initialMem.vszMB}MB`);
        }

        for (let l = 0; l < LOOPS; l++) {
            console.log(`Loop ${l + 1}/${LOOPS}`);
            
            for (const chunk of longChunks) {
                try {
                    const res = await fetch(SERVER_URL, {
                        method: 'POST',
                        headers: { 'Content-Type': 'application/json' },
                        body: JSON.stringify({
                            from: 'en',
                            to: 'zh-Hans',
                            text: chunk
                        })
                    });

                    if (!res.ok) {
                        const errText = await res.text();
                        throw new Error(`HTTP ${res.status}: ${errText}`);
                    }

                    const data = await res.json();
                    if (!data.result) throw new Error('No result in response');

                    totalChars += chunk.length;
                    iterations++;

                    if (iterations % 10 === 0 && serverPid) {
                        const mem = await getProcessMemory(serverPid);
                        process.stdout.write(`\rIter: ${iterations}, TotalChars: ${totalChars}, Errors: ${errors}, ServerMem: RSS=${mem.rssMB}MB `);
                    } else if (iterations % 10 === 0) {
                        process.stdout.write(`\rIter: ${iterations}, TotalChars: ${totalChars}, Errors: ${errors} `);
                    }

                } catch (e: any) {
                    errors++;
                    console.error(`\nTranslation error: ${e.message}`);
                }
            }
            console.log('\nLoop completed.');
        }

        const endTime = Date.now();
        const durationSec = (endTime - startTime) / 1000;
        
        console.log('\n--- Stress Test Finished ---');
        console.log(`Duration: ${durationSec.toFixed(2)}s`);
        console.log(`Total Requests: ${iterations}`);
        console.log(`Total Characters: ${totalChars}`);
        console.log(`Errors: ${errors}`);
        console.log(`Throughput: ${(iterations / durationSec).toFixed(2)} req/s`);
        console.log(`Throughput: ${(totalChars / durationSec).toFixed(2)} chars/s`);

        if (serverPid) {
            const finalMem = await getProcessMemory(serverPid);
            console.log(`Memory Change: RSS ${initialMem.rssMB}MB -> ${finalMem.rssMB}MB`);
        }

    } catch (e) {
        console.error('Error during stress test:', e);
    } finally {
        // Cleanup
        try {
            await fs.unlink(corpusPath);
            console.log(`Deleted temp corpus file: ${corpusPath}`);
        } catch (e) {
            console.error('Failed to delete temp corpus file:', e);
        }
    }
}

main().catch(console.error);
