import assert from 'node:assert/strict';
import type { BotState } from '../services/apiServer.js';

const ALLOWED_SYMBOL_SIGNALS = new Set(['BUY', 'SELL', 'NONE', 'MIXED']);
const LIFECYCLE_ACTIONS = new Set(['ENTRY', 'EXIT']);
const BOT_ORDER_SOURCES = new Set(['BOT', undefined]);

const hasValue = (value: unknown): boolean => value !== null && value !== undefined;

export function validateWebsocketContract(state: BotState): string[] {
    const errors: string[] = [];
    const entryOrdersByTrade = new Map<string, BotState['orders'][number]>();

    for (const [symbol, snapshot] of Object.entries(state.symbols || {})) {
        if (!ALLOWED_SYMBOL_SIGNALS.has(String(snapshot.signal || ''))) {
            errors.push(`[symbols.${symbol}.signal] Invalid signal: ${snapshot.signal}`);
        }

        for (const [profileId, profileSignal] of Object.entries(snapshot.profileSignals || {})) {
            if (!ALLOWED_SYMBOL_SIGNALS.has(String(profileSignal.signal || ''))) {
                errors.push(`[symbols.${symbol}.profileSignals.${profileId}.signal] Invalid profile signal: ${profileSignal.signal}`);
            }
            if (typeof profileSignal.passed !== 'boolean') {
                errors.push(`[symbols.${symbol}.profileSignals.${profileId}.passed] Expected boolean.`);
            }
        }
    }

    for (const order of state.orders || []) {
        const action = (order.action || '').toUpperCase();

        if (action && !LIFECYCLE_ACTIONS.has(action)) {
            errors.push(`[orders.${order.id}.action] Invalid lifecycle action: ${order.action}`);
        }

        if (action === 'ENTRY' && order.trade_id) {
            const existing = entryOrdersByTrade.get(order.trade_id);
            if (!existing || order.timestamp < existing.timestamp) {
                entryOrdersByTrade.set(order.trade_id, order);
            }
        }

        if (BOT_ORDER_SOURCES.has(order.source) && action && !order.trade_id) {
            errors.push(`[orders.${order.id}] BOT lifecycle order missing trade_id.`);
        }

        if (action === 'EXIT' && !order.trade_id) {
            errors.push(`[orders.${order.id}] EXIT order missing trade_id.`);
        }
    }

    for (const position of state.positions || []) {
        if (position.profileId && !position.tradeId) {
            errors.push(`[positions.${position.id}] Profile position missing tradeId.`);
            continue;
        }

        if (position.tradeId) {
            const entry = entryOrdersByTrade.get(position.tradeId);
            if (!entry) {
                errors.push(`[positions.${position.id}] tradeId ${position.tradeId} has no ENTRY order in payload.`);
                continue;
            }
            if (entry.symbol !== position.symbol) {
                errors.push(`[positions.${position.id}] tradeId ${position.tradeId} symbol mismatch (${entry.symbol} != ${position.symbol}).`);
            }
            if ((entry.profileId || 'global') !== (position.profileId || 'global')) {
                errors.push(`[positions.${position.id}] tradeId ${position.tradeId} profile mismatch.`);
            }
        }
    }

    for (const trade of state.history || []) {
        if ((trade.source || 'BOT') === 'BOT' && !trade.trade_id) {
            errors.push(`[history.${trade.symbol}.${trade.timestamp}] BOT history row missing trade_id.`);
        }
    }

    return errors;
}

function buildFixture(): BotState {
    return {
        symbols: {
            'BTC/USDT': {
                price: 70100,
                change24h: 1.2,
                changeToday: 0.5,
                session: 'NY',
                volatility: 'Medium',
                signal: 'BUY',
                signalTime: Date.now(),
                tradingMode: 'Paper',
                activePosition: null,
                priceHistory: [{ timestamp: Date.now(), price: 70100 }],
                rules: {
                    TrendBiasRule: { passed: true, reason: 'Aligned' }
                },
                profileSignals: {
                    'profile-a': {
                        profileName: 'Scalper',
                        signal: 'BUY',
                        passed: true,
                        reason: 'Rules aligned'
                    }
                },
                indicators: {
                    ema20_1h: 70000
                }
            }
        },
        alerts: [],
        positions: [
            {
                id: 'pos-1',
                symbol: 'BTC/USDT',
                side: 'BUY',
                size: 0.01,
                entryPrice: 70000,
                currentPrice: 70100,
                stopLoss: 69500,
                takeProfit: 71000,
                unrealizedPnl: 1,
                unrealizedPnlPercent: 0.14,
                marketValue: 701,
                profileId: 'profile-a',
                profileName: 'Scalper',
                tradeId: 'TRD-1700000000-profile-a-BTCUSDT'
            }
        ],
        orders: [
            {
                id: 'ord-entry-1',
                symbol: 'BTC/USDT',
                type: 'market',
                side: 'BUY',
                qty: 0.01,
                price: 70000,
                status: 'filled',
                timestamp: Date.now() - 60_000,
                profileId: 'profile-a',
                trade_id: 'TRD-1700000000-profile-a-BTCUSDT',
                action: 'ENTRY',
                source: 'BOT'
            }
        ],
        history: [
            {
                symbol: 'ETH/USDT',
                side: 'SELL',
                entryPrice: 2200,
                exitPrice: 2100,
                size: 0.2,
                pnl: 20,
                pnlPercent: 4.5,
                reason: 'TP Hit',
                timestamp: Date.now() - 3_600_000,
                profileId: 'profile-a',
                trade_id: 'TRD-1699999000-profile-a-ETHUSDT',
                source: 'BOT'
            }
        ],
        settings: {
            executionMode: 'Pro',
            riskPerTrade: 1,
            totalCapital: 10000,
            maxOpenTrades: 3,
            isAlgoEnabled: true,
            enabledRules: ['TrendBiasRule']
        },
        health: {
            tradingLoopHealthy: true,
            tradingLoopLastRun: Date.now(),
            monitorLoopHealthy: true,
            monitorLoopLastRun: Date.now(),
            orderSyncHealthy: true,
            orderSyncLastRun: Date.now(),
            lockContentionCount: 0,
            reconciliationLoopHealthy: true,
            reconciliationLoopLastRun: Date.now(),
            reconciliationMismatchCount: 0,
            reconciliationMissingFromExchange: 0,
            reconciliationMissingInDb: 0,
            reconciliationNoGoTrades: 0,
            reconciliationNoGoReasonCounts: {},
            reconciliationNoGoSamples: [],
            reconciliationIntegrityWatchdogTriggered: false,
            reconciliationLockContentionCount: 0,
            tradingControl: {
                mode: 'RUNNING',
                lastChangedBy: 'system',
                lastChangedAt: Date.now()
            }
        },
        uptime: 120_000,
        accountSnapshot: null,
        orderFailures: [],
        operationalEvents: []
    };
}

function main() {
    const fixture = buildFixture();
    const errors = validateWebsocketContract(fixture);
    assert.equal(errors.length, 0, `Websocket contract violations:\n${errors.join('\n')}`);
    assert.ok(hasValue(fixture.symbols['BTC/USDT'].profileSignals), 'Fixture must include profileSignals payload.');
    console.log('[PASS] Websocket bot-state contract and lifecycle consistency checks passed.');
}

main();
