#!/usr/bin/env node

const { Command } = require('commander');
const axios = require('axios');
const path = require('path');
const fs = require('fs/promises');
const packageJson = require('./package.json');
const { spawn } = require('child_process');

const DAEMON_BASE_URL = 'http://localhost:9998';

// Uncomment the next line to use localhost for development
// const FRONTEND_BASE_URL ='http://localhost:3001';
const FRONTEND_BASE_URL = 'https://app.repoburg.com';


const program = new Command();


const handleApiError = async (error) => {
    const { default: chalk } = await import('chalk');
    if (error.response) {
        console.error(chalk.red(`Error: ${error.response.status} - ${error.response.statusText}`));
        if (error.response.data && error.response.data.error) {
            console.error(chalk.red.bold('Daemon Error:'), chalk.red(error.response.data.message || error.response.data.error));
        }
    } else if (error.request) {
        console.error(chalk.red('Daemon not reachable. Is it running?'));
        console.error(chalk.yellow(`Attempted to connect to ${DAEMON_BASE_URL}`));
    } else {
        console.error(chalk.red('An unexpected error occurred:'), error.message);
    }
    process.exit(1);
};

program
  .name('repoburg')
  .description('CLI to manage the Repoburg Platform Daemon and services.')
  .version(packageJson.version);

program
    .command('list')
    .alias('ls')
    .description('List all managed repoburg-backend services.')
    .action(async () => {
        const Table = require('cli-table3');
        const { default: chalk } = await import('chalk');
        try {
            const response = await axios.get(`${DAEMON_BASE_URL}/services`);
            const services = response.data;

            const table = new Table({
                head: ['NAME', 'STATUS', 'PORT', 'PID', 'PATH'].map(h => chalk.cyan(h)),
                colWidths: [20, 12, 8, 8, 60]
            });

            if (services.length === 0) {
                console.log(chalk.yellow('No services are currently being managed.'));
                return;
            }

            services.forEach(service => {
                const statusColor = service.status === 'running' ? chalk.green : chalk.yellow;
                table.push([
                    service.name,
                    statusColor(service.status),
                    service.port || '-',
                    service.pid || '-',
                    service.path
                ]);
            });

            console.log(table.toString());
        } catch (error) {
            handleApiError(error);
        }
    });

program
    .command('start [projectPath]')
    .alias('s')
    .description('Start a new repoburg-backend instance for a project.')
    .option('--name <name>', 'Custom name for the instance (enables parallel instances)')
    .action(async (projectPath, options) => {
        const { default: chalk } = await import('chalk');
        const { default: open } = await import('open');
        const { default: ora } = await import('ora');

        const pollForAuth = (timeout = 300000) => { // 5 minutes timeout
            process.stdout.write(chalk.yellow('Waiting for authorization...'));
            const spinner = setInterval(() => process.stdout.write('.'), 1000);

            return new Promise((resolve, reject) => {
                const startTime = Date.now();
                const pollInterval = setInterval(async () => {
                    if (Date.now() - startTime > timeout) {
                        clearInterval(pollInterval);
                        clearInterval(spinner);
                        process.stdout.write('\n');
                        return reject(new Error('Login timed out. Please run `repoburg login` and try again.'));
                    }

                    try {
                        const statusResponse = await axios.get(`${DAEMON_BASE_URL}/auth/status`);
                        if (statusResponse.data.status === 'authorized') {
                            clearInterval(pollInterval);
                            clearInterval(spinner);
                            process.stdout.write('\n');
                            console.log(chalk.green('✓ Authorization successful!'));
                            resolve();
                        }
                    } catch (e) {
                        // ignore connection errors during polling
                    }
                }, 2000);
            });
        };

        const pollForServiceHealth = (port, timeout = 30000) => {
            const spinner = ora(`Waiting for backend on port ${port} to be healthy...`).start();
            return new Promise((resolve, reject) => {
                const startTime = Date.now();
                const pollInterval = setInterval(async () => {
                     if (Date.now() - startTime > timeout) {
                        clearInterval(pollInterval);
                        spinner.fail('Backend health check timed out.');
                        return reject(new Error('Backend health check timed out. Check logs with `repoburg logs <name>`.'));
                    }
                    try {
                        await axios.get(`http://localhost:${port}/health`);
                        clearInterval(pollInterval);
                        spinner.succeed(`Backend is healthy.`);
                        resolve();
                    } catch (e) {
                        // ignore until timeout
                    }
                }, 1000)
            });
        };

        try {
            // 1. Check daemon status. This will fail if the daemon isn't running.
            const authResponse = await axios.get(`${DAEMON_BASE_URL}/auth/status`);

            if (authResponse.data.status !== 'authorized') {
                console.log(chalk.yellow(`Authentication required. Status: ${authResponse.data.status}.`));

                // 2. Initiate login
                console.log(chalk.yellow('Opening your browser to log in...'));
                const initiateResponse = await axios.post(`${DAEMON_BASE_URL}/auth/initiate`);
                const { loginUrl } = initiateResponse.data;

                if (loginUrl) {
                    await open(loginUrl);
                    console.log(chalk.green('✓ Please complete the authentication in your browser.'));
                    await pollForAuth();
                } else {
                    console.error(chalk.red('Could not retrieve login URL from the daemon.'));
                    process.exit(1);
                }
            }

            // 4. Start the service
            console.log(chalk.cyan('Starting backend service...'));
            const targetPath = path.resolve(projectPath || '.');
            const startResponse = await axios.post(`${DAEMON_BASE_URL}/services/start`, { 
                path: targetPath,
                name: options.name 
            });
            const service = startResponse.data;
            console.log(chalk.green(`✓ Repoburg backend for '${service.name}' started on port ${service.port}.`));

            // 5. Wait for service to be healthy
            await pollForServiceHealth(service.port);

            // 6. Open frontend in browser

            const frontendUrl = `${FRONTEND_BASE_URL}?bport=${service.port}`;
            console.log(chalk.cyan(`Opening frontend at ${frontendUrl}`));
            await open(frontendUrl);

        } catch (error) {
            handleApiError(error);
        }
    });

