#!/usr/bin/env node
/**
 * Upfyn-Code CLI
 * by Thinqmesh Technologies
 *
 * Visual AI coding interface with Upfyn-Canvas for AI coding assistants
 *
 * Commands:
 *   (no args)     - Launch Claude Code with Upfyn branding (default)
 *   start         - Start the local server (web UI)
 *   stop          - Stop a running server
 *   connect       - Connect to hosted server (relay bridge)
 *   config        - View/set configuration (API key, server, etc.)
 *   status        - Show configuration and data locations
 *   update        - Update to the latest version
 *   reset         - Clear local database
 *   uninstall     - Remove all data, config, and slash commands
 *   help          - Show help information
 *   version       - Show version information
 */

import fs from 'fs';
import path from 'path';
import os from 'os';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import { execSync, spawn } from 'child_process';
import {
    c,
    showStyledHelp,
    showStyledStatus,
    showServerBanner,
    showConnectStartup,
    showConnectionBanner,
    showLaunchScreen,
    showConfig,
    logRelayEvent,
    createSpinner,
    playRocketAnimation,
    clearScreen,
} from './cli-ui.js';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Load package.json for version info
const packageJsonPath = path.join(__dirname, '../package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));

const CONFIG_DIR = path.join(os.homedir(), '.upfynai');
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
const PID_FILE = path.join(CONFIG_DIR, 'server.pid');

/** Mask internal Railway URL for display */
function displayServerUrl(url) {
    if (!url || url === 'https://upfynai-code-production.up.railway.app') return 'Upfyn Cloud';
    try { return new URL(url).host; } catch { return url; }
}

// Load environment variables from .env file if it exists
function loadEnvFile() {
    try {
        const envPath = path.join(__dirname, '../.env');
        const envFile = fs.readFileSync(envPath, 'utf8');
        envFile.split('\n').forEach(line => {
            const trimmedLine = line.trim();
            if (trimmedLine && !trimmedLine.startsWith('#')) {
                const [key, ...valueParts] = trimmedLine.split('=');
                if (key && valueParts.length > 0 && !process.env[key]) {
                    process.env[key] = valueParts.join('=').trim();
                }
            }
        });
    } catch (e) {
        // .env file is optional
    }
}

// Load saved config from ~/.upfynai/config.json
function loadConfig() {
    try {
        if (fs.existsSync(CONFIG_FILE)) {
            return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
        }
    } catch (e) { /* ignore */ }
    return {};
}

// Save config to ~/.upfynai/config.json
function saveConfig(config) {
    if (!fs.existsSync(CONFIG_DIR)) {
        fs.mkdirSync(CONFIG_DIR, { recursive: true });
    }
    // Don't save internal keys
    const { _path, ...rest } = config;
    fs.writeFileSync(CONFIG_FILE, JSON.stringify(rest, null, 2));
}

// Get the database path (same logic as db.js)
function getDatabasePath() {
    loadEnvFile();
    return process.env.DATABASE_PATH || path.join(__dirname, 'database', 'auth.db');
}

// Get the installation directory
function getInstallDir() {
    return path.join(__dirname, '..');
}

// --- OS Detection + Claude Binary Discovery ---

function findClaudeBinary() {
    const isWindows = process.platform === 'win32';
    const candidates = isWindows
        ? ['claude.exe', 'claude.cmd', 'claude']
        : ['claude'];

    for (const cmd of candidates) {
        try {
            const whichCmd = isWindows ? 'where' : 'which';
            const result = execSync(`${whichCmd} ${cmd}`, { stdio: 'pipe', encoding: 'utf8' }).trim();
            if (result) return cmd;
        } catch {
            // not found, try next
        }
    }
    return null;
}

// Check if a server is running via PID file
function isServerRunning() {
    if (!fs.existsSync(PID_FILE)) return false;
    try {
        const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
        process.kill(pid, 0); // signal 0 = check if alive
        return pid;
    } catch {
        // Process not running, clean up stale PID file
        try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
        return false;
    }
}

// --- Show status command ---
function showStatus() {
    const config = loadConfig();
    const installDir = getInstallDir();
    const dbPath = getDatabasePath();
    const dbExists = fs.existsSync(dbPath);
    let dbSize = '';
    if (dbExists) {
        const stats = fs.statSync(dbPath);
        dbSize = (stats.size / 1024).toFixed(2) + ' KB';
    }

    const serverPid = isServerRunning();

    showStyledStatus({
        version: packageJson.version,
        installDir,
        dbPath,
        dbExists,
        dbSize,
        port: process.env.PORT || '3001',
        portDefault: !process.env.PORT,
        claudeCli: findClaudeBinary() || null,
        apiKey: config.anthropicApiKey || null,
        serverRunning: serverPid,
    });
}

// Show help
function showHelp() {
    showStyledHelp(packageJson.version);
}

// Show version
function showVersion() {
    console.log(`${packageJson.version}`);
}

// Compare semver versions, returns true if v1 > v2
function isNewerVersion(v1, v2) {
    const parts1 = v1.split('.').map(Number);
    const parts2 = v2.split('.').map(Number);
    for (let i = 0; i < 3; i++) {
        if (parts1[i] > parts2[i]) return true;
        if (parts1[i] < parts2[i]) return false;
    }
    return false;
}

// Check for updates
async function checkForUpdates(silent = false) {
    try {
        const latestVersion = execSync('npm show upfynai-code version', { encoding: 'utf8' }).trim();
        const currentVersion = packageJson.version;

        if (isNewerVersion(latestVersion, currentVersion)) {
            if (!silent) {
                console.log(`\n  ${c.yellow('!')} New version available: ${c.bBright(latestVersion)} ${c.dim(`(current: ${currentVersion})`)}`);
                console.log(`    Run ${c.bright('uc update')} to update\n`);
            }
            return { hasUpdate: true, latestVersion, currentVersion };
        } else if (!silent) {
            console.log(`  ${c.green('OK')} You are on the latest version (${currentVersion})`);
        }
        return { hasUpdate: false, latestVersion, currentVersion };
    } catch (e) {
        if (!silent) {
            console.log(`  ${c.yellow('!')} Could not check for updates`);
        }
        return { hasUpdate: false, error: e.message };
    }
}

// Update the package
async function updatePackage() {
    try {
        const spinner = createSpinner('Checking for updates...');
        spinner.start();

        const { hasUpdate, latestVersion, currentVersion } = await checkForUpdates(true);
        if (!hasUpdate) {
            spinner.stop(`Already on the latest version (${currentVersion})`);
            return;
        }
        spinner.stop(`Update available: ${currentVersion} -> ${latestVersion}`);

        const installSpinner = createSpinner(`Updating to v${latestVersion}...`);
        installSpinner.start();
        execSync('npm update -g upfynai-code', { stdio: 'pipe' });
        installSpinner.stop(`Updated to v${latestVersion}! Restart uc to use the new version.`);
    } catch (e) {
        console.log(`  ${c.red('FAIL')} Update failed: ${e.message}`);
        console.log(`  ${c.dim('Try running manually:')} ${c.bright('npm update -g upfynai-code')}`);
    }
}

// Install slash commands to ~/.claude/commands/
async function installCommands(silent = false) {
    const commandsSource = path.join(__dirname, '..', 'commands');
    const commandsDest = path.join(os.homedir(), '.claude', 'commands');

    if (!fs.existsSync(commandsDest)) {
        fs.mkdirSync(commandsDest, { recursive: true });
    }

    if (!fs.existsSync(commandsSource)) {
        if (!silent) console.log(`  ${c.red('FAIL')} Commands directory not found`);
        return 0;
    }

    const files = fs.readdirSync(commandsSource).filter(f => f.endsWith('.md'));
    let count = 0;

    for (const file of files) {
        fs.copyFileSync(
            path.join(commandsSource, file),
            path.join(commandsDest, file)
        );
        if (!silent) {
            console.log(`  ${c.green('OK')} Installed ${c.violet('/' + file.replace('.md', ''))}`);
        }
        count++;
    }

    if (!silent) {
        console.log(`\n  ${c.bBright(`${count} slash commands installed!`)}`);
        console.log(`  ${c.dim('Available in your AI CLI as /upfynai-*')}\n`);
    }
    return count;
}

// Remove slash commands from ~/.claude/commands/
async function uninstallCommands() {
    const commandsDest = path.join(os.homedir(), '.claude', 'commands');
    const files = fs.existsSync(commandsDest)
        ? fs.readdirSync(commandsDest).filter(f => f.startsWith('upfynai'))
        : [];

    if (files.length === 0) {
        console.log(`  ${c.yellow('!')} No Upfyn-Code commands found to remove`);
        return;
    }

    for (const file of files) {
        fs.unlinkSync(path.join(commandsDest, file));
        console.log(`  ${c.green('OK')} Removed ${c.violet('/' + file.replace('.md', ''))}`);
    }

    console.log(`\n  ${c.bBright(`${files.length} slash commands removed.`)}\n`);
}

// --- Config command ---
function handleConfig(options) {
    const config = loadConfig();

    if (options.apiKey) {
        // Set API key
        config.anthropicApiKey = options.apiKey;
        saveConfig(config);
        console.log(`\n  ${c.green('OK')} ${c.white('Anthropic API key saved')}`);
        console.log(`  ${c.dim('Key:')} sk-ant-...${options.apiKey.slice(-4)}`);
        console.log(`  ${c.dim('Stored in:')} ${CONFIG_FILE}\n`);
        return;
    }

    if (options.clearApiKey) {
        delete config.anthropicApiKey;
        saveConfig(config);
        console.log(`\n  ${c.green('OK')} ${c.white('Anthropic API key removed')}\n`);
        return;
    }

    // Show current config
    showConfig({ ...config, _path: CONFIG_FILE });
}

// --- First-run detection ---
function isFirstRun() {
    return !fs.existsSync(CONFIG_FILE);
}

function showFirstRunWelcome() {
    const W = 58;
    const lines = [];
    lines.push('');
    lines.push(`  ${c.bBright('Welcome to Upfyn-Code!')} ${c.dim('v' + packageJson.version)}`);
    lines.push(`  ${c.dark('='.repeat(W))}`);
    lines.push('');
    lines.push(`  ${c.white('Upfyn-Code gives you a visual AI coding interface')}`);
    lines.push(`  ${c.white('for Claude Code, Cursor, and Codex — all in one place.')}`);
    lines.push('');
    lines.push(`  ${c.bCyan('Quick start:')}`);
    lines.push(`    ${c.bright('1.')} ${c.gray('Run')} ${c.cyan('uc')} ${c.gray('to launch with Claude Code')}`);
    lines.push(`    ${c.bright('2.')} ${c.gray('Run')} ${c.cyan('uc start')} ${c.gray('for just the web UI')}`);
    lines.push(`    ${c.bright('3.')} ${c.gray('Run')} ${c.cyan('uc connect --key <token>')} ${c.gray('to bridge to cli.upfyn.com')}`);
    lines.push('');
    lines.push(`  ${c.bCyan('Optional setup:')}`);
    lines.push(`    ${c.dim('$')} ${c.bright('uc config --api-key sk-ant-xxx')}  ${c.dim('# Save your API key')}`);
    lines.push(`    ${c.dim('$')} ${c.bright('uc install-commands')}              ${c.dim('# Add slash commands')}`);
    lines.push('');
    lines.push(`  ${c.dim('Run')} ${c.bright('uc help')} ${c.dim('for all commands.')}`);
    lines.push(`  ${c.dark('='.repeat(W))}`);
    lines.push('');
    process.stdout.write(lines.join('\n') + '\n');

    // Create config dir so first-run only shows once
    if (!fs.existsSync(CONFIG_DIR)) {
        fs.mkdirSync(CONFIG_DIR, { recursive: true });
    }
    saveConfig({});
}

// --- Launch Interactive (default command) ---
async function launchInteractive() {
    // 0. Show first-run welcome if needed
    const firstRun = isFirstRun();

    // 1. Play rocket animation
    await playRocketAnimation();
    clearScreen();

    // 1.5 Show first-run tips (only once)
    if (firstRun) {
        showFirstRunWelcome();
    }

    // 2. Ensure slash commands are installed (silent)
    await installCommands(true);

    // 3. Find Claude Code binary
    const claudeBin = findClaudeBinary();

    // 4. Load config for relay + API key
    const config = loadConfig();

    // 5. Show launch screen
    showLaunchScreen(packageJson.version, claudeBin, {
        relayStatus: config.relayKey && config.server
            ? `Auto-connecting to ${displayServerUrl(config.server)}`
            : null,
        apiKey: config.anthropicApiKey || null,
    });

    // 6. If Claude Code not found, fall back to server mode
    if (!claudeBin) {
        console.log(`  ${c.dim('Falling back to server mode...')}\n`);
        await startServer();
        return;
    }

    // 7. Build environment for Claude Code
    const childEnv = { ...process.env };
    if (config.anthropicApiKey) {
        childEnv.ANTHROPIC_API_KEY = config.anthropicApiKey;
    }

    // 8. Start the local web UI server in the background
    const port = process.env.PORT || '3001';
    process.env.VITE_IS_PLATFORM = 'true'; // local mode
    startBackgroundServer(port);

    // 9. Start background relay if config exists (for cloud mode)
    if (config.relayKey && config.server) {
        startBackgroundRelay(config);
    }

    // 10. Spawn Claude Code interactively
    const child = spawn(claudeBin, [], {
        stdio: 'inherit',
        cwd: process.cwd(),
        shell: true,
        env: childEnv,
    });

    child.on('exit', (code) => {
        process.exit(code || 0);
    });

    child.on('error', (err) => {
        console.log(`\n  ${c.red('FAIL')} Failed to launch Claude Code: ${err.message}`);
        console.log(`  ${c.dim('Make sure Claude Code is installed:')}`);
        console.log(`    ${c.bright('npm install -g @anthropic-ai/claude-code')}\n`);
        process.exit(1);
    });
}

// --- Background server for local mode ---
function startBackgroundServer(port) {
    // Start the server in the background so the web UI is available
    import('./index.js').then(() => {
        // Server started successfully in background
    }).catch(() => {
        // Server failed to start — user still has Claude Code CLI
    });

    // Open browser after a short delay
    setTimeout(() => {
        const url = `http://localhost:${port}`;
        try {
            const openCmd = process.platform === 'win32' ? 'start'
                : process.platform === 'darwin' ? 'open'
                : 'xdg-open';
            execSync(`${openCmd} ${url}`, { stdio: 'ignore' });
        } catch {
            // Browser open failed — not critical
        }
    }, 2000);
}

// --- Background relay connection ---
function startBackgroundRelay(config) {
    // Import and start relay in background (non-blocking)
    import('./relay-client.js').then(({ connectToServerBackground }) => {
        if (typeof connectToServerBackground === 'function') {
            connectToServerBackground({
                server: config.server,
                key: config.relayKey,
                silent: true,
            });
        }
    }).catch(() => {
        // Relay module not available or no background connect — that's ok
    });
}

// Write PID file so `uc stop` can find and kill the server
function writePidFile() {
    if (!fs.existsSync(CONFIG_DIR)) {
        fs.mkdirSync(CONFIG_DIR, { recursive: true });
    }
    fs.writeFileSync(PID_FILE, String(process.pid));
}

// Remove PID file on exit
function cleanupPidFile() {
    try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
}

// Stop a running server
function stopServer() {
    if (!fs.existsSync(PID_FILE)) {
        console.log(`\n  ${c.yellow('!')} No running server found.`);
        console.log(`  ${c.dim('Start one with:')} ${c.bright('uc start')}\n`);
        return;
    }

    try {
        const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
        process.kill(pid, 'SIGTERM');
        fs.unlinkSync(PID_FILE);
        console.log(`\n  ${c.green('OK')} Server stopped (PID ${pid})\n`);
    } catch (e) {
        // Process might already be dead
        try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
        if (e.code === 'ESRCH') {
            console.log(`\n  ${c.yellow('!')} Server was not running (stale PID file removed)\n`);
        } else {
            console.log(`\n  ${c.red('FAIL')} Could not stop server: ${e.message}\n`);
        }
    }
}

// Uninstall: remove config, database, slash commands
function uninstallApp() {
    console.log(`\n  ${c.bBright('Upfyn-Code — Uninstall')}`);
    console.log(`  ${c.dark('='.repeat(40))}\n`);

    // 1. Stop any running server
    if (fs.existsSync(PID_FILE)) {
        try {
            const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
            process.kill(pid, 'SIGTERM');
            console.log(`  ${c.green('OK')} Stopped running server (PID ${pid})`);
        } catch { /* ignore */ }
    }

    // 2. Remove slash commands
    const commandsDest = path.join(os.homedir(), '.claude', 'commands');
    const cmdsRemoved = [];
    if (fs.existsSync(commandsDest)) {
        const files = fs.readdirSync(commandsDest).filter(f => f.startsWith('upfynai'));
        for (const file of files) {
            fs.unlinkSync(path.join(commandsDest, file));
            cmdsRemoved.push(file);
        }
    }
    if (cmdsRemoved.length > 0) {
        console.log(`  ${c.green('OK')} Removed ${cmdsRemoved.length} slash commands`);
    }

    // 3. Remove config directory (~/.upfynai/)
    if (fs.existsSync(CONFIG_DIR)) {
        fs.rmSync(CONFIG_DIR, { recursive: true, force: true });
        console.log(`  ${c.green('OK')} Removed config directory: ${c.dim(CONFIG_DIR)}`);
    }

    // 4. Remove local database (in install dir)
    const dbPath = path.join(__dirname, 'database', 'auth.db');
    const dbFiles = [dbPath, dbPath + '-wal', dbPath + '-shm'];
    for (const f of dbFiles) {
        if (fs.existsSync(f)) {
            fs.unlinkSync(f);
        }
    }
    if (fs.existsSync(dbPath + '-wal') === false) {
        console.log(`  ${c.green('OK')} Removed local database`);
    }

    console.log('');
    console.log(`  ${c.bBright('Almost done!')} Run this to finish:`);
    console.log(`    ${c.bright('npm uninstall -g upfynai-code')}`);
    console.log('');
}

// Reset: clear database only
function resetApp() {
    console.log(`\n  ${c.bBright('Upfyn-Code — Reset')}`);
    console.log(`  ${c.dark('='.repeat(40))}\n`);

    const dbPath = getDatabasePath();
    const dbFiles = [dbPath, dbPath + '-wal', dbPath + '-shm'];
    let removed = false;
    for (const f of dbFiles) {
        if (fs.existsSync(f)) {
            fs.unlinkSync(f);
            removed = true;
        }
    }

    if (removed) {
        console.log(`  ${c.green('OK')} Database cleared: ${c.dim(dbPath)}`);
        console.log(`  ${c.dim('A fresh database will be created on next server start.')}\n`);
    } else {
        console.log(`  ${c.yellow('!')} No database found at ${c.dim(dbPath)}\n`);
    }
}

// Start the server (self-hosted local mode)
async function startServer() {
    // Check for updates silently on startup
    checkForUpdates(true);

    const port = process.env.PORT || '3001';

    // Auto-detect local mode — set IS_PLATFORM flag
    if (!process.env.RAILWAY_ENVIRONMENT && !process.env.VERCEL && !process.env.FORCE_HOSTED_MODE) {
        process.env.VITE_IS_PLATFORM = 'true';
    }

    // Write PID file so `uc stop` can kill this process
    writePidFile();
    process.on('exit', cleanupPidFile);
    process.on('SIGINT', () => { cleanupPidFile(); process.exit(0); });
    process.on('SIGTERM', () => { cleanupPidFile(); process.exit(0); });

    // Show server banner
    showServerBanner(port, packageJson.version);

    // Detect local agents
    const claudeBin = findClaudeBinary();
    if (claudeBin) {
        console.log(`  ${c.green('OK')} Claude Code detected: ${c.bright(claudeBin)}`);
    } else {
        console.log(`  ${c.yellow('!')} Claude Code not found. Install: ${c.bright('npm i -g @anthropic-ai/claude-code')}`);
    }
    console.log('');

    // Import and run the server
    await import('./index.js');

    // Auto-open browser after server starts (local mode only)
    if (!process.env.RAILWAY_ENVIRONMENT && !process.env.VERCEL) {
        const url = `http://localhost:${port}`;
        setTimeout(() => {
            try {
                const openCmd = process.platform === 'win32' ? 'start'
                    : process.platform === 'darwin' ? 'open'
                    : 'xdg-open';
                execSync(`${openCmd} ${url}`, { stdio: 'ignore' });
                console.log(`  ${c.green('OK')} Opened ${c.cyan(url)} in browser\n`);
            } catch {
                console.log(`  ${c.dim('Open in browser:')} ${c.cyan(url)}\n`);
            }
        }, 1500);
    }
}

// Parse CLI arguments
function parseArgs(args) {
    const parsed = { command: null, options: {} };

    for (let i = 0; i < args.length; i++) {
        const arg = args[i];

        if (arg === '--port' || arg === '-p') {
            parsed.options.port = args[++i];
        } else if (arg.startsWith('--port=')) {
            parsed.options.port = arg.split('=')[1];
        } else if (arg === '--database-path') {
            parsed.options.databasePath = args[++i];
        } else if (arg.startsWith('--database-path=')) {
            parsed.options.databasePath = arg.split('=')[1];
        } else if (arg === '--server') {
            parsed.options.server = args[++i];
        } else if (arg.startsWith('--server=')) {
            parsed.options.server = arg.split('=')[1];
        } else if (arg === '--key') {
            parsed.options.key = args[++i];
        } else if (arg.startsWith('--key=')) {
            parsed.options.key = arg.split('=')[1];
        } else if (arg === '--api-key') {
            parsed.options.apiKey = args[++i];
        } else if (arg.startsWith('--api-key=')) {
            parsed.options.apiKey = arg.split('=')[1];
        } else if (arg === '--clear-api-key') {
            parsed.options.clearApiKey = true;
        } else if (arg === '--help' || arg === '-h') {
            parsed.command = 'help';
        } else if (arg === '--version' || arg === '-v') {
            parsed.command = 'version';
        } else if (!arg.startsWith('-') && !parsed.command) {
            parsed.command = arg;
        }
    }

    // Default command: launch interactive
    if (!parsed.command) {
        parsed.command = 'launch';
    }

    return parsed;
}

// Main CLI handler
async function main() {
    const args = process.argv.slice(2);
    const { command, options } = parseArgs(args);

    // Apply CLI options to environment variables
    if (options.port) {
        process.env.PORT = options.port;
    }
    if (options.databasePath) {
        process.env.DATABASE_PATH = options.databasePath;
    }

    switch (command) {
        case 'launch':
            await launchInteractive();
            break;
        case 'start':
            await startServer();
            break;
        case 'status':
        case 'info':
            showStatus();
            break;
        case 'help':
        case '-h':
        case '--help':
            showHelp();
            break;
        case 'version':
        case '-v':
        case '--version':
            showVersion();
            break;
        case 'update':
            await updatePackage();
            break;
        case 'config':
            handleConfig(options);
            break;
        case 'install-commands':
            await installCommands();
            break;
        case 'uninstall-commands':
            await uninstallCommands();
            break;
        case 'stop':
            stopServer();
            break;
        case 'uninstall':
            uninstallApp();
            break;
        case 'reset':
            resetApp();
            break;
        case 'connect': {
            const { connectToServer } = await import('./relay-client.js');
            await connectToServer({
                server: options.server || args[1],
                key: options.key || args[2],
            });
            break;
        }
        default:
            console.error(`\n  ${c.red('FAIL')} Unknown command: ${command}`);
            console.log(`    Run ${c.bright('"uc help"')} for usage information.\n`);
            process.exit(1);
    }
}

// Run the CLI
main().catch(error => {
    console.error(`\n  ${c.red('FAIL')} Error: ${error.message}`);
    process.exit(1);
});
