#!/usr/bin/env node

import * as process from 'process';
// Suppress all warnings in Node.js
process.env.NODE_NO_WARNINGS = '1';

// This is a valid way to suppress deprecation warnings
process.env.NODE_OPTIONS = '--no-deprecation --no-warnings';

import { initConfig, getHerdClient } from './config';
initConfig();

import { Command } from 'commander';
import { prompt } from 'promptly';
import { spawn, ChildProcess } from 'child_process';
import * as os from 'os';
import * as fs from 'fs';
import * as path from 'path';
import * as chokidar from 'chokidar';
import { execute as executeRun } from '../src/lib/trails/run';
import { execute as executeTest } from '../src/lib/trails/test';
import { execute as executeInit } from '../src/lib/trails/init';
import { HerdClient } from '../src/lib/HerdClient';
import { version } from '../package.json';

const program = new Command();

program
    .name('herd')
    .description('🐂 Herd.garden - A modern web automation platform for developers')
    .version(
        version
    );

// login (and save token to .herdrc)
program
    .command('login')
    .description('🔑 Login to Herd.garden and save your credentials')
    .action(async () => {
        if (process.env.HERD_API_KEY) {
            console.log('✅ You are already logged in!');
            process.exit(0);
        }
        // Ask user to enter their token
        const token = await prompt('🔐 Enter your Herd.garden token: ');
        if (!token) {
            console.error('❌ Error: No token provided');
            process.exit(1);
        }
        // Save token to .herdrc with proper permissions (so that we can read it later)
        try {
            // First we try to login with the token
            const herdClient = new HerdClient({ token });
            await herdClient.initialize();
            fs.writeFileSync(path.join(os.homedir(), '.herdrc'), token, { mode: 0o600 });
            console.log('🎉 Token saved successfully to .herdrc!');
        } catch (error) {
            console.error('❌ Error: Failed to save token to .herdrc', error);
        } finally {
            process.exit(1);
        }
    });

program
    .command('devices')
    .description('🖥️ List all registered devices in your herd')
    .action(async () => {
        const herdClient = getHerdClient();

        console.log('🔍 Fetching your devices...');
        const devices = await herdClient.listDevices();
        // display device[].info as a nice table
        console.table(devices.map(device => ({
            deviceId: device.deviceId,
            name: device.name,
            type: device.type,
            status: device.status,
            lastActive: device.lastActive ? new Date(device.lastActive).toLocaleString() : 'never',
        })));

        console.log(`\n✨ Found ${devices.length} device${devices.length === 1 ? '' : 's'} in your herd.\n`);
    });

// MCP command group
const mcpCommand = program
    .command('mcp')
    .description('🚀 MCP server management and development tools');

mcpCommand
    .command('dev')
    .description('🛠️ Start MCP server in development mode with hot reloading')
    .action(async () => {
        try {
            // Get the current working directory
            const cwd = process.cwd();

            // Read package.json to find the main file
            const packageJsonPath = path.join(cwd, 'package.json');
            if (!fs.existsSync(packageJsonPath)) {
                console.error('❌ Error: package.json not found in the current directory');
                process.exit(1);
            }

            const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
            const mainFile = packageJson.main;

            if (!mainFile) {
                console.error('❌ Error: No "main" field found in package.json');
                process.exit(1);
            }

            // Default ports
            const MCP_PORT = process.env.MCP_PORT || 4999;
            const INSPECTOR_PORT = process.env.INSPECTOR_PORT || 5999;

            let mcpProcess: ChildProcess | null = null;
            let inspectorProcess: ChildProcess | null = null;

            // Function to start the MCP server
            const startMcpServer = () => {
                if (mcpProcess) {
                    mcpProcess.kill();
                }

                console.log('🚀 Starting MCP server...');
                mcpProcess = spawn('npx', ['tsx', mainFile], {
                    stdio: 'inherit',
                    env: {
                        ...process.env,
                        NODE_NO_WARNINGS: '1',
                        NODE_OPTIONS: '--no-deprecation --no-warnings'
                    }
                });

                mcpProcess.on('error', (err) => {
                    console.error('❌ Failed to start MCP server:', err);
                });
            };

            // Function to start the inspector
            const startInspector = () => {
                if (inspectorProcess) {
                    inspectorProcess.kill();
                }

                console.log('🔍 Starting MCP inspector...');
                inspectorProcess = spawn('npx', ['@modelcontextprotocol/inspector'], {
                    stdio: 'inherit',
                    env: {
                        ...process.env,
                        PORT: INSPECTOR_PORT.toString(),
                        NODE_NO_WARNINGS: '1',
                        NODE_OPTIONS: '--no-deprecation --no-warnings'
                    }
                });

                inspectorProcess.on('error', (err) => {
                    console.error('❌ Failed to start MCP inspector:', err);
                });

                // Construct the inspector URL
                const serverUrl = encodeURIComponent(`http://localhost:${MCP_PORT}/sse`);
                console.log(`\n✨ MCP Inspector URL: http://localhost:${INSPECTOR_PORT}/?serverUrl=${serverUrl}\n`);
            };

            // Start both processes
            startMcpServer();
            startInspector();

            // Watch for file changes
            const watcher = chokidar.watch(['./**/*.ts', './**/*.js'], {
                ignored: ['**/node_modules/**', '**/dist/**'],
                persistent: true
            });

            console.log('👀 Watching for file changes...');

            watcher.on('change', (path) => {
                console.log(`\n🔄 File ${path} has been changed. Restarting...`);
                startMcpServer();
                startInspector();
            });

            // Handle process termination
            const cleanup = () => {
                if (mcpProcess) mcpProcess.kill();
                if (inspectorProcess) inspectorProcess.kill();
                console.log('\n👋 Shutting down MCP development server...');
                process.exit(0);
            };

            process.on('SIGINT', cleanup);
            process.on('SIGTERM', cleanup);

            console.log('\n🎉 MCP development environment is ready!');

        } catch (error) {
            console.error('❌ Error:', error);
            process.exit(1);
        }
    });