program
    .command('start-live')
    .description('Start the Visual Editor proxy for a live frontend application.')
    .requiredOption('-u, --url <url>', 'The URL of the target application (e.g., http://localhost:5173)')
    .action(async (options) => {
        const { default: chalk } = await import('chalk');

        // 1. Get active backend port from daemon
        let backendPort;
        try {
            const response = await axios.get(`${DAEMON_BASE_URL}/registry/get-active-port`);
            backendPort = response.data.port;
            if (!backendPort) {
                // This will be caught by the catch block
                throw new Error('No active port returned from daemon.');
            }
            console.log(chalk.green(`✓ Found active Repoburg backend on port ${backendPort}`));
        } catch (error) {
            console.error(chalk.red('Could not find an active Repoburg backend service.'));
            console.error(chalk.yellow('Please start a service first with `repoburg start`.'));
            process.exit(1);
        }

        // 2. Spawn the proxy server process
        const proxyPath = path.resolve(__dirname, 'visual-editor-proxy');
        console.log(chalk.cyan(`Starting Visual Editor proxy for: ${options.url}`));

        const child = spawn('npm', ['run', 'start'], {
            cwd: proxyPath,
            stdio: 'inherit', // Pipe output to the parent terminal
            shell: true,
            env: {
                ...process.env,
                TARGET_URL: options.url,
                BACKEND_PORT: backendPort,
            }
        });

        child.on('error', (error) => {
            console.error(chalk.red(`Failed to start Visual Editor proxy:`));
            console.error(error);
            process.exit(1);
        });

        child.on('exit', (code, signal) => {
            if (code !== 0) {
                console.log(chalk.yellow(`Visual Editor proxy process exited with code ${code} and signal ${signal}`));
            }
        });
    });

program
    .command('stop <name>')
    .alias('st')
    .description('Stop a running repoburg-backend instance.')
    .action(async (name) => {
        const { default: chalk } = await import('chalk');
        try {
            await axios.post(`${DAEMON_BASE_URL}/services/${name}/stop`);
            console.log(chalk.green(`✓ Service '${name}' stopped successfully.`));
        } catch (error) {
            handleApiError(error);
        }
    });

