#!/usr/bin/env node
import express from 'express';
import cors from 'cors';
import { EventEmitter } from 'events';
import { Runner } from '../core/runner.js';
import { Logger } from '../utils/logger.js';
import { Command } from 'commander';
import * as readline from 'readline';
import { spawn } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { ConfigManager } from '../utils/configManager.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const program = new Command();
program
    .name('scrapit-agent')
    .description('Local execution agent for Scrapit OS')
    .version('1.0.0')
    .option('-p, --port <number>', 'Port to run the agent on', '4001')
    .option('-u, --auth-uri <uri>', 'Cloud authentication URI', 'http://localhost:4000/api/login')
    .option('-d, --daemon', 'Run agent in background as a daemon')
    .action(async (options) => {
    if (options.daemon && !process.env.__DETACHED__) {
        console.log('🚀 Starting Scrapit Agent in background...');
        const logDir = ConfigManager.getInstance().getLogBaseDir();
        if (!fs.existsSync(logDir))
            fs.mkdirSync(logDir, { recursive: true });
        const out = fs.openSync(path.join(logDir, 'agent.log'), 'a');
        const err = fs.openSync(path.join(logDir, 'agent.err'), 'a');
        const child = spawn(process.argv[0], [...process.argv.slice(1)], {
            detached: true,
            stdio: ['ignore', out, err],
            env: { ...process.env, __DETACHED__: 'true' }
        });
        child.unref();
        process.exit();
    }
    await startAgent(options);
});
program.parse();
async function startAgent(options) {
    const { port, authUri } = options;
    const app = express();
    app.use(cors({
        origin: '*',
        methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
        allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With']
    }));
    // Handle Private Network Access (PNA) preflight requests
    app.use((req, res, next) => {
        if (req.headers['access-control-request-private-network']) {
            res.setHeader('Access-Control-Allow-Private-Network', 'true');
        }
        next();
    });
    app.use(express.json());
    const globalJobEmitter = new EventEmitter();
    let activeRunId = null;
    let isProcessingPreview = false;
    let clients = [];
    let authToken = null;
    let credentials = { email: '', password: '' };
    // Nice ASCII Banner
    const showBanner = () => {
        console.clear();
        console.log(`
\x1b[35m   _____                      _  _     _____                                 
  / ____|                    (_)| |   |  __ \\                                
 | (___   ___ _ __ __ _ _ __  _ | |_  | |__) |   _ _ __  _ __   ___ _ __     
  \\___ \\ / __| '__/ _\` | '_ \\| || __| |  _  / | | | '_ \\| '_ \\ / _ \\ '__|    
  ____) | (__| | | (_| | |_) | || |_  | | \\ \\ |_| | | | | | | |  __/ |       
 |_____/ \\___|_|  \\__,_| .__/|_| \\__| |_|  \\_\\__,_|_| |_|_| |_|\\___|_|       
                       | |                                                   
                       |_|                                                   \x1b[0m
   \x1b[1m\x1b[34m[ SCRAPIT LOCAL AGENT ]\x1b[0m
   --------------------------------------------------
   \x1b[32m✔ Mode:\x1b[0m       Local Preview & Verification
   \x1b[32m✔ Port:\x1b[0m       ${port}
   \x1b[32m✔ Cloud Auth:\x1b[0m ${authUri}
   \x1b[32m✔ Status:\x1b[0m     Ready for interactions
   --------------------------------------------------
        `);
    };
    const askCredentials = () => {
        const rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout
        });
        return new Promise((resolve) => {
            console.log('\x1b[33m🔑 Cloud Authentication Required\x1b[0m');
            rl.question('📧 Email: ', (email) => {
                // Password masking hack for native readline
                const oldWrite = rl.output.write;
                rl.output.write = (data, encoding, callback) => {
                    if (data.toString().includes('🔒 Password: ')) {
                        return oldWrite.call(rl.output, data, encoding, callback);
                    }
                    if (data.toString().includes('\n') || data.toString().includes('\r')) {
                        return oldWrite.call(rl.output, data, encoding, callback);
                    }
                    return oldWrite.call(rl.output, '*', encoding, callback);
                };
                rl.question('🔒 Password: ', (password) => {
                    rl.output.write = oldWrite;
                    credentials = { email, password };
                    rl.close();
                    resolve();
                });
            });
        });
    };
    const authenticate = async () => {
        try {
            Logger.log(`[Auth] Authenticating with ${authUri}...`);
            const response = await fetch(authUri, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(credentials)
            });
            if (!response.ok)
                throw new Error(`Auth failed with status ${response.status}`);
            const data = await response.json();
            authToken = data.token;
            // Inject into environment so framework core picks it up
            process.env.SCRAPIT_TOKEN = authToken;
            process.env.SCRAPIT_API_URL = authUri.replace('/login', '');
            process.env.SCRAPIT_REGISTRY_MODE = 'api';
            Logger.log('\x1b[32m✔ Authentication successful! Token secured.\x1b[0m');
        }
        catch (err) {
            Logger.error(`\x1b[31m✘ Authentication failed: ${err.message}\x1b[0m`);
            if (!process.env.__DETACHED__) {
                console.log('\x1b[33mRetrying authentication...\x1b[0m');
                await askCredentials();
                await authenticate();
            }
            else {
                process.exit(1);
            }
        }
    };
    // Auto-renewal loop
    const startTokenRenewal = () => {
        setInterval(async () => {
            Logger.log('[Auth] Renewing cloud session...');
            await authenticate();
        }, 1000 * 60 * 50); // Every 50 mins
    };
    if (!process.env.__DETACHED__) {
        await askCredentials();
    }
    else {
        // In daemon mode, we assume credentials are in env or config
        if (!process.env.SCRAPIT_EMAIL) {
            console.error('✘ Daemon mode requires SCRAPIT_EMAIL and SCRAPIT_PASSWORD env variables.');
            process.exit(1);
        }
        credentials = { email: process.env.SCRAPIT_EMAIL, password: process.env.SCRAPIT_PASSWORD || '' };
    }
    await authenticate();
    startTokenRenewal();
    showBanner();
    app.get('/api/stream', (req, res) => {
        res.setHeader('Content-Type', 'text/event-stream');
        res.setHeader('Cache-Control', 'no-cache');
        res.setHeader('Connection', 'keep-alive');
        res.flushHeaders();
        const clientId = Date.now();
        const newClient = { id: clientId, res };
        clients.push(newClient);
        req.on('close', () => {
            clients = clients.filter(c => c.id !== clientId);
        });
    });
    const broadcast = (event, data) => {
        const payload = JSON.stringify({ event, data });
        clients.forEach(c => c.res.write(`data: ${payload}\n\n`));
    };
    Logger.emitter.on('log', (data) => broadcast('log', data));
    globalJobEmitter.on('job:started', (data) => { activeRunId = data.runId; broadcast('job:started', data); });
    globalJobEmitter.on('job:step', (data) => broadcast('job:step', data));
    globalJobEmitter.on('job:screenshot', (data) => broadcast('job:screenshot', data));
    globalJobEmitter.on('job:data', (data) => broadcast('job:data', data));
    globalJobEmitter.on('job:completed', (data) => { broadcast('job:completed', data); activeRunId = null; });
    globalJobEmitter.on('job:failed', (data) => { broadcast('job:failed', data); activeRunId = null; });
    globalJobEmitter.on('batch:started', (data) => broadcast('batch:started', data));
    globalJobEmitter.on('batch:completed', (data) => broadcast('batch:completed', data));
    globalJobEmitter.on('batch:failed', (data) => broadcast('batch:failed', data));
    app.post('/api/preview', async (req, res) => {
        if (!authToken)
            return res.status(401).json({ error: 'Unauthorized agent' });
        Logger.log(`[Agent] Received preview request for scenario: ${req.body.name}`);
        const scenario = req.body;
        const overrides = { headless: false, concurrency: 1, isPreview: true };
        const orgId = ConfigManager.getInstance().getOrganizationId();
        Runner.runScenario(scenario, orgId, globalJobEmitter, overrides)
            .catch(err => Logger.error(`[Agent] Preview run failed: ${err.message}`));
        res.json({ message: 'Preview run started locally', status: 'processing' });
    });
    app.post('/api/record', async (req, res) => {
        if (!authToken)
            return res.status(401).json({ error: 'Unauthorized agent' });
        const { url, name, config } = req.body;
        // Dynamic import to avoid circular dependency or pre-mature loading
        const { Recorder } = await import('../recorder/recorder.js');
        // Listen for preview requests during the session
        const previewHandler = (result) => {
            if (isProcessingPreview) {
                Logger.log(`[Agent] Preview already in progress. Ignoring new request for: ${name}`);
                return;
            }
            Logger.log(`[Agent] Auto-triggering preview run for: ${name}`);
            isProcessingPreview = true;
            const overrides = { headless: false, concurrency: 1, isPreview: true };
            const orgId = ConfigManager.getInstance().getOrganizationId();
            Runner.runScenario(result.generated, orgId, globalJobEmitter, overrides)
                .then(() => {
                Logger.log(`[Agent] Auto-preview completed for: ${name}`);
            })
                .catch(err => Logger.error(`[Agent] Auto-preview failed: ${err.message}`))
                .finally(() => {
                isProcessingPreview = false;
            });
        };
        // Clear any stale listeners before adding the new one
        Recorder.emitter.removeAllListeners('preview');
        Recorder.emitter.on('preview', previewHandler);
        broadcast('record:started', { name, url });
        Recorder.start(url, name, config)
            .then(() => {
            Logger.log(`[Agent] Local recording session completed: ${name}`);
            broadcast('record:completed', { name });
        })
            .catch(err => {
            Logger.error(`[Agent] Local recording failed: ${err.message}`);
            broadcast('record:failed', { name, error: err.message });
        })
            .finally(() => {
            Recorder.emitter.off('preview', previewHandler);
        });
        res.json({ message: 'Local recording session launched', status: 'processing' });
    });
    app.get('/api/health', (req, res) => res.json({ status: 'ok', authenticated: !!authToken }));
    app.listen(port, () => { });
}
