"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.startServer = startServer;
const express_1 = __importDefault(require("express"));
const cors_1 = __importDefault(require("cors"));
const path_1 = __importDefault(require("path"));
const helmet_1 = __importDefault(require("helmet"));
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
const state_1 = require("../utils/state");
const reasoning_1 = require("../agent/reasoning");
const parser_1 = require("../config/parser");
const tracker_1 = require("./tracker");
const transactionManager_1 = require("../agent/transactionManager");
const limitOrderManager_1 = require("../agent/limitOrderManager");
const pluginManager_1 = require("../system/pluginManager");
const transfer_1 = require("../web3/skills/transfer");
const swapToken_1 = require("../web3/skills/swapToken");
const getBalance_1 = require("../web3/skills/getBalance");
const checkAddress_1 = require("../web3/skills/checkAddress");
const getMyAddress_1 = require("../web3/skills/getMyAddress");
const getPrice_1 = require("../web3/skills/getPrice");
const checkSecurity_1 = require("../web3/skills/checkSecurity");
const checkPortfolio_1 = require("../web3/skills/checkPortfolio");
const marketAnalysis_1 = require("../web3/skills/marketAnalysis");
const createWallet_1 = require("../web3/skills/createWallet");
const limitOrderManager_2 = require("../agent/limitOrderManager");
const skillManager_1 = require("../utils/skillManager");
const bridgeToken_1 = require("../web3/skills/bridgeToken");
const mintNft_1 = require("../web3/skills/mintNft");
const customTx_1 = require("../web3/skills/customTx");
// System Skills
const browseWeb_1 = require("../system/skills/browseWeb");
const executeShell_1 = require("../system/skills/executeShell");
const installSkill_1 = require("../system/skills/installSkill");
const readFile_1 = require("../system/skills/readFile");
const updateSecurityPolicy_1 = require("../system/skills/updateSecurityPolicy");
const writeFile_1 = require("../system/skills/writeFile");
const analyzeDocument_1 = require("../system/skills/analyzeDocument");
const searchWeb_1 = require("../system/skills/searchWeb");
const googleWorkspace_1 = require("../system/skills/googleWorkspace");
const telegram_1 = require("./telegram");
const googleAuthModule_1 = require("./googleAuthModule");
// Initialize Google Auth
(0, googleAuthModule_1.initGoogleAuth)();
const util_1 = __importDefault(require("util"));
// Intercept console.log and console.error
const originalLog = console.log;
const originalError = console.error;
const safeFormat = (a) => typeof a === 'object' ? util_1.default.inspect(a, { depth: 2 }) : String(a);
console.log = function (...args) {
    tracker_1.Tracker.addGatewayLog(args.map(safeFormat).join(' '));
    originalLog.apply(console, args);
};
console.error = function (...args) {
    tracker_1.Tracker.addGatewayLog(args.map(safeFormat).join(' '), { level: 'error' });
    originalError.apply(console, args);
};
const app = (0, express_1.default)();
app.use((0, helmet_1.default)());
app.use((0, cors_1.default)({
    origin: function (origin, callback) {
        if (!origin || /^(http:\/\/(localhost|127\.0\.0\.1):\d+)$/.test(origin)) {
            callback(null, true);
        }
        else {
            callback(new Error('Not allowed by CORS'));
        }
    },
    allowedHeaders: ['Content-Type', 'Authorization', 'x-nyxora-token']
}));
app.use(express_1.default.json());
const apiLimiter = (0, express_rate_limit_1.default)({
    windowMs: 15 * 60 * 1000,
    max: 10000, // Increased from 100 to 10000 to prevent breaking dashboard polling (which polls every 2s)
    message: 'Too many requests from this IP, please try again after 15 minutes'
});
app.use('/api/', apiLimiter);
// API Auth Middleware
app.use('/api', (req, res, next) => {
    // Bypass auth for Google OAuth callback and URLs since they are handled externally or by the browser
    if (req.path.startsWith('/auth/google')) {
        return next();
    }
    const token = req.headers['x-nyxora-token'];
    if (token !== (0, state_1.getSessionToken)()) {
        console.error(`[Auth] Rejected ${req.method} ${req.originalUrl} - Expected: ${(0, state_1.getSessionToken)().substring(0, 8)}... Received: ${token ? token.toString().substring(0, 8) + '...' : 'undefined'}`);
        return res.status(401).json({ error: `Unauthorized: Invalid or missing token.` });
    }
    next();
});
// Serve Static Dashboard
const dashboardPath = path_1.default.resolve(process.cwd(), 'packages/dashboard/dist');
app.use(express_1.default.static(dashboardPath));
app.get('/', (req, res) => {
    res.sendFile(path_1.default.join(dashboardPath, 'index.html'));
});
app.get('/api/history', (req, res) => {
    try {
        const sessionId = req.query.session_id;
        const history = reasoning_1.logger.getHistory(sessionId);
        // Filter out internal system prompt for the frontend
        const cleanHistory = history.filter((msg) => msg.role !== 'system');
        res.json(cleanHistory);
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
app.delete('/api/history', (req, res) => {
    try {
        const sessionId = req.query.session_id;
        reasoning_1.logger.clear(sessionId);
        tracker_1.Tracker.addEvent('memory.cleared');
        res.json({ success: true });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
app.get('/api/sessions', (req, res) => {
    try {
        res.json(reasoning_1.logger.getSessions());
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
app.post('/api/sessions', (req, res) => {
    try {
        const { title } = req.body;
        const id = reasoning_1.logger.createSession(title || 'New Chat');
        res.json({ id });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
app.delete('/api/sessions/:id', (req, res) => {
    try {
        reasoning_1.logger.deleteSession(req.params.id);
        res.json({ success: true });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
app.put('/api/sessions/:id', (req, res) => {
    try {
        const { title } = req.body;
        if (title) {
            reasoning_1.logger.renameSession(req.params.id, title);
        }
        res.json({ success: true });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
app.get('/api/config', (req, res) => {
    try {
        const config = (0, parser_1.loadConfig)();
        res.json(config);
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
app.post('/api/config', (req, res) => {
    try {
        const currentConfig = (0, parser_1.loadConfig)();
        const newConfig = {
            ...currentConfig,
            ...req.body,
            agent: { ...currentConfig.agent, ...(req.body.agent || {}) },
            llm: { ...currentConfig.llm, ...(req.body.llm || {}) },
            web3: { ...currentConfig.web3, ...(req.body.web3 || {}) }
        };
        // Save merged configuration to file
        (0, parser_1.saveConfig)(newConfig);
        tracker_1.Tracker.addEvent('config.updated', { provider: req.body.llm?.provider });
        res.json({ success: true });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
app.get('/api/stats', (req, res) => {
    res.json(tracker_1.Tracker.getStats());
});
app.get('/api/logs', (req, res) => {
    res.json(tracker_1.Tracker.getLogs());
});
app.get('/api/skills', (req, res) => {
    const allSkills = [
        getBalance_1.getBalanceToolDefinition,
        transfer_1.transferToolDefinition,
        getPrice_1.getPriceToolDefinition,
        swapToken_1.swapTokenToolDefinition,
        bridgeToken_1.bridgeTokenToolDefinition,
        mintNft_1.mintNftToolDefinition,
        customTx_1.customTxToolDefinition,
        checkAddress_1.checkAddressToolDefinition,
        getMyAddress_1.getMyAddressToolDefinition,
        checkSecurity_1.checkSecurityToolDefinition,
        checkPortfolio_1.checkPortfolioToolDefinition,
        marketAnalysis_1.marketAnalysisToolDefinition,
        createWallet_1.createWalletToolDefinition,
        limitOrderManager_2.createLimitOrderToolDefinition,
        limitOrderManager_2.listLimitOrdersToolDefinition,
        limitOrderManager_2.cancelLimitOrderToolDefinition
    ];
    const skillsWithStatus = allSkills.map(skill => ({
        ...skill,
        isActive: (0, skillManager_1.isSkillActive)(skill.function.name)
    }));
    res.json(skillsWithStatus);
});
app.get('/api/skills/system', (req, res) => {
    const systemSkills = [
        executeShell_1.runTerminalCommandToolDefinition,
        readFile_1.readLocalFileToolDefinition,
        writeFile_1.writeLocalFileToolDefinition,
        browseWeb_1.browseWebsiteToolDefinition,
        updateSecurityPolicy_1.updateSecurityPolicyToolDefinition,
        installSkill_1.installExternalSkillToolDefinition,
        analyzeDocument_1.analyzeDocumentToolDefinition,
        searchWeb_1.searchWebToolDefinition,
        googleWorkspace_1.readGmailInboxToolDefinition,
        googleWorkspace_1.listCalendarEventsToolDefinition,
        googleWorkspace_1.appendRowToSheetsToolDefinition,
        googleWorkspace_1.readGoogleDocsToolDefinition,
        googleWorkspace_1.readGoogleFormResponsesToolDefinition
    ];
    const skillsWithStatus = systemSkills.map(skill => ({
        ...skill,
        isActive: (0, skillManager_1.isSkillActive)(skill.function.name)
    }));
    res.json(skillsWithStatus);
});
app.post('/api/skills/toggle', (req, res) => {
    const { skillName, active } = req.body;
    if (!skillName || typeof active !== 'boolean') {
        return res.status(400).json({ error: 'Invalid payload' });
    }
    (0, skillManager_1.toggleSkill)(skillName, active);
    res.json({ success: true, skillName, active });
});
// Google Workspace Auth Routes
app.get('/api/auth/google/url', (req, res) => {
    const url = (0, googleAuthModule_1.getAuthUrl)();
    if (!url)
        return res.status(500).json({ error: 'Google Auth not configured' });
    res.json({ url });
});
app.get('/api/auth/google/callback', async (req, res) => {
    const code = req.query.code;
    if (!code)
        return res.status(400).send('No code provided');
    const success = await (0, googleAuthModule_1.processCallback)(code);
    if (success) {
        res.send(`
      <html><body>
      <h2>Authentication Successful!</h2>
      <p>You can close this window and return to the dashboard.</p>
      <script>
        setTimeout(() => window.close(), 2000);
      </script>
      </body></html>
    `);
    }
    else {
        res.status(500).send('Authentication failed');
    }
});
app.get('/api/auth/google/status', async (req, res) => {
    const connected = await (0, googleAuthModule_1.isAuthenticated)();
    res.json({ connected });
});
app.delete('/api/auth/google', async (req, res) => {
    const success = await (0, googleAuthModule_1.logoutGoogle)();
    res.json({ success });
});
app.get('/api/transactions', (req, res) => {
    res.json(transactionManager_1.txManager.getPending());
});
app.post('/api/transactions/:id/approve', async (req, res) => {
    try {
        const id = req.params.id;
        const { sessionId } = req.body || {};
        const tx = transactionManager_1.txManager.getTransaction(id);
        if (!tx || tx.status !== 'pending')
            return res.status(404).json({ error: 'Transaction not found or not pending' });
        transactionManager_1.txManager.updateStatus(id, 'approved', 'Executing on-chain...');
        res.json({ success: true, status: 'processing', message: 'Transaction submitted to background processing.' });
        // Execute in background
        (async () => {
            try {
                let result = '';
                if (tx.type === 'transfer') {
                    result = await (0, transfer_1.executeTransfer)(tx.chainName, tx.details, true);
                }
                else if (tx.type === 'swap') {
                    result = await (0, swapToken_1.executeSwap)(tx.chainName, tx.details, true);
                }
                else if (tx.type === 'bridge') {
                    result = await (0, bridgeToken_1.executeBridge)(tx.chainName, tx.details, true);
                }
                else if (tx.type === 'mint') {
                    result = await (0, mintNft_1.executeMintNft)(tx.chainName, tx.details, true);
                }
                else if (tx.type === 'custom') {
                    result = await (0, customTx_1.executeCustomTx)(tx.chainName, tx.details, true);
                }
                if (result.startsWith('Failed to execute')) {
                    let errorMsg = result;
                    if (result.toLowerCase().includes('insufficient funds') || result.toLowerCase().includes('exceeds the balance')) {
                        errorMsg = "Insufficient Coin/Token balance to cover the transaction amount and Gas (Network Fee).";
                    }
                    else {
                        errorMsg = result.replace('Failed to execute ', '');
                    }
                    transactionManager_1.txManager.updateStatus(id, 'failed', errorMsg);
                    reasoning_1.logger.addEntry({ role: 'tool', name: tx.type === 'swap' ? 'swap_token' : 'transfer_native', content: `Failed: ${errorMsg}` });
                    reasoning_1.logger.addEntry({ role: 'assistant', content: `❌ **Transaction Failed**\n\n${errorMsg}` }, sessionId);
                }
                else {
                    transactionManager_1.txManager.updateStatus(id, 'executed', result);
                    reasoning_1.logger.addEntry({ role: 'tool', name: tx.type === 'swap' ? 'swap_token' : 'transfer_native', content: `Success: ${result}` });
                    reasoning_1.logger.addEntry({ role: 'assistant', content: `✅ **Transaction Executed Successfully**\n\n${result}` }, sessionId);
                }
            }
            catch (err) {
                transactionManager_1.txManager.updateStatus(id, 'failed', err.message);
                reasoning_1.logger.addEntry({ role: 'assistant', content: `❌ **Transaction Failed**\n\n${err.message}` }, sessionId);
            }
        })();
    }
    catch (err) {
        transactionManager_1.txManager.updateStatus(req.params.id, 'failed', err.message);
        res.status(500).json({ error: err.message });
    }
});
app.post('/api/transactions/:id/reject', async (req, res) => {
    try {
        const id = req.params.id;
        const { sessionId } = req.body || {};
        const tx = transactionManager_1.txManager.getTransaction(id);
        if (!tx || tx.status !== 'pending')
            return res.status(404).json({ error: 'Transaction not found or not pending' });
        transactionManager_1.txManager.updateStatus(id, 'rejected');
        reasoning_1.logger.addEntry({ role: 'tool', name: tx.type === 'swap' ? 'swap_token' : 'transfer_native', content: 'User rejected the transaction.' });
        reasoning_1.logger.addEntry({ role: 'assistant', content: `❌ **Transaction Cancelled**\n\nYou have cancelled this transaction.` }, sessionId || 'default');
        res.json({ success: true });
    }
    catch (err) {
        res.status(500).json({ error: err.message });
    }
});
let cachedTrending = null;
let lastTrendingFetch = 0;
app.get('/api/trending', async (req, res) => {
    const now = Date.now();
    if (cachedTrending && now - lastTrendingFetch < 5 * 60 * 1000) {
        return res.json(cachedTrending);
    }
    try {
        const response = await fetch('https://api.coingecko.com/api/v3/search/trending');
        if (response.ok) {
            const data = await response.json();
            const top5 = data.coins.slice(0, 5).map((c) => '$' + c.item.symbol.toUpperCase());
            cachedTrending = top5;
            lastTrendingFetch = now;
            res.json(top5);
        }
        else {
            // Fallback if coingecko rate limits
            if (cachedTrending)
                return res.json(cachedTrending);
            res.status(response.status).json({ error: 'Failed to fetch trending' });
        }
    }
    catch (err) {
        if (cachedTrending)
            return res.json(cachedTrending);
        res.status(500).json({ error: err.message });
    }
});
app.post('/api/chat', async (req, res) => {
    try {
        const { message, session_id } = req.body;
        if (!message) {
            return res.status(400).json({ error: 'Message is required' });
        }
        // Process input (this will automatically add to memory)
        const response = await (0, reasoning_1.processUserInput)(message, 'user', undefined, session_id);
        res.json({ response });
    }
    catch (error) {
        res.status(500).json({ error: error.message });
    }
});
// Fallback for React Router (Single Page Application)
app.use((req, res, next) => {
    if (req.method === 'GET' && !req.path.startsWith('/api')) {
        res.sendFile(path_1.default.join(dashboardPath, 'index.html'));
    }
    else {
        next();
    }
});
function startServer() {
    pluginManager_1.pluginManager.loadPlugins().then(() => {
        console.log(`[PluginManager] Finished loading external skills.`);
    });
    limitOrderManager_1.limitOrderManager.startMonitor();
    const PORT = Number(process.env.PORT || 3000);
    app.listen(PORT, '0.0.0.0', () => {
        console.log(`🤖 Nyxora API Server running on port ${PORT}`);
        // Start the Telegram bot listener
        (0, telegram_1.startTelegramBot)();
    });
}
// Start server if this file is run directly
if (require.main === module) {
    startServer();
}