program
    .command('restart <name>')
    .alias('rs')
    .description('Restart a repoburg-backend instance.')
    .action(async (name) => {
        const { default: chalk } = await import('chalk');
        try {
            await axios.post(`${DAEMON_BASE_URL}/services/${name}/restart`);
            console.log(chalk.green(`✓ Service '${name}' restarted successfully.`));
        } catch (error) {
            handleApiError(error);
        }
    });

program
    .command('remove <name>')
    .alias('rm')
    .description('Stop and remove a service from management.')
    .action(async (name) => {
        const { default: chalk } = await import('chalk');
        try {
            const response = await axios.delete(`${DAEMON_BASE_URL}/services/${name}`);
            console.log(chalk.green(`✓ ${response.data.message}`));
        } catch (error) {
            handleApiError(error);
        }
    });

program
    .command('remove-all')
    .alias('rma')
    .description('Stop and remove all services from management.')
    .action(async () => {
        const { default: chalk } = await import('chalk');
        try {
            const response = await axios.post(`${DAEMON_BASE_URL}/services/remove-all`);
            const { results } = response.data;

            if (!results || results.length === 0) {
                console.log(chalk.yellow('No services were managed.'));
                return;
            }

            console.log(chalk.green('✓ All services processed:'));

            const Table = require('cli-table3');
            const table = new Table({
                head: ['NAME', 'RESULT'].map(h => chalk.cyan(h)),
                colWidths: [20, 80]
            });

            results.forEach(result => {
                table.push([result.name, result.message]);
            });
            console.log(table.toString());

        } catch (error) {
            handleApiError(error);
        }
    });

program
    .command('logs <name>')
    .description('Display logs for a specific backend instance.')
    .action(async (name) => {
        const { default: chalk } = await import('chalk');
        try {
            const response = await axios.get(`${DAEMON_BASE_URL}/services/${name}/logs`);
            const { out: outLogPath, err: errLogPath } = response.data;

            console.log(chalk.cyan(`--- Logs for ${name} ---`));

            const readAndPrint = async (filePath, logType) => {
                try {
                    const content = await fs.readFile(filePath, 'utf-8');
                    console.log(chalk.bold.yellow(`\n--- ${logType} Log (${filePath}) ---\n`));
                    console.log(content || chalk.gray('(empty)'));
                } catch (e) {
                    if (e.code !== 'ENOENT') {
                        throw e;
                    }
                    console.log(chalk.bold.yellow(`\n--- ${logType} Log (${filePath}) ---\n`));
                    console.log(chalk.gray('(not found)'));
                }
            };

            await readAndPrint(outLogPath, 'Output');
            await readAndPrint(errLogPath, 'Error');

        } catch (error) {
            handleApiError(error);
        }
    });

program
    .command('login')
    .description('Initiate the web-based authentication flow.')
    .action(async () => {
        const { default: chalk } = await import('chalk');
        const { default: open } = await import('open');
        try {
            console.log(chalk.yellow('Opening your browser to log in...'));
            const response = await axios.post(`${DAEMON_BASE_URL}/auth/initiate`);
            const { loginUrl } = response.data;

            if (loginUrl) {
                await open(loginUrl);
                console.log(chalk.green('✓ Please complete the authentication in your browser.'));
            } else {
                console.error(chalk.red('Could not retrieve login URL from the daemon.'));
            }
        } catch (error) {
            handleApiError(error);
        }
    });

program
    .command('logout')
    .description('Remove local authentication credentials.')
    .action(async () => {
        const { default: chalk } = await import('chalk');
        try {
            await axios.post(`${DAEMON_BASE_URL}/auth/logout`);
            console.log(chalk.green('✓ Credentials removed. You have been logged out.'));
        } catch (error) {
            handleApiError(error);
        }
    });

/**
 * Resolves backend port from instance name or current working directory.
 * @param {string|undefined} instanceName - Optional instance name
 * @returns {Promise<number>} - Backend port
 */