// Trail command group
const trailCommand = program
    .command('trail')
    .description('🐂 Herd.garden trail management - create, run, and test your trails');

trailCommand
    .command('init')
    .description('🏗️  Create a new trail with everything you need to get started')
    .action(async () => {
        await executeInit();
    });

trailCommand
    .command('run')
    .description('🏃 Run a trail to perform automated actions on the web')
    .option('-t, --trail <trailPath>', 'The trail to run (defaults to current directory)')
    .option('-a, --action <actionName>', 'The action to run')
    .option('-p, --params <params>', 'The params to pass to the action')
    // .option('-d, --device <deviceId>', 'The device to run the trail on')
    .option('-o, --output-only', 'Output only the result, no colors or formatting')
    .action(async (options) => {
        const herdClient = getHerdClient();

        try {
            const trailPath = getTrailPath(options);

            console.log(`🚀 Running trail action ${options.action || 'default'} from ${path.basename(trailPath)}...`);

            // load the trail
            const result = await executeRun(herdClient, trailPath, options.action, options.params ? JSON.parse(options.params) : {});

            if (options.outputOnly) {
                console.log(JSON.stringify(result, null, 2));
            } else {
                // nice colorful output of result
                console.log("\n✨ Result:");
                console.log("\x1b[33m%s\x1b[0m", JSON.stringify(result, null, 2));
                console.log("\n🎉 Trail run completed successfully!");
            }
        } catch (error) {
            console.error('\n❌ Error running trail:', error);
            process.exit(1);
        }
    });

