const { spawn, execSync } = require('child_process');
const { configName, serverConfig, configUpdate, configSave, configExists } = require('../server_config');
const { logDebug, logError, logInfo, logWarn, logFfmpeg } = require('../console');
const checkFFmpeg = require('./checkFFmpeg');
const audioServer = require('./3las.server');

const consoleLogTitle = '[Audio Stream]';

let startupSuccess;

function connectMessage(message) {
    if (!startupSuccess) {
        logInfo(message);
        startupSuccess = true;
    }
}

function checkAudioUtilities() {
    if (process.platform === 'darwin') {
        try {
            execSync('which sox');
        } catch (error) {
            logError(`${consoleLogTitle} Error: SoX ("sox") not found, Please install sox.`);
            process.exit(1);
        }
    } else if (process.platform === 'linux') {
        try {
            execSync('which arecord');
        } catch (error) {
            logError(`${consoleLogTitle} Error: ALSA ("arecord") not found. Please install ALSA utils.`);
            process.exit(1);
        }
    }
}

function buildCommand(ffmpegPath) {
    const inputDevice = serverConfig.audio.audioDevice || 'Stereo Mix';
    const audioChannels = serverConfig.audio.audioChannels || 2;
    const webPort = Number(serverConfig.webserver.webserverPort);

    // Common audio options for FFmpeg
    const baseOptions = {
        flags: ['-fflags', '+nobuffer+flush_packets', '-flags', 'low_delay', '-rtbufsize', '6192', '-probesize', '32'],
        codec: ['-acodec', 'pcm_s16le', '-ar', '48000', '-ac', `${audioChannels}`],
        output: ['-f', 's16le', '-fflags', '+nobuffer+flush_packets', '-packetsize', '384', '-flush_packets', '1', '-bufsize', '960', '-reconnect', '1', '-reconnect_streamed', '1', '-reconnect_delay_max', '10', 'pipe:1']
    };

    // Windows
    if (process.platform === 'win32') {
        logInfo(`${consoleLogTitle} Platform: Windows (win32). Using "dshow" input.`);
        return {
            command: ffmpegPath,
            args: [
                ...baseOptions.flags,
                '-f', 'dshow',
                '-audio_buffer_size', '200',
                '-i', `audio=${inputDevice}`,
                ...baseOptions.codec,
                ...baseOptions.output
            ]
        };
    } else if (process.platform === 'darwin') {
        // macOS
        if (!serverConfig.audio.ffmpeg) {
            logInfo(`${consoleLogTitle} Platform: macOS (darwin) using "coreaudio"`);
            return {
                args: [],
                soxArgs: [
                    '-t', 'coreaudio', `${inputDevice}`,
                    '-b', '32',
                    '-r', '48000',
                    '-c', `${audioChannels}`,
                    '-t', 'raw',
                    '-b', '16',
                    '-r', '48000',
                    '-c', `${audioChannels}`
                    , '-'
                ]
            };
        } else {
            const device = serverConfig.audio.audioDevice;
            return {
                command: ffmpegPath,
                args: [
                    ...baseOptions.flags,
                    '-f', 'avfoundation',
                    '-i', `${device || ':0'}`,
                    ...baseOptions.codec,
                    ...baseOptions.output
                ]
            };
        }
    } else {
        // Linux
        if (!serverConfig.audio.ffmpeg) {
            const prefix = serverConfig.audio.softwareMode ? 'plug' : '';
            const device = `${prefix}${serverConfig.audio.audioDevice}`;
            logInfo(`${consoleLogTitle} Platform: Linux. Using "alsa" input.`);
            return {
                // command not used if arecordArgs are used
                command: `while true; do arecord -D "${device}" -f S16_LE -r 48000 -c ${audioChannels} -t raw; done`,
                args: [],
                arecordArgs: [
                    '-D', device,
                    '-f', 'S16_LE',
                    '-r', '48000',
                    '-c', audioChannels,
                    '-t', 'raw'
                ],
                ffmpegArgs: []
            };
        } else {
            const device = serverConfig.audio.audioDevice;
            return {
                command: ffmpegPath,
                args: [
                    ...baseOptions.flags,
                    '-f', 'alsa',
                    '-i', `${device}`,
                    ...baseOptions.codec,
                    ...baseOptions.output
                ],
                arecordArgs: [],
            };
        }
    }
}

