import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { ApiServer } from './src/services/apiServer.js';
import { supabaseService } from './src/services/SupabaseService.js';

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

async function verifyStaticIsolationGuards(): Promise<void> {
    const apiServerSource = fs.readFileSync(path.join(repoRoot, 'src/services/apiServer.ts'), 'utf8');

    assert(!/this\.io\.emit\(/.test(apiServerSource), 'Global websocket broadcasts detected (this.io.emit).');
    assert(!/socket\.emit\('state',\s*this\.state\)/.test(apiServerSource), 'Global runtime state push detected on websocket connect.');
    assert(/private\s+getScopedState\s*\(/.test(apiServerSource), 'Scoped state projector missing.');
    assert(/private\s+emitToConnectedUsers/.test(apiServerSource), 'Tenant-scoped websocket emitter missing.');
}

async function verifyRuntimeIsolationGuards(): Promise<void> {
    const originalStartServer = (ApiServer.prototype as any).startServer;
    (ApiServer.prototype as any).startServer = () => undefined;
    const originalLoadSnapshot = supabaseService.loadLatestBotStateSnapshot.bind(supabaseService);
    (supabaseService as any).loadLatestBotStateSnapshot = async () => null;

    const server = new ApiServer(0);

    try {
        const runtimeState = server.getState();
        runtimeState.symbols = {};
        runtimeState.positions = [];
        runtimeState.orders = [];
        runtimeState.history = [];
        runtimeState.alerts = [];

        server.registerManualTrader('profile-a', { getUserId: () => 'user-a' } as any);
        server.registerManualTrader('profile-b', { getUserId: () => 'user-b' } as any);

        server.updatePositions([
            {
                id: 'pos-a',
                symbol: 'BTC/USD',
                side: 'BUY',
                size: 1,
                entryPrice: 100,
                currentPrice: 101,
                stopLoss: 90,
                takeProfit: 110,
                unrealizedPnl: 1,
                unrealizedPnlPercent: 1,
                marketValue: 101,
                userId: 'user-a',
                profileId: 'profile-a',
                profileName: 'Profile A',
                tradeId: 'TRD-A-1'
            }
        ], 'profile-a');

        server.updatePositions([
            {
                id: 'pos-b',
                symbol: 'BTC/USD',
                side: 'BUY',
                size: 2,
                entryPrice: 100,
                currentPrice: 102,
                stopLoss: 90,
                takeProfit: 110,
                unrealizedPnl: 4,
                unrealizedPnlPercent: 2,
                marketValue: 204,
                userId: 'user-b',
                profileId: 'profile-b',
                profileName: 'Profile B',
                tradeId: 'TRD-B-1'
            }
        ], 'profile-b');

        server.updateOrders([
            {
                id: 'ord-a',
                symbol: 'BTC/USD',
                type: 'Market',
                side: 'BUY',
                qty: 1,
                price: 100,
                status: 'filled',
                timestamp: Date.now(),
                profileId: 'profile-a',
                userId: 'user-a',
                trade_id: 'TRD-A-1',
                action: 'ENTRY',
                source: 'BOT'
            }
        ], 'profile-a');

        server.updateOrders([
            {
                id: 'ord-b',
                symbol: 'BTC/USD',
                type: 'Market',
                side: 'BUY',
                qty: 2,
                price: 100,
                status: 'filled',
                timestamp: Date.now(),
                profileId: 'profile-b',
                userId: 'user-b',
                trade_id: 'TRD-B-1',
                action: 'ENTRY',
                source: 'BOT'
            }
        ], 'profile-b');

        server.addHistory({
            symbol: 'BTC/USD',
            side: 'BUY',
            entryPrice: 100,
            exitPrice: 105,
            size: 1,
            pnl: 5,
            pnlPercent: 5,
            reason: 'target',
            timestamp: Date.now(),
            userId: 'user-a',
            profileId: 'profile-a',
            trade_id: 'TRD-A-1',
            source: 'BOT'
        });

        server.addHistory({
            symbol: 'BTC/USD',
            side: 'BUY',
            entryPrice: 100,
            exitPrice: 95,
            size: 1,
            pnl: -5,
            pnlPercent: -5,
            reason: 'stop',
            timestamp: Date.now(),
            userId: 'user-b',
            profileId: 'profile-b',
            trade_id: 'TRD-B-1',
            source: 'BOT'
        });

        server.addAlert('info', 'BTC/USD', 'alert-a', { userId: 'user-a', profileId: 'profile-a' });
        server.addAlert('info', 'BTC/USD', 'alert-b', { userId: 'user-b', profileId: 'profile-b' });

        server.updateSymbol('BTC/USD', {
            price: 101,
            change24h: 0.5,
            changeToday: 0.2,
            session: 'NY',
            volatility: 'Low',
            signal: 'BUY',
            profileSignals: {
                'profile-a': {
                    profileName: 'Profile A',
                    signal: 'BUY',
                    passed: true,
                    reason: 'ok',
                    rules: {}
                },
                'profile-b': {
                    profileName: 'Profile B',
                    signal: 'SELL',
                    passed: true,
                    reason: 'ok',
                    rules: {}
                }
            },
            activePosition: {
                side: 'BUY',
                entryPrice: 100,
                size: 2,
                stopLoss: 90,
                takeProfit: 110,
                userId: 'user-b',
                profileId: 'profile-b',
                tradeId: 'TRD-B-1',
                profileName: 'Profile B'
            },
            indicators: {
                ema20_1h: 99
            },
            rules: {}
        });
        const userAState = (server as any).getScopedState('user-a', false);
        const userBState = (server as any).getScopedState('user-b', false);

        assert.equal(userAState.positions.length, 1, 'User A should only receive one owned position.');
        assert.equal(userAState.positions[0].profileId, 'profile-a', 'User A received foreign position.');
        assert.equal(userAState.orders.length, 1, 'User A should only receive one owned order.');
        assert.equal(userAState.orders[0].profileId, 'profile-a', 'User A received foreign order.');
        assert.equal(userAState.history.length, 1, 'User A should only receive one owned history row.');
        assert.equal(userAState.history[0].profileId, 'profile-a', 'User A received foreign history row.');
        assert.equal(userAState.alerts.length, 1, 'User A should only receive one owned alert.');
        assert.equal(userAState.alerts[0].profileId, 'profile-a', 'User A received foreign alert.');
        assert.deepEqual(Object.keys(userAState.symbols['BTC/USD'].profileSignals || {}), ['profile-a'], 'User A received foreign profile signal.');
        assert.equal(userAState.symbols['BTC/USD'].activePosition, null, 'User A should not receive User B active symbol position.');

        assert.equal(userBState.positions.length, 1, 'User B should only receive one owned position.');
        assert.equal(userBState.positions[0].profileId, 'profile-b', 'User B received foreign position.');
        assert.equal(userBState.orders.length, 1, 'User B should only receive one owned order.');
        assert.equal(userBState.orders[0].profileId, 'profile-b', 'User B received foreign order.');
        assert.equal(userBState.history.length, 1, 'User B should only receive one owned history row.');
        assert.equal(userBState.history[0].profileId, 'profile-b', 'User B received foreign history row.');
        assert.equal(userBState.alerts.length, 1, 'User B should only receive one owned alert.');
        assert.equal(userBState.alerts[0].profileId, 'profile-b', 'User B received foreign alert.');
        assert.deepEqual(Object.keys(userBState.symbols['BTC/USD'].profileSignals || {}), ['profile-b'], 'User B received foreign profile signal.');
        assert.equal(userBState.symbols['BTC/USD'].activePosition?.profileId, 'profile-b', 'User B should receive owned active symbol position.');

        const symbolStateA = (server as any).getScopedSymbolState(runtimeState.symbols['BTC/USD'], 'user-a');
        assert.deepEqual(Object.keys(symbolStateA.profileSignals || {}), ['profile-a'], 'Symbol endpoint leaked foreign profile signal to User A.');

        const symbolStateB = (server as any).getScopedSymbolState(runtimeState.symbols['BTC/USD'], 'user-b');
        assert.deepEqual(Object.keys(symbolStateB.profileSignals || {}), ['profile-b'], 'Symbol endpoint leaked foreign profile signal to User B.');
    } finally {
        (ApiServer.prototype as any).startServer = originalStartServer;
        (supabaseService as any).loadLatestBotStateSnapshot = originalLoadSnapshot;
    }
}

await verifyStaticIsolationGuards();
await verifyRuntimeIsolationGuards();
console.log('[tenant-isolation] OK: tenant-scoped runtime state guards passed');