trailCommand
    .command('test')
    .description('🧪 Test a trail to ensure it works correctly')
    .option('-t, --trail <trailPath>', 'The trail to test (defaults to current directory)')
    .option('-s, --selector <selectorId>', 'The selector to test (either this or action must be provided)')
    .option('-a, --action <actionName>', 'The action to test (either this or selector must be provided)')
    .option('-p, --params <params>', 'The params to pass to the action (defaults to manifest params)')
    // .option('-d, --device <deviceId>', 'The device to test the trail on')
    .option('-w, --watch', 'Watch for changes in the trail folder and re-run tests')
    .option('-k, --keep-alive', 'Do not exit on error (defaults to true in watch mode)')
    .action(async (options) => {
        const herdClient = getHerdClient();

        // Always try to register the TypeScript loader since tsx is now a direct dependency
        try {
            // Dynamically import the tsHook module
            const { registerTypeScriptLoader } = await import('../src/lib/trails/tsHook');
            const registered = registerTypeScriptLoader();
            if (registered) {
                console.log('✅ TypeScript loader registered successfully');
            } else {
                console.warn('⚠️ Failed to register TypeScript loader - falling back to JavaScript files if available');
            }
        } catch (e) {
            console.error('❌ Error loading TypeScript hook module', e);
        }

        try {
            const trailPath = getTrailPath(options);

            if (options.watch) {
                console.log(`👀 Watching trail for changes in ${trailPath} ...`);

                // Watch the entire trail directory
                const watcher = chokidar.watch(path.resolve(trailPath), {
                    ignored: ['**/node_modules/**', '**/dist/**'],
                    persistent: true,
                    ignoreInitial: true
                });

                // Run the initial test
                runChildProcess();

                watcher.on('change', (changedPath) => {
                    console.log(`\n🔄 Changes detected in ${changedPath}. Running tests...`);
                    console.clear();
                    runChildProcess();
                });

                function runChildProcess() {
                    // Build the command arguments without the watch flag but with keep-alive
                    const args = ['trail', 'test'];

                    if (options.trail) args.push('-t', options.trail);
                    if (options.selector) args.push('-s', options.selector);
                    if (options.action) args.push('-a', options.action);
                    if (options.params) args.push('-p', options.params);
                    args.push('-k'); // Add keep-alive flag

                    // Use the same command that was used to run this script
                    // If we're using tsx, we need to use it for the child process too
                    const executable = process.argv[0]; // node or other interpreter
                    const scriptPath = process.argv[1]; // path to cli.ts

                    // Check if we're using tsx
                    const usingTsx = process.execArgv.some(arg => arg.includes('tsx'));

                    let command;
                    let commandArgs;

                    if (usingTsx) {
                        // If using tsx, we need to run with tsx
                        command = executable;
                        commandArgs = ['--import', 'tsx', scriptPath, ...args];
                    } else {
                        command = executable;
                        commandArgs = [scriptPath, ...args];
                    }

                    console.log(`🧪 Running: ${command} ${commandArgs.join(' ')}`);

                    const child = spawn(command, commandArgs, {
                        stdio: 'inherit'
                    });

                    child.on('error', (error) => {
                        console.error(`❌ Error running test: ${error.message}`);
                    });
                }
            } else {
                if (options.selector) {
                    console.log(`🧪 Testing selector '${options.selector}' from ${path.basename(trailPath)}...`);
                } else if (options.action) {
                    console.log(`🧪 Testing action '${options.action}' from ${path.basename(trailPath)}...`);
                }

                let result;
                try {
                    if (options.action) {
                        result = await executeTest(herdClient, trailPath, { actionName: options.action });
                    } else if (options.selector) {
                        result = await executeTest(herdClient, trailPath, { selectorId: options.selector });
                    } else {
                        console.error('❌ Error: No action or selector provided');
                        process.exit(1);
                    }

                    if (result && result.status === "success") {
                        console.log("\n✅ Test completed successfully!");
                    } else if (result && result.status === "error" && !options.keepAlive) {
                        console.error("\n❌ Test failed!");
                        process.exit(1);
                    }
                } catch (err: any) {
                    // Enhanced error handling
                    if (err.message && err.message.includes('Unexpected token')) {
                        console.error(`
❌ TypeScript loading error: Unable to parse TypeScript files directly.

Please either:
1. Install tsx globally: npm install -g tsx
2. Run with the --use-tsx flag: herd trail test --use-tsx -a ${options.action || options.selector}
3. Compile your TypeScript files to JavaScript first: tsc

Original error: ${err.message}
                        `);
                    } else {
                        console.error('\n❌ Error testing trail:', err);
                    }

                    if (!options.keepAlive) {
                        process.exit(1);
                    }
                }
            }
        } catch (error) {
            console.error('❌ Error testing trail:', error);
            if (!options.keepAlive) {
                process.exit(1);
            }
        }
    });

// Let's also update the getTrailPath function to have nicer error messages
function getTrailPath(options: any) {
    let trailPath = process.cwd();
    // Did we get a trail name? pick this one otherwise try current directory
    if (options.trail) {
        trailPath = path.join(process.cwd(), options.trail);
    }

    // Check for both TS and JS files
    const hasTypeScriptFiles = fs.existsSync(path.join(trailPath, "actions.ts")) &&
        fs.existsSync(path.join(trailPath, "urls.ts")) &&
        fs.existsSync(path.join(trailPath, "selectors.ts"));

    const hasJavaScriptFiles = fs.existsSync(path.join(trailPath, "actions.js")) &&
        fs.existsSync(path.join(trailPath, "urls.js")) &&
        fs.existsSync(path.join(trailPath, "selectors.js"));

    // are we inside a trail directory?
    if (!hasTypeScriptFiles && !hasJavaScriptFiles) {
        console.error('❌ No valid trail found! Missing required files (actions, urls, selectors)');
        console.log('💡 Tip: Run "herd trail init" to create a new trail, or cd into an existing trail directory');
        process.exit(1);
    }

    return trailPath;
}

program.parse(process.argv);