const resolveBackendPort = async (instanceName) => {
    const { default: chalk } = await import('chalk');
    
    const servicesResponse = await axios.get(`${DAEMON_BASE_URL}/services`);
    const services = servicesResponse.data;

    if (!services || services.length === 0) {
        console.error(chalk.red('No backend services found.'));
        console.error(chalk.yellow('Start a service first with `repoburg start`.'));
        process.exit(1);
    }

    let service;
    if (instanceName) {
        service = services.find(s => s.name === instanceName);
        if (!service) {
            console.error(chalk.red(`Instance '${instanceName}' not found.`));
            console.error(chalk.yellow('Available instances:'));
            services.forEach(s => console.error(chalk.gray(`  - ${s.name} (${s.status}, port ${s.port})`)));
            process.exit(1);
        }
    } else {
        const cwd = process.cwd();
        service = services.find(s => s.status === 'running' && cwd.startsWith(s.path));
        if (!service) {
            console.error(chalk.red('No running backend service found for current directory.'));
            console.error(chalk.yellow(`Current directory: ${cwd}`));
            console.error(chalk.yellow('Running services:'));
            services.filter(s => s.status === 'running').forEach(s => {
                console.error(chalk.gray(`  - ${s.name} (path: ${s.path}, port ${s.port})`));
            });
            process.exit(1);
        }
    }

    if (service.status !== 'running') {
        console.error(chalk.red(`Instance '${service.name}' is not running (status: ${service.status}).`));
        process.exit(1);
    }

    console.log(chalk.gray(`Using instance '${service.name}' on port ${service.port}`));
    return service.port;
};

program
    .command('export [sessionId] [path]')
    .description('Export a session to a JSON file. Uses active session if sessionId not provided.')
    .option('-i, --instance <name>', 'Backend instance name')
    .action(async (sessionId, exportPath, options) => {
        const { default: chalk } = await import('chalk');
        try {
            const backendPort = await resolveBackendPort(options.instance);

            // If sessionId not provided, get active session
            let targetSessionId = sessionId;
            if (!targetSessionId) {
                const activeResponse = await axios.get(`http://localhost:${backendPort}/sessions/active`);
                if (activeResponse.status === 204 || !activeResponse.data) {
                    console.error(chalk.red('No active session found.'));
                    console.error(chalk.yellow('Specify a session ID or activate a session first.'));
                    process.exit(1);
                }
                targetSessionId = activeResponse.data.id;
                console.log(chalk.gray(`Using active session: ${targetSessionId}`));
            }

            // Determine export path
            const finalPath = exportPath || `./session-export-${targetSessionId}.json`;
            const resolvedPath = path.resolve(finalPath);

            // Export session (get raw text, don't let axios parse JSON)
            const exportResponse = await axios.get(
                `http://localhost:${backendPort}/sessions/${targetSessionId}/export`,
                { responseType: 'text' }
            );
            
            // Write to file directly
            await fs.writeFile(resolvedPath, exportResponse.data);
            console.log(chalk.green(`✓ Session exported to: ${resolvedPath}`));

        } catch (error) {
            handleApiError(error);
        }
    });

program
    .command('import <path>')
    .description('Import a session from a JSON file.')
    .option('-i, --instance <name>', 'Backend instance name')
    .option('-n, --name <name>', 'Name for the imported session (not yet supported)')
    .action(async (importPath, options) => {
        const { default: chalk } = await import('chalk');
        try {
            const backendPort = await resolveBackendPort(options.instance);

            // Resolve and read the file
            const resolvedPath = path.resolve(importPath);
            console.log(chalk.gray(`Reading file: ${resolvedPath}`));
            
            const fileContent = await fs.readFile(resolvedPath, 'utf-8');
            
            // Parse to validate JSON
            let sessionData;
            try {
                sessionData = JSON.parse(fileContent);
            } catch (e) {
                console.error(chalk.red('Invalid JSON file.'));
                process.exit(1);
            }

            // Import session via API
            const FormData = require('form-data');
            const form = new FormData();
            form.append('file', fileContent, {
                filename: path.basename(resolvedPath),
                contentType: 'application/json',
            });

            const importResponse = await axios.post(
                `http://localhost:${backendPort}/sessions/import`,
                form,
                { headers: form.getHeaders() }
            );

            const { session_id, message_count } = importResponse.data;
            console.log(chalk.green(`✓ Session imported successfully!`));
            console.log(chalk.cyan(`  Session ID: ${session_id}`));
            console.log(chalk.cyan(`  Messages: ${message_count}`));

        } catch (error) {
            handleApiError(error);
        }
    });

program.parse(process.argv);