checkFFmpeg().then((ffmpegPath) => {
    if (!serverConfig.audio.ffmpeg) checkAudioUtilities();
    let audioErrorLogged = false;

    logInfo(`${consoleLogTitle} Using`, ffmpegPath === 'ffmpeg' ? 'system-installed FFmpeg' : 'ffmpeg-static');

    if (process.platform !== 'darwin') {
        logInfo(`${consoleLogTitle} Starting audio stream on device: \x1b[35m${serverConfig.audio.audioDevice}\x1b[0m`);
    } else {
        logInfo(`${consoleLogTitle} Starting audio stream on default input device.`);
    }

    if (process.platform === 'win32') {
        // Windows (FFmpeg DirectShow Capture)
        let ffmpeg;
        let restartTimer = null;
        let lastTimestamp = null;
        let lastCheckTime = Date.now();
        let audioErrorLogged = false;
        let staleCount = 0;

        function launchFFmpeg() {
            const commandDef = buildCommand(ffmpegPath);
            let ffmpegArgs = commandDef.args;

            // Apply audio boost if enabled
            if (serverConfig.audio.audioBoost) {
                ffmpegArgs.splice(ffmpegArgs.indexOf('pipe:1'), 0, '-af', 'volume=1.7');
            }

            logDebug(`${consoleLogTitle} Launching FFmpeg with args: ${ffmpegArgs.join(' ')}`);
            ffmpeg = spawn(ffmpegPath, ffmpegArgs, { stdio: ['ignore', 'pipe', 'pipe'] });

            audioServer.waitUntilReady.then(() => {
                audioServer.Server.StdIn = ffmpeg.stdout;
                audioServer.Server.Run();
                connectMessage(`${consoleLogTitle} Connected FFmpeg (capture) \u2192 FFmpeg (process) \u2192 Server.StdIn${serverConfig.audio.audioBoost ? ' (audio boost)' : ''}`);
            });

            ffmpeg.stderr.on('data', (data) => {
                const msg = data.toString();
                logFfmpeg(`[FFmpeg stderr]: ${msg}`);

                if (msg.includes('I/O error') && !audioErrorLogged) {
                    audioErrorLogged = true;
                    logError(`${consoleLogTitle} Audio device "${serverConfig.audio.audioDevice}" failed to start.`);
                    logError('Please start the server with: node . --ffmpegdebug for more info.');
                }

                // Detect frozen timestamp
                const match = msg.match(/time=(\d\d):(\d\d):(\d\d\.\d+)/);
                if (match) {
                    const [_, hh, mm, ss] = match;
                    const totalSec = parseInt(hh) * 3600 + parseInt(mm) * 60 + parseFloat(ss);

                    if (lastTimestamp !== null && totalSec === lastTimestamp) {
                        const now = Date.now();
                        staleCount++;
                        if (staleCount >= 10 && now - lastCheckTime > 10000 && !restartTimer) {
                            restartTimer = setTimeout(() => {
                                restartTimer = null;
                                staleCount = 0;
                                try {
                                    ffmpeg.kill('SIGKILL');
                                } catch (e) {
                                    logWarn(`${consoleLogTitle} Failed to kill FFmpeg process: ${e.message}`);
                                }
                                launchFFmpeg(); // Restart FFmpeg
                            }, 0);
                            setTimeout(() => logWarn(`${consoleLogTitle} FFmpeg appears frozen. Restarting...`), 100);
                        }
                    } else {
                        lastTimestamp = totalSec;
                        lastCheckTime = Date.now();
                        staleCount = 0;
                    }
                }
            });

            ffmpeg.on('exit', (code, signal) => {
                if (signal) {
                    logFfmpeg(`[FFmpeg exited] with signal ${signal}`);
                    logWarn(`${consoleLogTitle} FFmpeg was killed with signal ${signal}`);
                } else {
                    logFfmpeg(`[FFmpeg exited] with code ${code}`);
                    if (code !== 0) {
                        logWarn(`${consoleLogTitle} FFmpeg exited unexpectedly with code ${code}`);
                    }
                }

                // Retry on device fail
                if (audioErrorLogged) {
                    logWarn(`${consoleLogTitle} Retrying in 10 seconds...`);
                    setTimeout(() => {
                        audioErrorLogged = false;
                        launchFFmpeg();
                    }, 10000);
                }
            });
        }
        launchFFmpeg(); // Initial launch
    } else if (process.platform === 'darwin') {
        // macOS (sox --> 3las.server.js --> FFmpeg)
        const commandDef = buildCommand(ffmpegPath);

        // Apply audio boost if enabled and FFmpeg is used
        if (serverConfig.audio.audioBoost && serverConfig.audio.ffmpeg) {
            commandDef.args.splice(commandDef.soxArgs.indexOf('pipe:1'), 0, '-af', 'volume=1.7');
        }

        let currentSox = null;

        process.on('exit', () => {
            if (currentSox) currentSox.kill('SIGINT');
        });

        process.on('SIGINT', () => {
            if (currentSox) currentSox.kill('SIGINT');
            process.exit();
        });

        function startSox() {
            if (!serverConfig.audio.ffmpeg) {
                // Spawn sox
                logDebug(`${consoleLogTitle} Launching sox with args: ${commandDef.soxArgs.join(' ')}`);

                const sox = spawn('sox', commandDef.soxArgs, { stdio: ['ignore', 'pipe', 'pipe'] });
                currentSox = sox;

                audioServer.waitUntilReady.then(() => {
                    audioServer.Server.StdIn = sox.stdout;
                    audioServer.Server.Run();
                    connectMessage(`${consoleLogTitle} Connected SoX \u2192 FFmpeg \u2192 Server.StdIn${serverConfig.audio.audioBoost && serverConfig.audio.ffmpeg ? ' (audio boost)' : ''}`);
                });

                sox.stderr.on('data', (data) => {
                    logFfmpeg(`[sox stderr]: ${data}`);
                });

                sox.on('exit', (code) => {
                    logFfmpeg(`[sox exited] with code ${code}`);
                    if (code !== 0) {
                        setTimeout(startSox, 2000);
                    }
                });
            }
        }

        startSox();

        if (serverConfig.audio.ffmpeg) {
            logDebug(`${consoleLogTitle} Launching FFmpeg with args: ${commandDef.args.join(' ')}`);
            const ffmpeg = spawn(ffmpegPath, commandDef.args, { stdio: ['ignore', 'pipe', 'pipe'] });

            // Pipe FFmpeg output to 3las.server.js
            audioServer.waitUntilReady.then(() => {
                audioServer.Server.StdIn = ffmpeg.stdout;
                audioServer.Server.Run();
                connectMessage(`${consoleLogTitle} Connected FFmpeg stdout \u2192 Server.StdIn${serverConfig.audio.audioBoost ? ' (audio boost)' : ''}`);
            });

            process.on('SIGINT', () => {
                ffmpeg.kill('SIGINT');
                process.exit();
            });

            process.on('exit', () => {
                ffmpeg.kill('SIGINT');
            });

            // FFmpeg stderr handling
            ffmpeg.stderr.on('data', (data) => {
                logFfmpeg(`[FFmpeg stderr]: ${data}`);
            });

            // FFmpeg exit handling
            ffmpeg.on('exit', (code) => {
                logFfmpeg(`[FFmpeg exited] with code ${code}`);
                if (code !== 0) {
                    logWarn(`${consoleLogTitle} FFmpeg exited unexpectedly with code ${code}`);
                }
            });
        }
    } else {
        // Linux (arecord --> 3las.server.js --> FFmpeg)
        const commandDef = buildCommand(ffmpegPath);

        // Apply audio boost if enabled and FFmpeg is used
        if (serverConfig.audio.audioBoost && serverConfig.audio.ffmpeg) {
            commandDef.args.splice(commandDef.args.indexOf('pipe:1'), 0, '-af', 'volume=1.7');
        }

        let currentArecord = null;

        process.on('exit', () => {
            if (currentArecord) currentArecord.kill('SIGINT');
        });

        process.on('SIGINT', () => {
            if (currentArecord) currentArecord.kill('SIGINT');
            process.exit();
        });

        function startArecord() {
            if (!serverConfig.audio.ffmpeg) {
                // Spawn the arecord loop
                logDebug(`${consoleLogTitle} Launching arecord with args: ${commandDef.arecordArgs.join(' ')}`);

                //const arecord = spawn(commandDef.command, { shell: true, stdio: ['ignore', 'pipe', 'pipe'] });
                const arecord = spawn('arecord', commandDef.arecordArgs, { stdio: ['ignore', 'pipe', 'pipe'] });
                currentArecord = arecord;

                audioServer.waitUntilReady.then(() => {
                    audioServer.Server.StdIn = arecord.stdout;
                    audioServer.Server.Run();
                    connectMessage(`${consoleLogTitle} Connected arecord \u2192 FFmpeg \u2192 Server.StdIn${serverConfig.audio.audioBoost && serverConfig.audio.ffmpeg ? ' (audio boost)' : ''}`);
                });

                arecord.stderr.on('data', (data) => {
                    logFfmpeg(`[arecord stderr]: ${data}`);
                });

                arecord.on('exit', (code) => {
                    logFfmpeg(`[arecord exited] with code ${code}`);
                    if (code !== 0) {
                        setTimeout(startArecord, 2000);
                    }
                });
            }
        }

        startArecord();

        if (serverConfig.audio.ffmpeg) {
            logDebug(`${consoleLogTitle} Launching FFmpeg with args: ${commandDef.args.join(' ')}`);
            const ffmpeg = spawn(ffmpegPath, commandDef.args, { stdio: ['ignore', 'pipe', 'pipe'] });

            // Pipe FFmpeg output to 3las.server.js
            audioServer.waitUntilReady.then(() => {
                audioServer.Server.StdIn = ffmpeg.stdout;
                audioServer.Server.Run();
                connectMessage(`${consoleLogTitle} Connected FFmpeg stdout \u2192 Server.StdIn${serverConfig.audio.audioBoost ? ' (audio boost)' : ''}`);
            });

            process.on('SIGINT', () => {
                ffmpeg.kill('SIGINT');
                process.exit();
            });

            process.on('exit', () => {
                ffmpeg.kill('SIGINT');
            });

            // FFmpeg stderr handling
            ffmpeg.stderr.on('data', (data) => {
                logFfmpeg(`[FFmpeg stderr]: ${data}`);
            });

            // FFmpeg exit handling
            ffmpeg.on('exit', (code) => {
                logFfmpeg(`[FFmpeg exited] with code ${code}`);
                if (code !== 0) {
                    logWarn(`${consoleLogTitle} FFmpeg exited unexpectedly with code ${code}`);
                }
            });
        }
    }
}).catch((err) => {
    logError(`${consoleLogTitle} Error: ${err.message}`);
});
