import express, { NextFunction, Request, Response } from 'express';
import { createServer } from 'http';
import { Server, Socket } from 'socket.io';
import cors from 'cors';
import logger from '../utils/logger.js';
import fs from 'fs';
import path from 'path';
import { ManualTrader } from './ManualTrader.js';
import { applyDynamicConfigEntries, config, loadDynamicConfig } from '../config/index.js';
import { AIClient } from './aiClient.js';
import { supabaseService } from './SupabaseService.js';
import { healthTracker, HealthSnapshot, TradingControlSnapshot } from './healthTracker.js';
import { observabilityService } from './observabilityService.js';
import { isTradingAdmin, verifyTradingAccessToken } from './platformAuthService.js';
import { loadGlobalTradingControl, saveGlobalTradingControl } from './tradingControlRepository.js';
import { listDynamicConfigEntries, upsertDynamicConfigEntries } from './dynamicConfigRepository.js';
import {
    deleteTradeProfileForUser,
    ensureDefaultTradeProfileForUser,
    getCurrentUserProfile,
    listAllTradeProfiles,
    listTradeProfilesForUser,
    saveCurrentUserProfile,
    saveTradeProfileForUser,
} from './profileRepository.js';
import { mergeOrderSnapshots, mergePositionSnapshots } from './stateMerge.js';
import { OperationalEvent } from '../domain/operationalEvents.js';
import { runBacktest } from '../backtest/index.js';
import type { BacktestRequest, BacktestTimeframe } from '../backtest/types.js';
import {
    canonicalLifecycleService,
    type CanonicalLifecycleProfileMeta
} from './canonicalLifecycleService.js';

interface AuthenticatedRequest extends Request {
    authUserId?: string;
    authRole?: string;
    authEmail?: string;
    authDisplayName?: string;
    authPlan?: string;
}

interface RateLimitBucket {
    count: number;
    windowStart: number;
}

interface RuntimeHealth {
    tradingLoopRunning: boolean;
    tradingLoopLastStartedAt: number | null;
    tradingLoopLastCompletedAt: number | null;
    tradingLoopLastDurationMs: number;
    reconciliationRunning: boolean;
    reconciliationLastRunAt: number | null;
    reconciliationLastDurationMs: number;
    staleOrderBacklog: number;
    parityMismatchCount: number;
    exchangeConnectivity: 'unknown' | 'healthy' | 'degraded';
    reconciliationMismatchCount?: number;
    reconciliationMissingFromExchange?: number;
    reconciliationMissingInDb?: number;
    reconciliationNoGoTrades?: number;
    reconciliationParityMismatchTrades?: number;
    reconciliationParityQuarantinedTrades?: number;
    reconciliationParityAutoClosedTrades?: number;
    reconciliationParityMaxMismatchNotionalUsd?: number;
    reconciliationParityTotalMismatchNotionalUsd?: number;
    reconciliationIntegrityWatchdogTriggered?: boolean;
    reconciliationFailedProfiles?: number;
    canonicalLifecycleTruncated?: boolean;
    canonicalLifecycleOrderRows?: number;
}

interface TradeAuditEvent {
    event: string;
    userId?: string;
    profileId?: string;
    symbol?: string;
    outcome?: 'accepted' | 'rejected' | 'error';
    reason?: string;
    details?: Record<string, unknown>;
}

interface ChatProfileRuleConfig {
    ruleId: string;
    enabled: boolean;
    params: Record<string, any>;
}

interface ChatProfilePayload {
    id?: string;
    name: string;
    allocated_capital: number;
    risk_per_trade_percent: number;
    symbols: string;
    is_active: boolean;
    strategy_config: {
        rules: ChatProfileRuleConfig[];
        riskLimits: {
            maxDailyLossUsd: number;
            maxOpenTrades: number;
            maxConsecutiveLosses: number;
        };
        execution: {
            orderType: 'market' | 'limit';
            cooldownMinutes: number;
            entryMode: 'both' | 'long_only';
        };
    };
}

interface ChatResponsePayload {
    action: 'create_profile' | 'update_profile' | 'explain';
    profile?: ChatProfilePayload;
    summary: string;
    reasoning: string;
    fallback?: 'local_deterministic';
}

interface AccountSnapshot {
    profileId?: string;
    userId?: string;
    buying_power: number;
    cash: number;
    currency: string;
    timestamp: number;
}

interface OrderFailureRecord {
    profileId?: string;
    userId?: string;
    symbol: string;
    side: 'BUY' | 'SELL';
    qty: number;
    reason: string;
    tradeId?: string;
    subTag?: string;
    timestamp: number;
}

export interface BotState {
    symbols: {
        [symbol: string]: {
            price: number;
            change24h: number;
            changeToday: number;
            session: string;
            volatility: string;
            signal: string;
            signalTime?: number;
            tradingMode?: 'Paper' | 'Live' | 'Alerts';
            activePosition?: {
                side: 'BUY' | 'SELL';
                entryPrice: number;
                size: number;
                stopLoss: number;
                takeProfit: number;
                unrealizedPnl?: number;
                unrealizedPnlPercent?: number;
                marketValue?: number;
                userId?: string;
                profileId?: string;
                profileName?: string;
                tradeId?: string;
            } | null;
            priceHistory: Array<{ timestamp: number; price: number }>;
            rules: {
                [ruleName: string]: {
                    passed: boolean;
                    reason: string;
                    metadata?: any;
                };
            };
            profileSignals?: {
                [profileId: string]: {
                    profileName?: string;
                    signal: string;
                    passed: boolean;
                    reason?: string;
                    execution?: {
                        status: 'EXECUTED' | 'BLOCKED' | 'SKIPPED';
                        code: string;
                        reason: string;
                        orderId?: string;
                    };
                    rules?: {
                        [ruleName: string]: {
                            passed: boolean;
                            reason: string;
                            metadata?: any;
                        };
                    };
                };
            };
            indicators: {
                ema20_1h?: number;
                ema20_15m?: number;
                ema50_4h?: number;
                ema200_4h?: number;
                rsi_1h?: number;
                rsi_15m?: number;
            };
        };
    };
    alerts: Array<{
        timestamp: number;
        type: 'signal' | 'pulse' | 'error' | 'info';
        symbol: string;
        message: string;
        userId?: string;
        profileId?: string;
    }>;
    positions: Array<{
        id: string;
        symbol: string;
        side: 'BUY' | 'SELL';
        size: number;
        entryPrice: number;
        currentPrice: number;
        stopLoss: number;
        takeProfit: number;
        unrealizedPnl: number;
        unrealizedPnlPercent: number;
        marketValue: number;
        userId?: string;
        profileId?: string;
        profileName?: string;
        tradeId?: string;
    }>;
    orders: Array<{
        id: string;
        symbol: string;
        type: string;
        side: string;
        qty: number;
        price: number;
        status: string;
        timestamp: number;
        userId?: string;
        profileId?: string;
        trade_id?: string;
        subTag?: string;
        action?: string;
        source?: 'BOT' | 'MANUAL';
    }>;
    history: Array<{
        symbol: string;
        side: string;
        entryPrice: number;
        exitPrice: number;
        size: number;
        pnl: number;
        pnlPercent: number;
        reason: string;
        timestamp: number;
        userId?: string;
        profileId?: string;
        trade_id?: string;
        source?: 'BOT' | 'MANUAL';
    }>;
    settings: {
        executionMode: string;
        riskPerTrade: number;
        totalCapital: number;
        maxOpenTrades: number;
        isAlgoEnabled: boolean;
        enabledRules: string[];
    };
    health: HealthSnapshot;
    uptime: number;
    accountSnapshot?: AccountSnapshot | null;
    orderFailures: OrderFailureRecord[];
    operationalEvents: OperationalEvent[];
}



export class ApiServer {
    private app = express();
    private httpServer = createServer(this.app);
    private io = new Server(this.httpServer, {
        cors: {
            origin: (origin, callback) => {
                if (this.isCorsOriginAllowed(origin)) {
                    callback(null, true);
                    return;
                }
                callback(new Error(`CORS blocked for origin: ${origin || 'unknown'}`));
            },
            credentials: true
        }
    });

    private state: BotState = {
        symbols: {},
        positions: [],
        alerts: [],
        orders: [],
        history: [],
        settings: {
            executionMode: 'Alerts',
            riskPerTrade: 0.01,
            totalCapital: 1000,
            maxOpenTrades: 3,
            isAlgoEnabled: false,
            enabledRules: []
        },
        health: healthTracker.getSnapshot(),
        uptime: 0,
        accountSnapshot: null,
        orderFailures: [],
        operationalEvents: []
    };

    private accountSnapshotCache: AccountSnapshot[] = [];

    private startTime: number = Date.now();
    private storagePath = path.resolve(process.cwd(), 'bot_state.json');
    private executionManagers: Map<string, ManualTrader> = new Map(); // profileId -> ManualTrader
    private profileOwners: Map<string, string> = new Map(); // profileId -> userId
    private socketsByUser: Map<string, Set<string>> = new Map(); // userId -> socket ids
    private aiClient = new AIClient();
    private profilePositionsList = new Map<string, any[]>();
    private profileOrdersList = new Map<string, any[]>();
    private snapshotOwnerId: string | null = null;
    private snapshotWriteTimer: NodeJS.Timeout | null = null;
    private isSnapshotWriteInFlight = false;
    private snapshotWriteQueued = false;
    private lastSnapshotWriteAt = 0;
    private rateLimitBuckets: Map<string, RateLimitBucket> = new Map();

    private readonly routeRateLimits = {
        trade: { limit: 6, windowMs: 60_000 },
        close: { limit: 10, windowMs: 60_000 },
        chat: { limit: 20, windowMs: 60_000 },
        backtest: { limit: 4, windowMs: 60_000 }
    } as const;
    private stateWriteTimer: NodeJS.Timeout | null = null;
    private isStateWriteInFlight = false;
    private stateWriteQueued = false;
    private lastHealthBroadcastAt = 0;
    private readonly healthBroadcastMinIntervalMs = 1000;
    private canonicalTruncationAlertByScope = new Map<string, number>();
    private runtimeHealth: RuntimeHealth = {
        tradingLoopRunning: false,
        tradingLoopLastStartedAt: null,
        tradingLoopLastCompletedAt: null,
        tradingLoopLastDurationMs: 0,
        reconciliationRunning: false,
        reconciliationLastRunAt: null,
        reconciliationLastDurationMs: 0,
        staleOrderBacklog: 0,
        parityMismatchCount: 0,
        exchangeConnectivity: 'unknown',
        canonicalLifecycleTruncated: false,
        canonicalLifecycleOrderRows: 0
    };

    constructor(private port: number = 5000) {
        healthTracker.subscribeTradingControl((update) => {
            void this.handleTradingControlChanged(update);
        });
        this.loadState();
        void this.restoreStateFromLatestSnapshot();
        this.setupMiddleware();
        this.setupRoutes();
        this.setupSocketHandlers();
        this.subscribeToOperationalEvents();
        this.startServer();
    }

    private subscribeToOperationalEvents() {
        observabilityService.subscribe((event) => {
            this.state.operationalEvents = observabilityService.getEvents();
            this.broadcastOperationalEvent(event);
            // Keep operational events durable across restarts via local + DB snapshots.
            this.saveState();
            this.scheduleSnapshotWrite();
        });
    }

    private broadcastOperationalEvent(event: OperationalEvent) {
        this.emitToConnectedUsers('operational_event', (userId, socket) => {
            if (socket.data.isAdmin) return event;
            return null;
        });
    }

    public registerManualTrader(profileId: string, manager: ManualTrader) {
        this.executionManagers.set(profileId, manager);
        const owner = String(manager.getUserId() || '').trim();
        if (owner) {
            this.profileOwners.set(profileId, owner);
        }
        logger.info(`[API] Registered Manual Trader for profile: ${profileId}`);
    }

    public unregisterManualTrader(profileId: string) {
        if (!profileId) return;

        this.executionManagers.delete(profileId);
        this.profileOwners.delete(profileId);
        this.profilePositionsList.delete(profileId);
        this.profileOrdersList.delete(profileId);

        this.state.positions = mergePositionSnapshots(Array.from(this.profilePositionsList.values()));
        this.state.orders = mergeOrderSnapshots(Array.from(this.profileOrdersList.values()));
        this.broadcastPositionsUpdate();
        this.broadcastOrdersUpdate();
        this.saveState();
        logger.info(`[API] Unregistered Manual Trader for profile: ${profileId}`);
    }

    private getUserRoom(userId: string): string {
        return `user:${userId}`;
    }

    private trackSocket(userId: string, socket: Socket): void {
        const normalizedUserId = String(userId || '').trim();
        if (!normalizedUserId) return;
        const room = this.getUserRoom(normalizedUserId);
        socket.join(room);
        const existing = this.socketsByUser.get(normalizedUserId) || new Set<string>();
        existing.add(socket.id);
        this.socketsByUser.set(normalizedUserId, existing);
    }

    private untrackSocket(userId: string, socketId: string): void {
        const normalizedUserId = String(userId || '').trim();
        if (!normalizedUserId) return;
        const existing = this.socketsByUser.get(normalizedUserId);
        if (!existing) return;
        existing.delete(socketId);
        if (existing.size === 0) {
            this.socketsByUser.delete(normalizedUserId);
            return;
        }
        this.socketsByUser.set(normalizedUserId, existing);
    }

    private resolveProfileOwner(profileId?: string, fallbackUserId?: string): string | null {
        const fallback = String(fallbackUserId || '').trim();
        if (fallback) return fallback;

        const normalizedProfileId = String(profileId || '').trim();
        if (!normalizedProfileId) return null;

        const mappedOwner = this.profileOwners.get(normalizedProfileId);
        if (mappedOwner) return mappedOwner;

        const manager = this.executionManagers.get(normalizedProfileId);
        if (manager) {
            const managerOwner = String(manager.getUserId() || '').trim();
            if (managerOwner) {
                this.profileOwners.set(normalizedProfileId, managerOwner);
                return managerOwner;
            }
        }

        if (normalizedProfileId.startsWith('default-')) {
            return normalizedProfileId.slice('default-'.length);
        }

        if (normalizedProfileId === 'global') {
            return 'global';
        }

        return null;
    }

    private isOwnedByUser(userId: string, recordUserId?: string, profileId?: string): boolean {
        const normalizedUserId = String(userId || '').trim();
        if (!normalizedUserId) return false;
        const directOwner = String(recordUserId || '').trim();
        if (directOwner) return directOwner === normalizedUserId;
        const profileOwner = this.resolveProfileOwner(profileId);
        if (!profileOwner) return false;
        return profileOwner === normalizedUserId;
    }

    private getScopedSymbolState(symbolState: BotState['symbols'][string], userId: string): BotState['symbols'][string] {
        const profileSignals = Object.entries(symbolState.profileSignals || {}).reduce<Record<string, any>>((acc, [profileId, signal]) => {
            if (this.isOwnedByUser(userId, undefined, profileId)) {
                acc[profileId] = signal;
            }
            return acc;
        }, {});

        const activePosition = symbolState.activePosition
            ? (() => {
                const candidate = symbolState.activePosition as any;
                if (!this.isOwnedByUser(userId, candidate.userId, candidate.profileId)) {
                    return null;
                }
                return symbolState.activePosition;
            })()
            : null;

        return {
            ...symbolState,
            activePosition,
            profileSignals
        };
    }

    private getScopedState(userId: string, isAdmin: boolean): BotState {
        const scopedSymbols = Object.entries(this.state.symbols).reduce<BotState['symbols']>((acc, [symbol, symbolState]) => {
            acc[symbol] = this.getScopedSymbolState(symbolState, userId);
            return acc;
        }, {});

        const scopedPositions = this.state.positions.filter((position) =>
            this.isOwnedByUser(userId, position.userId, position.profileId)
        );
        const scopedOrders = this.state.orders.filter((order) =>
            this.isOwnedByUser(userId, order.userId, order.profileId)
        );
        const scopedHistory = this.state.history.filter((trade) =>
            this.isOwnedByUser(userId, trade.userId, trade.profileId)
        );
        const scopedAlerts = this.state.alerts.filter((alert) => {
            const directUser = String(alert.userId || '').trim();
            if (directUser) return directUser === userId;
            const profileOwner = this.resolveProfileOwner(alert.profileId);
            if (profileOwner) return profileOwner === userId;
            return true;
        });
        const scopedOrderFailures = this.state.orderFailures.filter((failure) =>
            this.isOwnedByUser(userId, failure.userId, failure.profileId)
        );
        const latestAssociatedSnapshot = [...this.accountSnapshotCache]
            .reverse()
            .find((snapshot) => this.isOwnedByUser(userId, snapshot.userId, snapshot.profileId));
        const fallbackSnapshot = this.state.accountSnapshot;
        const scopedAccountSnapshot =
            latestAssociatedSnapshot
            || (fallbackSnapshot && (!fallbackSnapshot.profileId || this.isOwnedByUser(userId, fallbackSnapshot.userId, fallbackSnapshot.profileId)) ? fallbackSnapshot : null);

        return {
            ...this.state,
            symbols: scopedSymbols,
            positions: scopedPositions,
            orders: scopedOrders,
            history: scopedHistory,
            alerts: scopedAlerts,
            health: healthTracker.getSnapshot(),
            accountSnapshot: scopedAccountSnapshot,
            orderFailures: scopedOrderFailures,
            operationalEvents: isAdmin ? this.state.operationalEvents : []
        };
    }


    private emitToUser(userId: string, event: string, payload: unknown): void {
        const normalizedUserId = String(userId || '').trim();
        if (!normalizedUserId) return;
        this.io.to(this.getUserRoom(normalizedUserId)).emit(event, payload);
    }

    private emitToConnectedUsers<T>(event: string, payloadBuilder: (userId: string, socket: Socket) => T): void {
        for (const [socketId, socket] of this.io.sockets.sockets) {
            const userId = socket.data.userId;
            if (!userId) continue;
            const payload = payloadBuilder(userId, socket);
            socket.emit(event, payload);
        }
    }

    private broadcastPositionsUpdate(): void {
        this.emitToConnectedUsers('positions_update', (userId) =>
            this.state.positions.filter((position) => this.isOwnedByUser(userId, position.userId, position.profileId))
        );
    }

    private broadcastOrdersUpdate(): void {
        this.emitToConnectedUsers('orders_update', (userId) =>
            this.state.orders.filter((order) => this.isOwnedByUser(userId, order.userId, order.profileId))
        );
    }

    private broadcastHistoryUpdate(trade: BotState['history'][0]): void {
        const owner = this.resolveProfileOwner(trade.profileId, trade.userId);
        if (!owner) return;
        this.emitToUser(owner, 'history_update', trade);
    }

    private broadcastSymbolUpdate(symbol: string): void {
        const symbolState = this.state.symbols[symbol];
        if (!symbolState) return;
        this.emitToConnectedUsers('symbol_update', (userId) => ({
            symbol,
            data: this.getScopedSymbolState(symbolState, userId)
        }));
    }

    private broadcastSettingsUpdate(): void {
        this.emitToConnectedUsers('settings_update', () => this.state.settings);
    }

    private broadcastHealthUpdate(): void {
        this.publishHealthSnapshot({ broadcast: true, force: true });
    }


    private isCorsOriginAllowed(origin?: string): boolean {
        if (!origin) return true;
        return config.ALLOWED_ORIGINS.includes(origin);
    }

    private extractBearerToken(authorizationHeader?: string | string[]): string | null {
        if (!authorizationHeader || typeof authorizationHeader !== 'string') return null;
        const [scheme, token] = authorizationHeader.split(' ');
        if (scheme?.toLowerCase() !== 'bearer' || !token) return null;
        return token.trim();
    }

    private async handleTradingControlChanged(update: TradingControlSnapshot): Promise<void> {
        this.state.health = {
            ...this.state.health,
            tradingControl: update,
        };
        this.broadcastHealthUpdate();
        this.saveState();
        this.scheduleSnapshotWrite();
        await saveGlobalTradingControl(update);
    }

    private requireAuth = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
        const token = this.extractBearerToken(req.headers.authorization);
        if (!token) {
            res.status(401).json({ error: 'Unauthorized: missing bearer token' });
            return;
        }

        const verified = await verifyTradingAccessToken(token);
        if (!verified.userId) {
            res.status(401).json({ error: `Unauthorized: ${verified.error || 'invalid token'}` });
            return;
        }

        (req as AuthenticatedRequest).authUserId = verified.userId;
        (req as AuthenticatedRequest).authRole = verified.role;
        (req as AuthenticatedRequest).authEmail = verified.email;
        (req as AuthenticatedRequest).authDisplayName = verified.displayName;
        (req as AuthenticatedRequest).authPlan = verified.plan;
        next();
    };

    private requireAdmin = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
        const userId = (req as AuthenticatedRequest).authUserId;
        if (!userId) {
            res.status(401).json({ error: 'Unauthorized' });
            return;
        }

        try {
            const isAdmin = await isTradingAdmin(userId, (req as AuthenticatedRequest).authRole);
            if (!isAdmin) {
                res.status(403).json({ error: 'Forbidden: Admin role required' });
                return;
            }

            next();
        } catch (err: any) {
            res.status(500).json({ error: `Internal server error: ${err.message}` });
        }
    };


    private checkRateLimit(userId: string, route: keyof ApiServer['routeRateLimits']): { allowed: boolean; retryAfterMs: number } {
        const { limit, windowMs } = this.routeRateLimits[route];
        const bucketKey = `${route}:${userId}`;
        const now = Date.now();
        const existing = this.rateLimitBuckets.get(bucketKey);

        if (!existing || (now - existing.windowStart) >= windowMs) {
            this.rateLimitBuckets.set(bucketKey, { count: 1, windowStart: now });
            return { allowed: true, retryAfterMs: 0 };
        }

        if (existing.count >= limit) {
            return { allowed: false, retryAfterMs: Math.max(0, windowMs - (now - existing.windowStart)) };
        }

        existing.count += 1;
        this.rateLimitBuckets.set(bucketKey, existing);
        return { allowed: true, retryAfterMs: 0 };
    }

    private enforceRateLimit(req: AuthenticatedRequest, res: Response, route: keyof ApiServer['routeRateLimits']): boolean {
        const userId = req.authUserId;
        if (!userId) {
            res.status(401).json({ success: false, error: 'Unauthorized' });
            return false;
        }

        const { allowed, retryAfterMs } = this.checkRateLimit(userId, route);
        if (!allowed) {
            res.status(429).json({
                success: false,
                error: 'Rate limit exceeded',
                retryAfterMs
            });
            return false;
        }

        return true;
    }

    private getPersistableState() {
        return {
            symbols: this.state.symbols,
            positions: this.state.positions,
            alerts: this.state.alerts,
            orders: this.state.orders,
            history: this.state.history,
            settings: this.state.settings,
            health: {
                tradingControl: this.state.health.tradingControl
            },
            operationalEvents: this.state.operationalEvents
        };
    }


    private getHealthStatus(now: number = Date.now()): 'healthy' | 'degraded' {
        const lastLoopCompletedAt = this.runtimeHealth.tradingLoopLastCompletedAt;
        const maxLoopGapMs = Math.max(config.POLLING_INTERVAL * 2, 120_000);
        if (!lastLoopCompletedAt) return 'degraded';
        if ((now - lastLoopCompletedAt) > maxLoopGapMs) return 'degraded';
        if (this.runtimeHealth.exchangeConnectivity === 'degraded') return 'degraded';
        return 'healthy';
    }

    private async flushStateToDisk(): Promise<void> {
        if (this.isStateWriteInFlight) {
            this.stateWriteQueued = true;
            return;
        }

        this.isStateWriteInFlight = true;
        const stateToSave = this.getPersistableState();
        const serializedState = JSON.stringify(stateToSave, null, 2);
        const tmpPath = `${this.storagePath}.tmp`;
        const backupPath = `${this.storagePath}.bak`;
        try {
            await fs.promises.writeFile(tmpPath, serializedState, 'utf8');
            // Validate serialized snapshot before promoting the file.
            JSON.parse(await fs.promises.readFile(tmpPath, 'utf8'));

            if (fs.existsSync(this.storagePath)) {
                await fs.promises.copyFile(this.storagePath, backupPath);
            }

            try {
                await fs.promises.rename(tmpPath, this.storagePath);
            } catch {
                // Windows fallback when rename replacement is blocked.
                await fs.promises.copyFile(tmpPath, this.storagePath);
                await fs.promises.unlink(tmpPath).catch(() => undefined);
            }
        } catch (error) {
            logger.error('[API] Failed to save state:', error);
        } finally {
            this.isStateWriteInFlight = false;
            if (this.stateWriteQueued) {
                this.stateWriteQueued = false;
                void this.flushStateToDisk();
            }
        }
    }

    private scheduleStateWrite(): void {
        if (this.stateWriteTimer) {
            clearTimeout(this.stateWriteTimer);
        }

        this.stateWriteTimer = setTimeout(() => {
            this.stateWriteTimer = null;
            void this.flushStateToDisk();
        }, 200);
    }

    private auditTradeEvent(evt: TradeAuditEvent): void {
        const payload = {
            ts: new Date().toISOString(),
            ...evt
        };
        logger.info(`[AUDIT] ${JSON.stringify(payload)}`);
    }

    private buildLocalChatFallback(message: string, context: any[]): ChatResponsePayload {
        const lower = String(message || '').toLowerCase();
        const asksForExplain = /(what|how|why|help|explain|suggest)/i.test(lower)
            && !/(create|build|make|generate|new profile|strategy|setup|configure|update|modify)/i.test(lower);
        if (asksForExplain) {
            return {
                action: 'explain',
                summary: 'AI provider is currently unavailable. A local fallback can still generate deterministic profile configurations.',
                reasoning: 'Use prompts that include risk appetite, symbols, capital, and whether you want long-only or both sides.'
            };
        }

        const symbols = this.extractSymbolsFromMessage(message);
        const entryMode: 'both' | 'long_only' = /(long[\s_-]*only|buy[\s_-]*only)/i.test(lower) ? 'long_only' : 'both';
        const orderType: 'market' | 'limit' = /\blimit\b/i.test(lower) ? 'limit' : 'market';
        const cooldownMinutes = this.extractCooldownMinutes(message);
        const allocatedCapital = this.extractCapital(message);
        const { riskPerTradePercent, riskTier } = this.extractRiskProfile(message);
        const aiRuleEnabled = /\bai\b|\bsentiment\b|\bllm\b/i.test(lower);
        const sessions = this.extractSessions(message);
        const profileName = this.buildFallbackProfileName(lower);
        const isActive = !/\b(inactive|paused|pause|draft)\b/i.test(lower);

        const riskLimits = {
            maxDailyLossUsd: riskTier === 'aggressive'
                ? Math.max(100, Math.round(allocatedCapital * 0.1))
                : riskTier === 'conservative'
                    ? Math.max(25, Math.round(allocatedCapital * 0.03))
                    : Math.max(50, Math.round(allocatedCapital * 0.05)),
            maxOpenTrades: riskTier === 'aggressive' ? 5 : riskTier === 'conservative' ? 2 : 3,
            maxConsecutiveLosses: riskTier === 'aggressive' ? 3 : 2
        };

        const profile: ChatProfilePayload = {
            name: profileName,
            allocated_capital: allocatedCapital,
            risk_per_trade_percent: riskPerTradePercent,
            symbols,
            is_active: isActive,
            strategy_config: {
                rules: [
                    { ruleId: 'TrendBiasRule', enabled: true, params: { fastPeriod: 50, slowPeriod: 200 } },
                    { ruleId: 'MomentumRule', enabled: true, params: { rsiPeriod: 14, overbought: 70, oversold: 30 } },
                    { ruleId: 'ZoneRule', enabled: true, params: { zonePercent: 1.5 } },
                    { ruleId: 'SessionRule', enabled: true, params: { sessions } },
                    { ruleId: 'EntryTriggerRule', enabled: true, params: { showPatterns: true } },
                    { ruleId: 'RiskManagementRule', enabled: true, params: { maxRisk: 2.0 } },
                    { ruleId: 'AIAnalysisRule', enabled: aiRuleEnabled, params: { minConfidence: 70 } }
                ],
                riskLimits,
                execution: {
                    orderType,
                    cooldownMinutes,
                    entryMode
                }
            }
        };

        const updateTarget = this.detectProfileToUpdate(message, context);
        if (updateTarget) {
            const existingConfig = updateTarget.strategy_config || {};
            const existingExecution = existingConfig.execution || {};
            const existingRiskLimits = existingConfig.riskLimits || {};
            const existingRules = Array.isArray(existingConfig.rules) ? existingConfig.rules : profile.strategy_config.rules;

            const mergedProfile: ChatProfilePayload = {
                ...updateTarget,
                id: updateTarget.id,
                name: updateTarget.name || profile.name,
                allocated_capital: allocatedCapital || Number(updateTarget.allocated_capital || profile.allocated_capital),
                risk_per_trade_percent: riskPerTradePercent || Number(updateTarget.risk_per_trade_percent || profile.risk_per_trade_percent),
                symbols: symbols || updateTarget.symbols || profile.symbols,
                is_active: typeof updateTarget.is_active === 'boolean' ? updateTarget.is_active : isActive,
                strategy_config: {
                    ...existingConfig,
                    rules: existingRules,
                    riskLimits: {
                        maxDailyLossUsd: Number(existingRiskLimits.maxDailyLossUsd || riskLimits.maxDailyLossUsd),
                        maxOpenTrades: Number(existingRiskLimits.maxOpenTrades || riskLimits.maxOpenTrades),
                        maxConsecutiveLosses: Number(existingRiskLimits.maxConsecutiveLosses ?? riskLimits.maxConsecutiveLosses)
                    },
                    execution: {
                        orderType: existingExecution.orderType === 'limit' ? 'limit' : orderType,
                        cooldownMinutes: Number(existingExecution.cooldownMinutes ?? cooldownMinutes),
                        entryMode: existingExecution.entryMode === 'long_only' ? 'long_only' : entryMode
                    }
                }
            };

            return {
                action: 'update_profile',
                profile: mergedProfile,
                summary: `AI was unavailable. Built a deterministic fallback update for profile "${mergedProfile.name}".`,
                reasoning: `Applied local heuristics from your prompt (symbols=${symbols}, risk=${riskPerTradePercent}%, mode=${entryMode}, orderType=${orderType}).`,
                fallback: 'local_deterministic'
            };
        }

        return {
            action: 'create_profile',
            profile,
            summary: `AI was unavailable. Built a deterministic fallback profile "${profileName}".`,
            reasoning: `Applied local heuristics from your prompt (symbols=${symbols}, risk=${riskPerTradePercent}%, mode=${entryMode}, orderType=${orderType}, cooldown=${cooldownMinutes}m).`,
            fallback: 'local_deterministic'
        };
    }

    private extractCapital(message: string): number {
        const normalized = String(message || '');
        const directPattern = /\$\s*([0-9][0-9,]*(?:\.[0-9]+)?)\s*([kKmM]?)/;
        const keywordPattern = /\b(capital|budget|allocate|allocated)\b[^0-9$]*\$?\s*([0-9][0-9,]*(?:\.[0-9]+)?)\s*([kKmM]?)/i;
        let numericPart = '';
        let suffix = '';

        const keywordMatch = normalized.match(keywordPattern);
        if (keywordMatch) {
            numericPart = keywordMatch[2];
            suffix = keywordMatch[3] || '';
        } else {
            const directMatch = normalized.match(directPattern);
            if (directMatch) {
                numericPart = directMatch[1];
                suffix = directMatch[2] || '';
            }
        }

        const base = Number(String(numericPart || '1000').replace(/,/g, ''));
        if (!Number.isFinite(base) || base <= 0) return 1000;
        const normalizedSuffix = String(suffix || '').toLowerCase();
        if (normalizedSuffix === 'k') return Math.round(base * 1000);
        if (normalizedSuffix === 'm') return Math.round(base * 1000000);
        return Math.round(base);
    }

    private extractCooldownMinutes(message: string): number {
        const normalized = String(message || '');
        const explicit = normalized.match(/([0-9]{1,3})\s*(?:min|mins|minute|minutes|m)\s*(?:cooldown|delay|wait)?/i);
        if (explicit) {
            const parsed = Number(explicit[1]);
            if (Number.isFinite(parsed) && parsed >= 1) return Math.min(240, Math.max(1, parsed));
        }
        if (/\bscalp|scalper\b/i.test(normalized)) return 10;
        if (/\bswing\b/i.test(normalized)) return 45;
        return 30;
    }

    private extractRiskProfile(message: string): { riskPerTradePercent: number; riskTier: 'conservative' | 'balanced' | 'aggressive' } {
        const normalized = String(message || '').toLowerCase();
        const explicit = normalized.match(/([0-9]+(?:\.[0-9]+)?)\s*%\s*(?:risk|risk\s*per\s*trade)/i);
        if (explicit) {
            const parsed = Number(explicit[1]);
            if (Number.isFinite(parsed) && parsed > 0) {
                const bounded = Math.min(10, Math.max(0.1, parsed));
                const riskTier = bounded <= 1 ? 'conservative' : bounded >= 2.5 ? 'aggressive' : 'balanced';
                return { riskPerTradePercent: Number(bounded.toFixed(2)), riskTier };
            }
        }

        if (/\b(conservative|low[\s-]*risk|safe)\b/i.test(normalized)) {
            return { riskPerTradePercent: 0.8, riskTier: 'conservative' };
        }
        if (/\b(aggressive|high[\s-]*risk|scalp|scalper)\b/i.test(normalized)) {
            return { riskPerTradePercent: 2.5, riskTier: 'aggressive' };
        }
        return { riskPerTradePercent: 1.2, riskTier: 'balanced' };
    }

    private extractSymbolsFromMessage(message: string): string {
        const upper = String(message || '').toUpperCase();
        const symbols = new Set<string>();
        const explicitPairs = upper.match(/\b[A-Z]{2,10}\/[A-Z]{2,10}\b/g) || [];
        explicitPairs.forEach((pair) => symbols.add(pair));

        const knownAssets = upper.match(/\b(BTC|ETH|SOL|DOGE|XRP|ADA|BNB|AVAX|MATIC|LTC|LINK|DOT|TRX|SHIB)\b/g) || [];
        for (const asset of knownAssets) {
            symbols.add(`${asset}/USDT`);
        }

        if (symbols.size === 0) {
            return 'BTC/USDT, ETH/USDT';
        }
        return Array.from(symbols).slice(0, 6).join(', ');
    }

    private extractSessions(message: string): string {
        const lower = String(message || '').toLowerCase();
        const sessions: string[] = [];
        if (/\blondon\b|\bldn\b/.test(lower)) sessions.push('London');
        if (/\bnew york\b|\bny\b/.test(lower)) sessions.push('NY');
        if (/\btokyo\b|\btok\b/.test(lower)) sessions.push('Tokyo');
        if (/\bsydney\b|\bsyd\b/.test(lower)) sessions.push('Sydney');
        return sessions.length > 0 ? sessions.join(',') : 'London,NY';
    }

    private buildFallbackProfileName(messageLower: string): string {
        if (/\bconservative\b/.test(messageLower)) return 'Conservative Fallback Strategy';
        if (/\baggressive|scalp|scalper\b/.test(messageLower)) return 'Aggressive Fallback Strategy';
        if (/\bswing\b/.test(messageLower)) return 'Swing Fallback Strategy';
        return 'AI Fallback Strategy';
    }

    private detectProfileToUpdate(message: string, context: any[]): any | null {
        const lower = String(message || '').toLowerCase();
        if (!/\b(update|modify|change|edit|tweak)\b/.test(lower)) return null;
        if (!Array.isArray(context) || context.length === 0) return null;

        for (const profile of context) {
            const name = String(profile?.name || '').toLowerCase().trim();
            if (!name) continue;
            if (lower.includes(name)) return profile;
        }
        return null;
    }

    private normalizeBacktestTimeframe(value: unknown): BacktestTimeframe {
        const normalized = String(value || '').trim().toLowerCase();
        if (normalized === '1m' || normalized === '1min') return '1m';
        if (normalized === '15m' || normalized === '15min') return '15m';
        if (normalized === '1h' || normalized === '60m') return '1h';
        if (normalized === '4h' || normalized === '240m') return '4h';
        throw new Error(`Invalid timeframe "${String(value || '')}". Use 1m, 15m, 1h, or 4h.`);
    }

    private parseSymbolList(input: unknown): string[] {
        if (Array.isArray(input)) {
            return input
                .map((value) => String(value || '').trim().toUpperCase())
                .filter(Boolean);
        }
        return String(input || '')
            .split(',')
            .map((value) => value.trim().toUpperCase())
            .filter(Boolean);
    }

    private enforceBacktestPayloadGuards(dataSource: any): void {
        if (!dataSource || typeof dataSource !== 'object') {
            throw new Error('Backtest request requires dataSource.');
        }

        if (dataSource.type === 'csv') {
            const payload = String(dataSource.payload || '');
            const bytes = Buffer.byteLength(payload, 'utf8');
            if (bytes > config.BACKTEST_MAX_CSV_BYTES) {
                throw new Error(`CSV payload too large (${bytes} bytes > ${config.BACKTEST_MAX_CSV_BYTES}).`);
            }
            const rows = payload.split(/\r?\n/).filter((line: string) => line.trim().length > 0).length;
            if (rows > config.BACKTEST_MAX_ROWS + 1) {
                throw new Error(`CSV row count exceeds BACKTEST_MAX_ROWS (${config.BACKTEST_MAX_ROWS}).`);
            }
            return;
        }

        if (dataSource.type === 'json' || dataSource.type === 'replay') {
            const candles = dataSource?.payload?.candles;
            if (Array.isArray(candles) && candles.length > config.BACKTEST_MAX_ROWS) {
                throw new Error(`JSON candle count exceeds BACKTEST_MAX_ROWS (${config.BACKTEST_MAX_ROWS}).`);
            }
            return;
        }

        if (dataSource.type === 'kraken') {
            return;
        }

        throw new Error(`Unsupported backtest dataSource.type "${String(dataSource.type || '')}".`);
    }

    private loadState() {
        const candidatePaths = [
            this.storagePath,
            `${this.storagePath}.bak`,
            `${this.storagePath}.tmp`
        ];

        for (const candidate of candidatePaths) {
            try {
                if (!fs.existsSync(candidate)) continue;
                const data = fs.readFileSync(candidate, 'utf8');
                const savedState = JSON.parse(data);
                if (!savedState) continue;

                this.state = {
                    ...this.state,
                    ...savedState,
                    settings: savedState.settings || this.state.settings,
                    health: {
                        ...this.state.health,
                        ...(savedState.health || {})
                    }
                };
                if (this.state.health.tradingControl) {
                    healthTracker.recordTradingControl(this.state.health.tradingControl);
                }
                logger.info(`[API] Restored state from ${candidate}`);
                return;
            } catch (error) {
                logger.error(`[API] Failed to load state from ${candidate}:`, error);
            }
        }
    }


    private async resolveSnapshotOwnerId(): Promise<string | null> {
        if (this.snapshotOwnerId) return this.snapshotOwnerId;
        const owner = await supabaseService.getSnapshotOwnerId();
        this.snapshotOwnerId = owner;
        return owner;
    }

    private async restoreStateFromLatestSnapshot(): Promise<void> {
        try {
            const ownerId = await this.resolveSnapshotOwnerId();
            if (!ownerId) {
                logger.warn('[API] Snapshot owner not resolved; skipping Supabase restore.');
            } else {
                const snapshot = await supabaseService.loadLatestBotStateSnapshot(ownerId);
                if (snapshot && snapshot.state) {
                    const restoredState = snapshot.state as Partial<BotState>;
                    this.state = {
                        ...this.state,
                        ...restoredState,
                        settings: restoredState.settings || this.state.settings,
                        health: {
                            ...this.state.health,
                            ...(restoredState.health || {})
                        }
                    };
                    if (this.state.health.tradingControl) {
                        healthTracker.recordTradingControl(this.state.health.tradingControl);
                    }
                    logger.info(`[API] Restored runtime state from Supabase snapshot (user=${ownerId}).`);
                }
            }

            const cosmosTradingControl = await loadGlobalTradingControl();
            if (cosmosTradingControl) {
                healthTracker.recordTradingControl(cosmosTradingControl);
                logger.info('[API] Restored trading control from Cosmos.');
            }
        } catch (error: any) {
            logger.error('[API] Failed to restore state from Supabase snapshot:', error);
        }
    }


    private scheduleSnapshotWrite(): void {
        if (!config.ENABLE_DB_SNAPSHOTS) return;
        if (this.snapshotWriteTimer) {
            clearTimeout(this.snapshotWriteTimer);
        }
        // Small debounce to catch bursts, but the actual write is throttled in persistSnapshotToDb
        this.snapshotWriteTimer = setTimeout(() => {
            void this.persistSnapshotToDb();
        }, 5000);
    }

    private async persistSnapshotToDb(): Promise<void> {
        if (!config.ENABLE_DB_SNAPSHOTS) return;

        const now = Date.now();
        const elapsed = now - this.lastSnapshotWriteAt;
        if (elapsed < config.DB_SNAPSHOT_INTERVAL_MS) {
            // If we are too soon, schedule another check at the next interval boundary
            const remaining = config.DB_SNAPSHOT_INTERVAL_MS - elapsed;
            if (this.snapshotWriteTimer) clearTimeout(this.snapshotWriteTimer);
            this.snapshotWriteTimer = setTimeout(() => {
                void this.persistSnapshotToDb();
            }, Math.max(remaining, 5000));
            return;
        }

        if (this.isSnapshotWriteInFlight) {
            this.snapshotWriteQueued = true;
            return;
        }

        this.isSnapshotWriteInFlight = true;
        try {
            const ownerId = await this.resolveSnapshotOwnerId();
            if (!ownerId) return;
            await supabaseService.saveBotStateSnapshot(ownerId, this.getPersistableState());
            this.lastSnapshotWriteAt = Date.now();
            logger.info(`[API] Persisted snapshot for ${ownerId}. Interval: ${Math.round(elapsed / 1000)}s`);
        } catch (error: any) {
            logger.error(`[API] Snapshot persistence failed: ${error.message}`);
        } finally {
            this.isSnapshotWriteInFlight = false;
            if (this.snapshotWriteQueued) {
                this.snapshotWriteQueued = false;
                this.scheduleSnapshotWrite();
            }
        }
    }

    private saveState() {
        this.scheduleStateWrite();
        this.scheduleSnapshotWrite();
    }

    private setupMiddleware() {
        this.app.use(cors({
            origin: (origin, callback) => {
                if (this.isCorsOriginAllowed(origin)) {
                    callback(null, true);
                    return;
                }
                callback(new Error(`CORS blocked for origin: ${origin || 'unknown'}`));
            },
            credentials: true
        }));
        this.app.use(express.json());
    }

    private setupRoutes() {
        this.app.get('/health', (req, res) => {
            const now = Date.now();
            const status = this.getHealthStatus(now);
            res.status(status === 'healthy' ? 200 : 503).json({
                status,
                uptime: now - this.startTime,
                runtime: this.runtimeHealth
            });
        });

        this.app.get('/health/live', (req, res) => {
            res.status(200).json({ status: 'alive', uptime: Date.now() - this.startTime });
        });

        this.app.get('/health/ready', (req, res) => {
            const status = this.getHealthStatus();
            res.status(status === 'healthy' ? 200 : 503).json({
                status,
                runtime: this.runtimeHealth
            });
        });

        this.app.get('/internal/health', (req, res) => {
            const snapshot = healthTracker.getSnapshot();
            res.status(200).json({
                ...snapshot,
                observability: observabilityService.getSummary(),
                sloFlags: observabilityService.sloFlags()
            });
        });

        this.app.get('/metrics', async (req, res) => {
            res.set('Content-Type', observabilityService.contentType());
            try {
                const metrics = await observabilityService.metrics();
                res.send(metrics);
            } catch (err: any) {
                logger.error(`[Metrics] Failed to render Prometheus metrics: ${err.message}`);
                res.status(500).json({ error: 'Metrics render failed' });
            }
        });

        this.app.get('/api/state', this.requireAuth, async (req, res) => {
            const authUserId = (req as AuthenticatedRequest).authUserId;
            if (!authUserId) {
                res.status(401).json({ error: 'Unauthorized' });
                return;
            }
            this.state.uptime = Date.now() - this.startTime;
            const isAdmin = await supabaseService.isAdmin(authUserId);
            const scopedState = this.getScopedState(authUserId, isAdmin);
            res.json({
                ...scopedState,
                runtimeHealth: this.runtimeHealth
            });
        });

        this.app.get('/api/lifecycle/canonical', this.requireAuth, async (req, res) => {
            const authUserId = (req as AuthenticatedRequest).authUserId;
            if (!authUserId) {
                res.status(401).json({ error: 'Unauthorized' });
                return;
            }

            try {
                const isAdmin = await supabaseService.isAdmin(authUserId);
                const requestedProfileId = String(req.query.profileId || '').trim();
                const splitByProfileQuery = String(req.query.splitByProfile || '').trim().toLowerCase();
                const splitByProfile = splitByProfileQuery
                    ? splitByProfileQuery !== 'false'
                    : true;
                const canonicalMaxRowsCap = 200_000;
                const defaultMaxRows = Math.max(1_000, Math.min(canonicalMaxRowsCap, Number(config.CANONICAL_LIFECYCLE_MAX_ROWS || 200_000)));
                const maxRows = Math.max(
                    1_000,
                    Math.min(canonicalMaxRowsCap, Number.parseInt(String(req.query.maxRows || defaultMaxRows), 10) || defaultMaxRows)
                );

                const profileRows = isAdmin
                    ? await supabaseService.getAllProfiles()
                    : await supabaseService.getProfilesForUser(authUserId);

                if (requestedProfileId && !profileRows.some((row) => row.id === requestedProfileId)) {
                    res.status(403).json({ success: false, error: 'Forbidden: profile does not belong to scoped user context' });
                    return;
                }

                const profilesInScope = requestedProfileId
                    ? profileRows.filter((row) => row.id === requestedProfileId)
                    : profileRows;

                const profileMeta: CanonicalLifecycleProfileMeta[] = profilesInScope.map((row) => ({
                    id: String(row.id),
                    userId: String(row.user_id),
                    name: String(row.name || row.id),
                    allocatedCapital: Number(row.allocated_capital || 0),
                    isActive: Boolean(row.is_active)
                }));
                const useProfileScopedFetch = !requestedProfileId && splitByProfile && profilesInScope.length > 1;
                let lifecycleOrderRows: any[] = [];
                let lifecycleOrderTruncated = false;

                if (useProfileScopedFetch) {
                    const deduped = new Map<string, any>();
                    for (const profileRow of profilesInScope) {
                        const profileScoped = isAdmin
                            ? await supabaseService.getFilledLifecycleOrdersGlobal({
                                profileId: String(profileRow.id),
                                maxRows
                            })
                            : await supabaseService.getFilledLifecycleOrdersForUser({
                                userId: authUserId,
                                profileId: String(profileRow.id),
                                maxRows
                            });

                        lifecycleOrderTruncated = lifecycleOrderTruncated || profileScoped.truncated;
                        for (const row of profileScoped.rows || []) {
                            const key = String(
                                row?.id
                                || row?.order_id
                                || `${row?.profile_id || profileRow.id}|${row?.trade_id || ''}|${row?.created_at || row?.timestamp || ''}|${row?.side || ''}|${row?.action || ''}`
                            ).trim();
                            if (!key) continue;
                            if (!deduped.has(key)) {
                                deduped.set(key, row);
                            }
                        }
                    }
                    lifecycleOrderRows = Array.from(deduped.values()).sort((a: any, b: any) => {
                        const aTs = Date.parse(String(a?.created_at || a?.filled_at || a?.timestamp || 0));
                        const bTs = Date.parse(String(b?.created_at || b?.filled_at || b?.timestamp || 0));
                        if (Number.isFinite(aTs) && Number.isFinite(bTs) && aTs !== bTs) {
                            return aTs - bTs;
                        }
                        const aId = String(a?.id || a?.order_id || '');
                        const bId = String(b?.id || b?.order_id || '');
                        return aId.localeCompare(bId);
                    });
                } else {
                    const lifecycleOrderResult = isAdmin
                        ? await supabaseService.getFilledLifecycleOrdersGlobal({
                            profileId: requestedProfileId || undefined,
                            maxRows
                        })
                        : await supabaseService.getFilledLifecycleOrdersForUser({
                            userId: authUserId,
                            profileId: requestedProfileId || undefined,
                            maxRows
                        });
                    lifecycleOrderRows = lifecycleOrderResult.rows || [];
                    lifecycleOrderTruncated = Boolean(lifecycleOrderResult.truncated);
                }

                const symbolPrices = Object.entries(this.state.symbols || {}).reduce<Record<string, number>>((acc, [symbol, value]) => {
                    const price = Number(value?.price || 0);
                    if (Number.isFinite(price) && price > 0) {
                        acc[symbol] = price;
                    }
                    return acc;
                }, {});

                const snapshot = canonicalLifecycleService.buildSnapshot({
                    orders: lifecycleOrderRows,
                    profiles: profileMeta,
                    symbolPrices,
                    truncated: lifecycleOrderTruncated
                });
                this.updateRuntimeHealth({
                    canonicalLifecycleTruncated: Boolean(snapshot.diagnostics.truncated),
                    canonicalLifecycleOrderRows: Number(snapshot.diagnostics.orderRows || 0)
                });

                if (snapshot.diagnostics.truncated) {
                    const alertScope = requestedProfileId || (isAdmin ? 'global-admin' : authUserId);
                    const now = Date.now();
                    const throttleMs = Math.max(0, Number(config.CANONICAL_LIFECYCLE_TRUNCATION_ALERT_MS || 600_000));
                    const lastAlertAt = this.canonicalTruncationAlertByScope.get(alertScope) || 0;
                    if (throttleMs <= 0 || (now - lastAlertAt) >= throttleMs) {
                        this.canonicalTruncationAlertByScope.set(alertScope, now);
                        observabilityService.emitEvent({
                            type: 'RECONCILIATION_DEGRADED',
                            severity: 'WARN',
                            message: `Canonical lifecycle response truncated at ${snapshot.diagnostics.orderRows} rows (maxRows=${maxRows}). Increase CANONICAL_LIFECYCLE_MAX_ROWS or query narrower scope.`,
                            profileId: requestedProfileId || undefined,
                            userId: isAdmin ? undefined : authUserId
                        });
                    }
                }

                res.json({
                    success: true,
                    scope: {
                        isAdmin,
                        profileId: requestedProfileId || null,
                        profileCount: profileMeta.length,
                        splitByProfile: useProfileScopedFetch
                    },
                    snapshot
                });
            } catch (error: any) {
                res.status(500).json({ success: false, error: error.message });
            }
        });

        this.app.get('/api/alerts', this.requireAuth, async (req, res) => {
            const authUserId = (req as AuthenticatedRequest).authUserId;
            if (!authUserId) {
                res.status(401).json({ error: 'Unauthorized' });
                return;
            }
            const limit = parseInt(req.query.limit as string) || 50;
            const isAdmin = await supabaseService.isAdmin(authUserId);
            const scopedState = this.getScopedState(authUserId, isAdmin);
            const alerts = scopedState.alerts;
            res.json(alerts.slice(-limit));
        });

        // --- SAFE ADMIN TRADE CONTROL ENDPOINTS ---
        this.app.get('/internal/trading/status', this.requireAuth, (req, res) => {
            res.json(healthTracker.getSnapshot().tradingControl);
        });

        this.app.post('/internal/trading/pause', this.requireAuth, this.requireAdmin, (req, res) => {
            const { reason } = req.body;
            const userId = (req as AuthenticatedRequest).authUserId || 'unknown';

            const update: TradingControlSnapshot = {
                mode: 'PAUSED',
                lastChangedBy: userId,
                lastChangedAt: Date.now(),
                reason: reason || 'Manual admin pause'
            };

            healthTracker.recordTradingControl(update);

            observabilityService.emitEvent({
                type: 'SYSTEM_ERROR',
                severity: 'WARN',
                message: `Trading PAUSED by operator: ${update.reason}`,
                userId
            });

            logger.warn(`[Admin] Trading PAUSED by ${userId}. Reason: ${update.reason}`);
            res.json({ success: true, status: update });
        });

        this.app.post('/internal/trading/resume', this.requireAuth, this.requireAdmin, (req, res) => {
            const { reason } = req.body;
            const userId = (req as AuthenticatedRequest).authUserId || 'unknown';

            const update: TradingControlSnapshot = {
                mode: 'RUNNING',
                lastChangedBy: userId,
                lastChangedAt: Date.now(),
                reason: reason || 'Manual admin resume'
            };

            healthTracker.recordTradingControl(update);

            observabilityService.emitEvent({
                type: 'SYSTEM_ERROR',
                severity: 'INFO',
                message: `Trading RESUMED by operator: ${update.reason}`,
                userId
            });

            logger.info(`[Admin] Trading RESUMED by ${userId}.`);
            res.json({ success: true, status: update });
        });

        // Non-destructive batch rollback for reconciliation backfill rows.
        this.app.post('/api/admin/revert-backfill-batch', this.requireAuth, this.requireAdmin, async (req, res) => {
            const batchId = String(req.body.batchId || '').trim();

            if (!batchId) {
                res.status(400).json({ success: false, error: 'batchId is required' });
                return;
            }

            try {
                const result = await supabaseService.revertBackfillBatch(batchId);
                if (result.errors.length > 0) {
                    res.status(500).json({ success: false, reverted: result.reverted, errors: result.errors });
                    return;
                }
                res.json({ success: true, reverted: result.reverted });
            } catch (err: any) {
                res.status(500).json({ success: false, error: err.message });
            }
        });



        this.app.get('/api/symbol/:symbol', this.requireAuth, (req, res) => {
            const authUserId = (req as AuthenticatedRequest).authUserId;
            if (!authUserId) {
                res.status(401).json({ error: 'Unauthorized' });
                return;
            }
            const symbolParam = req.params.symbol;
            const symbol = Array.isArray(symbolParam) ? symbolParam[0] : symbolParam;

            if (!symbol) {
                res.status(400).json({ error: 'Symbol is required' });
                return;
            }

            const symbolState = this.state.symbols[symbol];
            if (symbolState) {
                res.json(this.getScopedSymbolState(symbolState, authUserId));
            } else {
                res.status(404).json({ error: 'Symbol not found' });
            }
        });

        // --- Bot Configuration (non-secret) ---
        this.app.get('/api/config', this.requireAuth, (req, res) => {
            res.json({
                DATA_PROVIDER: config.DATA_PROVIDER,
                EXECUTION_PROVIDER: config.EXECUTION_PROVIDER,
                SYMBOLS: config.SYMBOLS,
                TIMEFRAME: config.TIMEFRAME,
                POLLING_INTERVAL: config.POLLING_INTERVAL,
                PAPER_TRADING: config.PAPER_TRADING,
                ASSET_CLASS: config.ASSET_CLASS,
                EXCHANGE: config.EXCHANGE,
                ENABLE_TRADING: config.ENABLE_TRADING,
                TOTAL_CAPITAL: config.TOTAL_CAPITAL,
                MAX_OPEN_TRADES: config.MAX_OPEN_TRADES,
                COOLDOWN_MS: config.COOLDOWN_MS,
                PROFIT_EXIT_PERCENT: config.PROFIT_EXIT_PERCENT,
                TRAILING_STOP_PERCENT: config.TRAILING_STOP_PERCENT,
                ENABLE_STRICT_CAPITAL_GUARD: config.ENABLE_STRICT_CAPITAL_GUARD,
                ENABLE_AUTO_PAUSE_ON_SLIPPAGE_BREACH: config.ENABLE_AUTO_PAUSE_ON_SLIPPAGE_BREACH,
                STRICT_CAPITAL_SLIPPAGE_BUFFER_PCT: config.STRICT_CAPITAL_SLIPPAGE_BUFFER_PCT,
                STRICT_CAPITAL_FEE_BUFFER_PCT: config.STRICT_CAPITAL_FEE_BUFFER_PCT,
                STRICT_CAPITAL_MIN_RESERVE_USD: config.STRICT_CAPITAL_MIN_RESERVE_USD,
                PROFILE_SYNC_INTERVAL_MS: config.PROFILE_SYNC_INTERVAL_MS,
                MONITOR_INTERVAL_MS: config.MONITOR_INTERVAL_MS,
                STALE_ORDER_THRESHOLD_MINUTES: config.STALE_ORDER_THRESHOLD_MINUTES,
                ORDER_SYNC_MISSING_GRACE_MINUTES: config.ORDER_SYNC_MISSING_GRACE_MINUTES,
                ORDER_SYNC_MISSING_CONFIRMATION_COUNT: config.ORDER_SYNC_MISSING_CONFIRMATION_COUNT,
                DYNAMIC_CONFIG_REFRESH_MS: config.DYNAMIC_CONFIG_REFRESH_MS,
                LOW_STRESS_MODE: config.LOW_STRESS_MODE,
                ENABLE_RECON_EXIT_BACKFILL: config.ENABLE_RECON_EXIT_BACKFILL,
                RECON_EXIT_BACKFILL_DRY_RUN: config.RECON_EXIT_BACKFILL_DRY_RUN,
                RECON_EXIT_BACKFILL_REQUIRE_PAUSE: config.RECON_EXIT_BACKFILL_REQUIRE_PAUSE,
                RECON_EXIT_BACKFILL_DUST_ABS_QTY: config.RECON_EXIT_BACKFILL_DUST_ABS_QTY,
                RECON_EXIT_BACKFILL_DUST_REL_PCT: config.RECON_EXIT_BACKFILL_DUST_REL_PCT,
                RECON_EXIT_BACKFILL_LOOKBACK_HOURS: config.RECON_EXIT_BACKFILL_LOOKBACK_HOURS,
                RECON_EXIT_BACKFILL_REQUIRE_STRONG_ATTRIBUTION: config.RECON_EXIT_BACKFILL_REQUIRE_STRONG_ATTRIBUTION,
                RECON_EXIT_BACKFILL_ALLOW_HEURISTIC_MATCH: config.RECON_EXIT_BACKFILL_ALLOW_HEURISTIC_MATCH,
                RECON_EXIT_BACKFILL_FILL_AFTER_TRADE_GRACE_MINUTES: config.RECON_EXIT_BACKFILL_FILL_AFTER_TRADE_GRACE_MINUTES,
                RECON_EXIT_BACKFILL_PROFILE_ALLOWLIST: config.RECON_EXIT_BACKFILL_PROFILE_ALLOWLIST,
                ENABLE_RECON_POSITION_PARITY_HEARTBEAT: config.ENABLE_RECON_POSITION_PARITY_HEARTBEAT,
                RECON_POSITION_PARITY_DRY_RUN: config.RECON_POSITION_PARITY_DRY_RUN,
                RECON_POSITION_PARITY_CONFIRMATIONS: config.RECON_POSITION_PARITY_CONFIRMATIONS,
                RECON_POSITION_PARITY_DUST_ABS_QTY: config.RECON_POSITION_PARITY_DUST_ABS_QTY,
                RECON_POSITION_PARITY_MAX_NOTIONAL_PCT: config.RECON_POSITION_PARITY_MAX_NOTIONAL_PCT,
                RECON_POSITION_PARITY_REQUIRE_SUBTAG_ATTRIBUTION: config.RECON_POSITION_PARITY_REQUIRE_SUBTAG_ATTRIBUTION,
                ENABLE_RECON_ORDER_COVERAGE_SYNC: config.ENABLE_RECON_ORDER_COVERAGE_SYNC,
                RECON_ORDER_COVERAGE_DRY_RUN: config.RECON_ORDER_COVERAGE_DRY_RUN,
                RECON_ORDER_COVERAGE_REQUIRE_PAUSE: config.RECON_ORDER_COVERAGE_REQUIRE_PAUSE,
                RECON_ORDER_COVERAGE_LOOKBACK_HOURS: config.RECON_ORDER_COVERAGE_LOOKBACK_HOURS,
                RECON_ORDER_COVERAGE_FETCH_LIMIT_PER_PAGE: config.RECON_ORDER_COVERAGE_FETCH_LIMIT_PER_PAGE,
                RECON_ORDER_COVERAGE_MAX_FETCH_PAGES: config.RECON_ORDER_COVERAGE_MAX_FETCH_PAGES,
                RECON_ORDER_COVERAGE_MAX_INSERTS_PER_PROFILE: config.RECON_ORDER_COVERAGE_MAX_INSERTS_PER_PROFILE,
                RECON_ORDER_COVERAGE_TRADE_ID_LOOKBACK_ROWS: config.RECON_ORDER_COVERAGE_TRADE_ID_LOOKBACK_ROWS,
                RECON_ORDER_COVERAGE_REQUIRE_SUBTAG_ATTRIBUTION: config.RECON_ORDER_COVERAGE_REQUIRE_SUBTAG_ATTRIBUTION,
                RECON_ORDER_COVERAGE_AUTO_PAUSE_ON_UNATTRIBUTED_FILLS: config.RECON_ORDER_COVERAGE_AUTO_PAUSE_ON_UNATTRIBUTED_FILLS,
                RECON_ORDER_COVERAGE_UNATTRIBUTED_PAUSE_MIN_COUNT: config.RECON_ORDER_COVERAGE_UNATTRIBUTED_PAUSE_MIN_COUNT,
                RECON_ORDER_COVERAGE_UNATTRIBUTED_BASELINE_MS: config.RECON_ORDER_COVERAGE_UNATTRIBUTED_BASELINE_MS,
                ENABLE_RECON_INTEGRITY_WATCHDOG: config.ENABLE_RECON_INTEGRITY_WATCHDOG,
                RECON_INTEGRITY_WATCHDOG_THROTTLE_MS: config.RECON_INTEGRITY_WATCHDOG_THROTTLE_MS,
                RECON_INTEGRITY_WATCHDOG_MISSING_DB_THRESHOLD: config.RECON_INTEGRITY_WATCHDOG_MISSING_DB_THRESHOLD,
                RECON_INTEGRITY_WATCHDOG_NO_GO_THRESHOLD: config.RECON_INTEGRITY_WATCHDOG_NO_GO_THRESHOLD,
                ENABLE_RECON_WATCHDOG_AUTO_RESUME: config.ENABLE_RECON_WATCHDOG_AUTO_RESUME,
                RECON_WATCHDOG_AUTO_RESUME_MIN_PAUSE_MS: config.RECON_WATCHDOG_AUTO_RESUME_MIN_PAUSE_MS,
                RECON_WATCHDOG_AUTO_RESUME_CLEAN_CYCLES: config.RECON_WATCHDOG_AUTO_RESUME_CLEAN_CYCLES,
                RECON_WATCHDOG_AUTO_RESUME_COOLDOWN_MS: config.RECON_WATCHDOG_AUTO_RESUME_COOLDOWN_MS,
                EXCHANGE_STATE_MISMATCH_THROTTLE_MS: config.EXCHANGE_STATE_MISMATCH_THROTTLE_MS,
                REQUIRE_EXCHANGE_FILL_EVIDENCE_FOR_AUTO_CLOSE: config.REQUIRE_EXCHANGE_FILL_EVIDENCE_FOR_AUTO_CLOSE,
                ENABLE_ALPACA_SUBTAG: config.ENABLE_ALPACA_SUBTAG,
                SUBTAG_OMNIBUS_ONLY: config.SUBTAG_OMNIBUS_ONLY,
                ALPACA_SUBTAG_ENV: config.ALPACA_SUBTAG_ENV,
                ALPACA_SUBTAG_MAX_LENGTH: config.ALPACA_SUBTAG_MAX_LENGTH,
                ALPACA_SUBTAG_DISABLE_FOR_EXCHANGE: config.ALPACA_SUBTAG_DISABLE_FOR_EXCHANGE,
                ALPACA_OMNIBUS_PROFILE_ALLOWLIST: config.ALPACA_OMNIBUS_PROFILE_ALLOWLIST,
                AI_PROVIDER: config.AI.PROVIDER,
                AI_MODEL: config.AI.MODEL,
                AI_CONFIDENCE_THRESHOLD: config.AI.CONFIDENCE_THRESHOLD,
                AI_FALLBACK_LIST: config.AI.FALLBACK_LIST,
                AI_CACHE_HOURS: config.AI.CACHE_HOURS,
                AI_FAIL_OPEN: config.AI.FAIL_OPEN,
                ENABLE_BACKTEST: config.ENABLE_BACKTEST,
                BACKTEST_CUSTOMER_ENABLED: config.BACKTEST_CUSTOMER_ENABLED,
                BACKTEST_MAX_CSV_BYTES: config.BACKTEST_MAX_CSV_BYTES,
                BACKTEST_MAX_ROWS: config.BACKTEST_MAX_ROWS,
                ENABLED_RULES: config.PRO_STRATEGY.ENABLED_RULES,
                RISK_PER_TRADE: config.PRO_STRATEGY.PARAMETERS.RISK_PER_TRADE,
                RISK_REWARD_RATIO: config.PRO_STRATEGY.PARAMETERS.RISK_REWARD_RATIO,
                SL_MULTIPLIER: config.PRO_STRATEGY.PARAMETERS.SL_MULTIPLIER,
                TREND_TIMEFRAME: config.PRO_STRATEGY.PARAMETERS.TREND_TIMEFRAME,
                EXECUTION_TIMEFRAME: config.PRO_STRATEGY.PARAMETERS.EXECUTION_TIMEFRAME,
                MOMENTUM_TIMEFRAME: config.PRO_STRATEGY.PARAMETERS.MOMENTUM_TIMEFRAME,
                RSI_PERIOD: config.PRO_STRATEGY.PARAMETERS.RSI_PERIOD,
                RSI_OVERBOUGHT: config.PRO_STRATEGY.PARAMETERS.RSI_OVERBOUGHT,
                RSI_OVERSOLD: config.PRO_STRATEGY.PARAMETERS.RSI_OVERSOLD,
                ATR_PERIOD: config.PRO_STRATEGY.PARAMETERS.ATR_PERIOD,
            });
        });

        this.app.get('/api/me/profile', this.requireAuth, async (req, res) => {
            const authReq = req as AuthenticatedRequest;
            const authUserId = authReq.authUserId;
            if (!authUserId) {
                res.status(401).json({ error: 'Unauthorized' });
                return;
            }

            const displayNameParts = String(authReq.authDisplayName || '').trim().split(/\s+/).filter(Boolean);
            const profile = await getCurrentUserProfile(authUserId, {
                email: authReq.authEmail,
                role: authReq.authRole,
                first_name: displayNameParts[0] || '',
                last_name: displayNameParts.slice(1).join(' '),
                trade_enable: true,
            }, supabaseService);

            res.json({ profile });
        });

        this.app.patch('/api/me/profile', this.requireAuth, async (req, res) => {
            const authReq = req as AuthenticatedRequest;
            const authUserId = authReq.authUserId;
            if (!authUserId) {
                res.status(401).json({ error: 'Unauthorized' });
                return;
            }

            const displayNameParts = String(authReq.authDisplayName || '').trim().split(/\s+/).filter(Boolean);
            try {
                const profile = await saveCurrentUserProfile(authUserId, req.body || {}, {
                    email: authReq.authEmail,
                    role: authReq.authRole,
                    first_name: displayNameParts[0] || '',
                    last_name: displayNameParts.slice(1).join(' '),
                    trade_enable: true,
                }, supabaseService);
                res.json({ profile });
            } catch (error: any) {
                res.status(400).json({ error: `Failed to update profile: ${error.message}` });
            }
        });

        this.app.get('/api/profiles', this.requireAuth, async (req, res) => {
            const authUserId = (req as AuthenticatedRequest).authUserId;
            if (!authUserId) {
                res.status(401).json({ error: 'Unauthorized' });
                return;
            }

            try {
                const ensureDefault = String(req.query.ensureDefault || '').toLowerCase() === 'true';
                const scope = String(req.query.scope || 'user').toLowerCase();
                const wantsAll = scope === 'all';
                const isAdmin = wantsAll ? await isTradingAdmin(authUserId, (req as AuthenticatedRequest).authRole) : false;

                let profiles;
                if (ensureDefault && !wantsAll) {
                    profiles = await ensureDefaultTradeProfileForUser(authUserId, supabaseService);
                } else if (wantsAll) {
                    if (!isAdmin) {
                        res.status(403).json({ error: 'Forbidden: Admin role required' });
                        return;
                    }
                    profiles = await listAllTradeProfiles(supabaseService);
                } else {
                    profiles = await listTradeProfilesForUser(authUserId, supabaseService);
                }
                res.json({ profiles });
            } catch (error: any) {
                res.status(500).json({ error: `Failed to load profiles: ${error.message}` });
            }
        });

        this.app.post('/api/profiles', this.requireAuth, async (req, res) => {
            const authReq = req as AuthenticatedRequest;
            const authUserId = authReq.authUserId;
            if (!authUserId) {
                res.status(401).json({ error: 'Unauthorized' });
                return;
            }

            try {
                const isAdmin = await isTradingAdmin(authUserId, authReq.authRole);
                const requestedUserId = String(req.body?.user_id || '').trim();
                const targetUserId = isAdmin && requestedUserId ? requestedUserId : authUserId;
                const profile = await saveTradeProfileForUser(req.body || {}, targetUserId, supabaseService);
                res.status(201).json({ profile });
            } catch (error: any) {
                res.status(400).json({ error: `Failed to save profile: ${error.message}` });
            }
        });

        this.app.put('/api/profiles/:id', this.requireAuth, async (req, res) => {
            const authReq = req as AuthenticatedRequest;
            const authUserId = authReq.authUserId;
            if (!authUserId) {
                res.status(401).json({ error: 'Unauthorized' });
                return;
            }

            try {
                const profileId = String(req.params.id || '').trim();
                const isAdmin = await isTradingAdmin(authUserId, authReq.authRole);
                const existingProfiles = isAdmin
                    ? await listAllTradeProfiles(supabaseService)
                    : await listTradeProfilesForUser(authUserId, supabaseService);
                const existing = existingProfiles.find((profile) => profile.id === profileId);
                if (!existing) {
                    res.status(404).json({ error: 'Profile not found' });
                    return;
                }

                const profile = await saveTradeProfileForUser({
                    ...existing,
                    ...(req.body || {}),
                    id: profileId,
                }, existing.user_id, supabaseService);
                res.json({ profile });
            } catch (error: any) {
                res.status(400).json({ error: `Failed to update profile: ${error.message}` });
            }
        });

        this.app.patch('/api/profiles/:id/active', this.requireAuth, async (req, res) => {
            const authReq = req as AuthenticatedRequest;
            const authUserId = authReq.authUserId;
            if (!authUserId) {
                res.status(401).json({ error: 'Unauthorized' });
                return;
            }

            try {
                const profileId = String(req.params.id || '').trim();
                const isAdmin = await isTradingAdmin(authUserId, authReq.authRole);
                const existingProfiles = isAdmin
                    ? await listAllTradeProfiles(supabaseService)
                    : await listTradeProfilesForUser(authUserId, supabaseService);
                const existing = existingProfiles
                    .find((profile) => profile.id === profileId);
                if (!existing) {
                    res.status(404).json({ error: 'Profile not found' });
                    return;
                }

                const profile = await saveTradeProfileForUser({
                    ...existing,
                    is_active: Boolean(req.body?.is_active),
                }, existing.user_id, supabaseService);
                res.json({ profile });
            } catch (error: any) {
                res.status(400).json({ error: `Failed to update profile state: ${error.message}` });
            }
        });

        this.app.delete('/api/profiles/:id', this.requireAuth, async (req, res) => {
            const authReq = req as AuthenticatedRequest;
            const authUserId = authReq.authUserId;
            if (!authUserId) {
                res.status(401).json({ error: 'Unauthorized' });
                return;
            }

            try {
                const profileId = String(req.params.id || '').trim();
                const isAdmin = await isTradingAdmin(authUserId, authReq.authRole);
                const existingProfiles = isAdmin
                    ? await listAllTradeProfiles(supabaseService)
                    : await listTradeProfilesForUser(authUserId, supabaseService);
                const existing = existingProfiles.find((profile) => profile.id === profileId);
                if (!existing) {
                    res.status(404).json({ error: 'Profile not found' });
                    return;
                }

                await deleteTradeProfileForUser(profileId, existing.user_id, supabaseService);
                res.json({ success: true });
            } catch (error: any) {
                res.status(400).json({ error: `Failed to delete profile: ${error.message}` });
            }
        });

        this.app.get('/api/admin/config/dynamic', this.requireAuth, this.requireAdmin, async (_req, res) => {
            try {
                const items = await listDynamicConfigEntries(supabaseService);
                res.json({ items });
            } catch (error: any) {
                res.status(500).json({ error: `Failed to load dynamic config: ${error.message}` });
            }
        });

        this.app.put('/api/admin/config/dynamic', this.requireAuth, this.requireAdmin, async (req, res) => {
            try {
                const items = Array.isArray(req.body?.items) ? req.body.items : [];
                await upsertDynamicConfigEntries(items, supabaseService);
                applyDynamicConfigEntries(items);
                await loadDynamicConfig(supabaseService);
                res.json({ success: true });
            } catch (error: any) {
                res.status(500).json({ error: `Failed to update dynamic config: ${error.message}` });
            }
        });

        this.app.post('/api/backtest/run', this.requireAuth, async (req, res) => {
            const authUserId = (req as AuthenticatedRequest).authUserId;
            if (!authUserId) {
                res.status(401).json({ success: false, error: 'Unauthorized' });
                return;
            }
            if (!this.enforceRateLimit(req as AuthenticatedRequest, res, 'backtest')) {
                return;
            }
            if (!config.ENABLE_BACKTEST) {
                res.status(404).json({ success: false, error: 'Backtest feature is disabled' });
                return;
            }

            const isAdmin = await supabaseService.isAdmin(authUserId);
            if (!isAdmin && !config.BACKTEST_CUSTOMER_ENABLED) {
                res.status(403).json({
                    success: false,
                    error: 'Backtest is restricted to admin users. Ask an admin to enable customer backtest access.'
                });
                return;
            }

            try {
                const body = req.body || {};
                const profileId = String(body.profileId || '').trim();
                let profileSettings: any = undefined;
                if (profileId) {
                    profileSettings = await supabaseService.getProfileForBacktest(profileId, authUserId);
                    if (!profileSettings) {
                        res.status(404).json({ success: false, error: 'Backtest profile not found for current user' });
                        return;
                    }
                }

                const symbols = this.parseSymbolList(body.symbols || profileSettings?.symbols);
                if (!symbols.length) {
                    res.status(400).json({ success: false, error: 'At least one symbol is required for backtest.' });
                    return;
                }

                const strategyConfig = body.strategyConfig || profileSettings?.strategy_config;
                if (!strategyConfig) {
                    res.status(400).json({ success: false, error: 'strategyConfig is required (or supply profileId with saved strategy).' });
                    return;
                }

                this.enforceBacktestPayloadGuards(body.dataSource);
                const timeframe = this.normalizeBacktestTimeframe(body.timeframe);

                const backtestRequest: BacktestRequest = {
                    mode: String(body.mode || 'backtest') as BacktestRequest['mode'],
                    profileId: profileId || undefined,
                    strategyConfig,
                    symbols,
                    timeframe,
                    dateRange: {
                        from: String(body?.dateRange?.from || ''),
                        to: String(body?.dateRange?.to || '')
                    },
                    dataSource: body.dataSource,
                    execution: body.execution
                };

                const executionProfileSettings = {
                    ...profileSettings,
                    strategy_config: strategyConfig,
                    symbols: symbols.join(','),
                    allocated_capital: Number(body?.execution?.initialCapitalUsd ?? (profileSettings?.allocated_capital || config.TOTAL_CAPITAL)),
                    risk_per_trade_percent: Number(profileSettings?.risk_per_trade_percent || config.PRO_STRATEGY.PARAMETERS.RISK_PER_TRADE * 100)
                };

                const result = await runBacktest(backtestRequest, {
                    profileSettings: executionProfileSettings
                });

                this.auditTradeEvent({
                    event: 'backtest_run',
                    userId: authUserId,
                    profileId: profileId || undefined,
                    outcome: 'accepted',
                    details: {
                        symbols,
                        timeframe,
                        from: backtestRequest.dateRange.from,
                        to: backtestRequest.dateRange.to
                    }
                });

                res.json({ success: true, result });
            } catch (error: any) {
                this.auditTradeEvent({
                    event: 'backtest_run',
                    userId: authUserId,
                    profileId: String(req.body?.profileId || '').trim() || undefined,
                    outcome: 'error',
                    reason: error.message
                });
                res.status(400).json({ success: false, error: error.message || 'Backtest run failed' });
            }
        });

        // --- AI Health Endpoint ---
        this.app.get('/api/ai/health', this.requireAuth, async (req, res) => {
            const authUserId = (req as AuthenticatedRequest).authUserId;
            if (!this.enforceRateLimit(req as AuthenticatedRequest, res, 'chat')) {
                return;
            }

            const probeParam = String(req.query.probe || '').toLowerCase();
            const probe = probeParam === '1' || probeParam === 'true' || probeParam === 'yes';

            try {
                const providers = await this.aiClient.getProviderHealth(probe);
                const configuredProviders = providers
                    .filter((p) => p.configured)
                    .map((p) => p.provider);
                const healthyProviders = providers
                    .filter((p) => p.status === 'ok' || p.status === 'configured')
                    .map((p) => p.provider);

                this.auditTradeEvent({
                    event: 'ai_health_check',
                    userId: authUserId,
                    outcome: 'accepted',
                    details: { probe, configuredProviders, healthyProviders }
                });

                res.json({
                    success: true,
                    probe,
                    failOpen: config.AI.FAIL_OPEN,
                    fallbackList: config.AI.FALLBACK_LIST,
                    providers,
                    summary: {
                        configuredCount: configuredProviders.length,
                        healthyCount: healthyProviders.length,
                        anyUsable: healthyProviders.length > 0
                    }
                });
            } catch (error: any) {
                this.auditTradeEvent({
                    event: 'ai_health_check',
                    userId: authUserId,
                    outcome: 'error',
                    reason: error.message
                });
                res.status(500).json({ success: false, error: `AI health check failed: ${error.message}` });
            }
        });

        // --- NEW: Manual Trade Execution Endpoint ---
        this.app.post('/api/trade', this.requireAuth, async (req, res) => {
            const authUserId = (req as AuthenticatedRequest).authUserId;
            const { profile_id, symbol, side, qty, type, price, sl, tp } = req.body;

            if (!symbol || !side || !qty) {
                return res.status(400).json({ success: false, error: "Missing required fields" });
            }
            if (!authUserId) {
                return res.status(401).json({ success: false, error: "Unauthorized" });
            }
            if (!this.enforceRateLimit(req as AuthenticatedRequest, res, 'trade')) {
                return;
            }

            this.auditTradeEvent({
                event: 'trade_request',
                userId: authUserId,
                profileId: profile_id,
                symbol,
                details: { side, qty, type: type || 'market' }
            });

            // Find manager
            let manager = profile_id ? this.executionManagers.get(profile_id) : undefined;
            if (manager && manager.getUserId() !== authUserId) {
                this.auditTradeEvent({
                    event: 'trade_request',
                    userId: authUserId,
                    profileId: profile_id,
                    symbol,
                    outcome: 'rejected',
                    reason: 'profile ownership mismatch'
                });
                return res.status(403).json({ success: false, error: 'Forbidden: profile does not belong to authenticated user' });
            }
            if (!manager && !profile_id) {
                const userManagers = Array.from(this.executionManagers.values()).filter(m => m.getUserId() === authUserId);
                if (userManagers.length === 1) {
                    manager = userManagers[0];
                } else if (userManagers.length > 1) {
                    return res.status(400).json({ success: false, error: 'Multiple profiles found. Please provide profile_id.' });
                }
            }

            if (!manager) {
                this.auditTradeEvent({
                    event: 'trade_request',
                    userId: authUserId,
                    profileId: profile_id,
                    symbol,
                    outcome: 'rejected',
                    reason: 'manual trader unavailable'
                });
                return res.status(503).json({ error: 'No Manual Trader available.' });
            }

            try {
                const livePrice = Number(this.state.symbols[symbol]?.price || 0);
                const priceHint = Number(price) > 0 ? Number(price) : (livePrice > 0 ? livePrice : undefined);
                const result = await manager.executeRequest(
                    symbol,
                    side,
                    qty,
                    type || 'market',
                    price,
                    priceHint,
                    authUserId,
                    sl,
                    tp
                );
                if (result.success) {
                    this.auditTradeEvent({
                        event: 'trade_request',
                        userId: authUserId,
                        profileId: profile_id,
                        symbol,
                        outcome: 'accepted',
                        details: { orderId: result.orderId }
                    });
                    res.json(result);
                } else {
                    this.auditTradeEvent({
                        event: 'trade_request',
                        userId: authUserId,
                        profileId: profile_id,
                        symbol,
                        outcome: 'rejected',
                        reason: result.error || 'execution returned failure'
                    });
                    res.status(500).json(result);
                }
            } catch (error: any) {
                this.auditTradeEvent({
                    event: 'trade_request',
                    userId: authUserId,
                    profileId: profile_id,
                    symbol,
                    outcome: 'error',
                    reason: error.message
                });
                res.status(500).json({ error: error.message });
            }
        });

        // --- NEW: Close Position Endpoint (Square Off) ---
        this.app.post('/api/close', this.requireAuth, async (req, res) => {
            const authUserId = (req as AuthenticatedRequest).authUserId;
            const { profile_id, symbol } = req.body;
            logger.info(`[API] Received Square Off request for ${symbol} (Profile: ${profile_id}, AuthUser: ${authUserId})`);

            if (!symbol) {
                return res.status(400).json({ success: false, error: "Missing symbol" });
            }
            if (!authUserId) {
                return res.status(401).json({ success: false, error: "Unauthorized" });
            }
            if (!this.enforceRateLimit(req as AuthenticatedRequest, res, 'close')) {
                return;
            }

            this.auditTradeEvent({
                event: 'close_request',
                userId: authUserId,
                profileId: profile_id,
                symbol
            });

            // Find manager
            let manager = profile_id ? this.executionManagers.get(profile_id) : undefined;
            if (manager && manager.getUserId() !== authUserId) {
                this.auditTradeEvent({
                    event: 'close_request',
                    userId: authUserId,
                    profileId: profile_id,
                    symbol,
                    outcome: 'rejected',
                    reason: 'profile ownership mismatch'
                });
                return res.status(403).json({ success: false, error: 'Forbidden: profile does not belong to authenticated user' });
            }
            if (!manager) {
                const userManagers = Array.from(this.executionManagers.values()).filter(m => m.getUserId() === authUserId);
                const matchingManagers = userManagers.filter(m => !!m.getActivePosition(symbol));

                if (matchingManagers.length > 1) {
                    return res.status(400).json({ success: false, error: 'Multiple matching positions found. Please pass profile_id.' });
                }
                manager = matchingManagers[0];
            }

            if (!manager) {
                this.auditTradeEvent({
                    event: 'close_request',
                    userId: authUserId,
                    profileId: profile_id,
                    symbol,
                    outcome: 'rejected',
                    reason: 'execution manager unavailable'
                });
                return res.status(503).json({ error: 'No Execution Manager available.' });
            }

            const activePos = manager.getActivePosition(symbol);
            if (!activePos) {
                this.auditTradeEvent({
                    event: 'close_request',
                    userId: authUserId,
                    profileId: profile_id,
                    symbol,
                    outcome: 'rejected',
                    reason: 'no active position'
                });
                return res.status(404).json({ error: 'No active position found for this symbol.' });
            }

            try {
                // Get current price from API state
                const currentPrice = this.state.symbols[symbol]?.price || 0;
                const symbolPositions = manager.getActivePositions(symbol);
                for (const pos of symbolPositions) {
                    await manager.executeExit(symbol, currentPrice, 'Manual Square Off', pos.tradeId);
                }
                this.auditTradeEvent({
                    event: 'close_request',
                    userId: authUserId,
                    profileId: profile_id,
                    symbol,
                    outcome: 'accepted'
                });
                res.json({ success: true, message: `Squared off ${symbol}` });
            } catch (error: any) {
                this.auditTradeEvent({
                    event: 'close_request',
                    userId: authUserId,
                    profileId: profile_id,
                    symbol,
                    outcome: 'error',
                    reason: error.message
                });
                res.status(500).json({ success: false, error: error.message });
            }
        });

        // --- NEW: Clear Operational Events ---
        this.app.delete('/api/events', this.requireAuth, this.requireAdmin, async (req, res) => {
            try {
                observabilityService.clearEvents();
                this.state.operationalEvents = [];
                this.emitToConnectedUsers('operational_event_cleared', () => ({ success: true }));
                res.json({ success: true, message: 'Operational events cleared' });
            } catch (error: any) {
                res.status(500).json({ success: false, error: error.message });
            }
        });

        // --- Reconciliation EXIT Backfill Audit (Admin) ---
        this.app.get('/api/reconciliation/backfill/audit', this.requireAuth, this.requireAdmin, async (req, res) => {
            try {
                const profileId = String(req.query.profileId || '').trim();
                const symbol = String(req.query.symbol || '').trim();
                const batchId = String(req.query.batchId || '').trim();
                const fromParam = String(req.query.from || '').trim();
                const toParam = String(req.query.to || '').trim();
                const days = Math.max(0, Math.min(365, Number.parseInt(String(req.query.days || '7'), 10) || 0));
                const limit = Math.max(1, Math.min(500, Number.parseInt(String(req.query.limit || '100'), 10) || 100));
                const offset = Math.max(0, Number.parseInt(String(req.query.offset || '0'), 10) || 0);
                const decisionParam = String(req.query.decision || '').trim();
                const decisions = decisionParam
                    ? decisionParam.split(',').map((value) => value.trim()).filter(Boolean)
                    : [];

                let fromIso = fromParam;
                if (!fromIso && days > 0) {
                    fromIso = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
                }
                const toIso = toParam || undefined;

                const result = await supabaseService.getReconciliationBackfillAuditRows({
                    profileId: profileId || undefined,
                    symbol: symbol || undefined,
                    batchId: batchId || undefined,
                    decisions,
                    fromIso,
                    toIso,
                    limit,
                    offset
                });

                res.json({
                    success: true,
                    filters: {
                        profileId: profileId || null,
                        symbol: symbol || null,
                        batchId: batchId || null,
                        decisions,
                        from: fromIso || null,
                        to: toIso || null,
                        days
                    },
                    pagination: {
                        limit,
                        offset,
                        totalCount: result.totalCount,
                        hasMore: offset + result.rows.length < result.totalCount
                    },
                    rows: result.rows
                });
            } catch (error: any) {
                res.status(500).json({ success: false, error: error.message });
            }
        });

        this.app.get('/api/reconciliation/backfill/batches', this.requireAuth, this.requireAdmin, async (req, res) => {
            try {
                const profileId = String(req.query.profileId || '').trim();
                const symbol = String(req.query.symbol || '').trim();
                const fromParam = String(req.query.from || '').trim();
                const toParam = String(req.query.to || '').trim();
                const days = Math.max(0, Math.min(365, Number.parseInt(String(req.query.days || '7'), 10) || 0));
                const limit = Math.max(1, Math.min(100, Number.parseInt(String(req.query.limit || '20'), 10) || 20));

                let fromIso = fromParam;
                if (!fromIso && days > 0) {
                    fromIso = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
                }
                const toIso = toParam || undefined;

                const batches = await supabaseService.getReconciliationBackfillBatchSummaries({
                    profileId: profileId || undefined,
                    symbol: symbol || undefined,
                    fromIso,
                    toIso,
                    limit
                });

                res.json({
                    success: true,
                    filters: {
                        profileId: profileId || null,
                        symbol: symbol || null,
                        from: fromIso || null,
                        to: toIso || null,
                        days
                    },
                    batches
                });
            } catch (error: any) {
                res.status(500).json({ success: false, error: error.message });
            }
        });

        // --- Chat-based Profile Control ---
        this.app.post('/api/chat', this.requireAuth, async (req, res) => {
            const authUserId = (req as AuthenticatedRequest).authUserId;
            if (!this.enforceRateLimit(req as AuthenticatedRequest, res, 'chat')) {
                return;
            }

            const { message, context } = req.body;
            if (!message) {
                return res.status(400).json({ error: 'Message is required' });
            }

            this.auditTradeEvent({
                event: 'chat_profile_control',
                userId: authUserId,
                details: {
                    messageLength: typeof message === 'string' ? message.length : 0
                }
            });

            const systemPrompt = `You are the AI assistant for the Bytelyst Trading Platform. You translate plain English instructions into structured trading profile configurations.

AVAILABLE RULES (use these exact ruleId values):
- TrendBiasRule: EMA50/200 trend direction check. Params: { fastPeriod: number, slowPeriod: number }
- MomentumRule: RSI overbought/oversold logic. Params: { rsiPeriod: number, overbought: number, oversold: number }
- ZoneRule: Price proximity to EMA zones. Params: { zonePercent: number }
- SessionRule: Trading session filter. Params: { sessions: string (comma-separated: "London,NY,Tokyo,Sydney") }
- EntryTriggerRule: Pattern-based entry. Params: { showPatterns: boolean }
- RiskManagementRule: ATR-based risk limits. Params: { maxRisk: number }
- AIAnalysisRule: LLM sentiment analysis. Params: { minConfidence: number (0-1) }

PROFILE SCHEMA:
{
  "action": "create_profile" | "update_profile" | "explain",
  "profile": {
    "name": string,
    "allocated_capital": number,
    "risk_per_trade_percent": number,
    "symbols": string (comma-separated, e.g. "BTC/USDT, ETH/USDT"),
    "is_active": boolean,
    "strategy_config": {
      "rules": [ { "ruleId": string, "enabled": boolean, "params": {...} } ],
      "riskLimits": { "maxDailyLossUsd": number, "maxOpenTrades": number, "maxConsecutiveLosses": number },
      "execution": { "orderType": "market" | "limit", "cooldownMinutes": number, "entryMode": "both" | "long_only" }
    }
  },
  "summary": string (1-2 sentence human-readable summary of what you did),
  "reasoning": string (brief explanation of why you chose these parameters)
}

CURRENT CONTEXT (existing profiles):
${context ? JSON.stringify(context, null, 2) : 'No existing profiles.'}

RULES:
1. For "create_profile": generate a complete profile with sensible defaults based on the user's description.
2. For "update_profile": include the profile "id" field and only change what the user asked for. Keep everything else the same.
3. For "explain": just set action to "explain" and put your answer in "summary". No profile needed.
4. Match the user's risk appetite: "conservative" = low risk (0.5-1%), low capital. "aggressive" = higher risk (2-5%), more rules enabled.
5. Always include at least TrendBiasRule and RiskManagementRule as enabled for safety.
6. Output ONLY valid JSON. No markdown, no backticks, no explanation outside the JSON.`;

            try {
                let aiResponse: string | null = null;
                try {
                    aiResponse = await this.aiClient.generateAnalysis(
                        `${systemPrompt}\n\nUser message: "${message}"`
                    );
                } catch (aiError: any) {
                    logger.error(`[Chat] AI provider chain failed: ${aiError.message}`);
                }

                if (!aiResponse) {
                    const fallback = this.buildLocalChatFallback(message, Array.isArray(context) ? context : []);
                    this.auditTradeEvent({
                        event: 'chat_profile_control',
                        userId: authUserId,
                        outcome: 'accepted',
                        details: { action: fallback.action, fallback: 'local_deterministic' }
                    });
                    return res.json(fallback);
                }

                // Parse the JSON from AI response (handle markdown code blocks if present)
                let parsed: any;
                try {
                    const cleaned = aiResponse.replace(/```json\n?/g, '').replace(/```\n?/g, '').trim();
                    parsed = JSON.parse(cleaned);
                } catch (parseErr) {
                    logger.error(`[Chat] Failed to parse AI response: ${aiResponse}`);
                    const fallback = this.buildLocalChatFallback(message, Array.isArray(context) ? context : []);
                    return res.json({
                        ...fallback,
                        reasoning: `${fallback.reasoning} AI output was non-JSON, so local fallback parsing was used.`
                    });
                }

                logger.info(`[Chat] Action: ${parsed.action}, Summary: ${parsed.summary}`);
                this.auditTradeEvent({
                    event: 'chat_profile_control',
                    userId: authUserId,
                    outcome: 'accepted',
                    details: { action: parsed.action }
                });
                res.json(parsed);
            } catch (error: any) {
                logger.error(`[Chat] Error: ${error.message}`);
                this.auditTradeEvent({
                    event: 'chat_profile_control',
                    userId: authUserId,
                    outcome: 'error',
                    reason: error.message
                });
                res.status(500).json({ error: `Chat failed: ${error.message}` });
            }
        });
    }

    private setupSocketHandlers() {
        this.io.use(async (socket, next) => {
            const authToken = typeof socket.handshake.auth?.token === 'string'
                ? socket.handshake.auth.token
                : this.extractBearerToken(socket.handshake.headers.authorization);

            if (!authToken) {
                next(new Error('Unauthorized: missing token'));
                return;
            }

            const { userId, role, error } = await verifyTradingAccessToken(authToken);
            if (!userId) {
                next(new Error(`Unauthorized: ${error || 'invalid token'}`));
                return;
            }

            socket.data.userId = userId;
            socket.data.authRole = role;
            socket.data.isAdmin = await isTradingAdmin(userId, role);
            next();
        });

        this.io.on('connection', (socket) => {
            const userId = String(socket.data.userId || '').trim();
            logger.info(`[API] Dashboard connected: ${socket.id} (user: ${userId || 'unknown'})`);
            if (userId) {
                this.trackSocket(userId, socket);
                const scopedState = this.getScopedState(userId, !!socket.data.isAdmin);
                socket.emit('state', scopedState);
            } else {
                socket.emit('state', {
                    symbols: {},
                    positions: [],
                    alerts: [],
                    orders: [],
                    history: [],
                    settings: this.state.settings,
                    health: healthTracker.getSnapshot(),
                    uptime: this.state.uptime,
                    accountSnapshot: null,
                    orderFailures: [],
                    operationalEvents: []
                } as BotState);
            }

            socket.on('disconnect', () => {
                if (userId) {
                    this.untrackSocket(userId, socket.id);
                }
                logger.info(`[API] Dashboard disconnected: ${socket.id}`);
            });
        });
    }

    private startServer() {
        this.httpServer.listen(this.port, () => {
            logger.info(`[API] Server running on port ${this.port}`);
        });
    }

    public updateSymbol(symbol: string, data: Partial<BotState['symbols'][string]>) {
        if (!this.state.symbols[symbol]) {
            this.state.symbols[symbol] = {
                price: 0,
                change24h: 0,
                changeToday: 0,
                session: 'Unknown',
                volatility: 'Low',
                signal: 'NONE',
                tradingMode: 'Alerts',
                activePosition: null,
                priceHistory: [],
                rules: {},
                profileSignals: {},
                indicators: {}
            };
        }

        if (data.price !== undefined) {
            this.state.symbols[symbol].priceHistory.push({
                timestamp: Date.now(),
                price: data.price
            });
            if (this.state.symbols[symbol].priceHistory.length > 60) {
                this.state.symbols[symbol].priceHistory = this.state.symbols[symbol].priceHistory.slice(-60);
            }
        }

        this.state.symbols[symbol] = {
            ...this.state.symbols[symbol],
            ...data
        };

        // --- NEW: Real-time P/L Update for Positions ---
        if (data.price) {
            let positionsChanged = false;
            const currentPrice = data.price;

            this.state.positions = this.state.positions.map(pos => {
                if (pos.symbol === symbol) {
                    positionsChanged = true;
                    // Calculate P/L
                    const diff = currentPrice - pos.entryPrice;
                    const direction = pos.side === 'BUY' ? 1 : -1;
                    const pnl = diff * pos.size * direction;
                    const pnlPercent = (diff / pos.entryPrice) * 100 * direction;

                    return {
                        ...pos,
                        currentPrice: currentPrice,
                        unrealizedPnl: Number(pnl.toFixed(2)),
                        unrealizedPnlPercent: Number(pnlPercent.toFixed(2)),
                        marketValue: Number((pos.size * currentPrice).toFixed(2))
                    };
                }
                return pos;
            });

            if (positionsChanged) {
                this.broadcastPositionsUpdate();
            }
        }

        this.broadcastSymbolUpdate(symbol);
        this.saveState();
    }

    public addAlert(
        type: BotState['alerts'][0]['type'],
        symbol: string,
        message: string,
        context?: { userId?: string; profileId?: string }
    ) {
        const alert = {
            timestamp: Date.now(),
            type,
            symbol,
            message,
            userId: context?.userId,
            profileId: context?.profileId
        };
        this.state.alerts.push(alert);
        if (this.state.alerts.length > 100) {
            this.state.alerts = this.state.alerts.slice(-100);
        }
        const owner = this.resolveProfileOwner(alert.profileId, alert.userId);
        if (owner) {
            this.emitToUser(owner, 'new_alert', alert);
        } else {
            this.emitToConnectedUsers('new_alert', () => alert);
        }
        this.saveState();
    }

    public updatePositions(positions: BotState['positions'], sourceId: string = 'global') {
        const sourceProfileId = sourceId === 'global' ? undefined : sourceId;
        // Enrich incoming positions with current P/L calculations before storing
        const enrichedPositions = positions.map(pos => {
            const currentPrice = this.state.symbols[pos.symbol]?.price || pos.entryPrice;
            const diff = currentPrice - pos.entryPrice;
            const direction = pos.side === 'BUY' ? 1 : -1;
            const pnl = diff * pos.size * direction;
            const pnlPercent = (diff / pos.entryPrice) * 100 * direction;
            const resolvedUserId = this.resolveProfileOwner(pos.profileId || sourceProfileId, pos.userId);

            return {
                ...pos,
                currentPrice: currentPrice,
                unrealizedPnl: Number(pnl.toFixed(2)),
                unrealizedPnlPercent: Number(pnlPercent.toFixed(2)),
                marketValue: Number((pos.size * currentPrice).toFixed(2)),
                userId: pos.userId || resolvedUserId || undefined,
                profileId: pos.profileId || sourceProfileId
            };
        });

        // Global updates are full snapshots from the trading loop; treat them as authoritative.
        if (sourceId === 'global') {
            this.profilePositionsList.clear();
        }
        this.profilePositionsList.set(sourceId, enrichedPositions);
        const merged = mergePositionSnapshots(Array.from(this.profilePositionsList.values()));
        this.state.positions = merged;
        this.broadcastPositionsUpdate();
        this.saveState();
    }

    public updateOrders(orders: BotState['orders'], sourceId: string = 'global') {
        const inferredProfileId = sourceId === 'global' ? undefined : sourceId;
        const normalizedOrders = orders.map((order) => ({
            ...order,
            trade_id: order.trade_id || (order as any).tradeId,
            subTag: order.subTag || (order as any).subtag || (order as any).sub_tag,
            profileId: order.profileId || inferredProfileId,
            userId: order.userId || this.resolveProfileOwner(order.profileId || inferredProfileId),
            source: order.source || ((order.profileId || inferredProfileId) ? 'BOT' : 'MANUAL')
        }));

        if (sourceId === 'global') {
            this.profileOrdersList.clear();
        }
        this.profileOrdersList.set(sourceId, normalizedOrders);
        const merged = mergeOrderSnapshots(Array.from(this.profileOrdersList.values()));
        this.state.orders = merged;
        this.broadcastOrdersUpdate();
        this.saveState();
    }

    public addHistory(trade: BotState['history'][0]) {
        const resolvedUserId = this.resolveProfileOwner(trade.profileId, trade.userId);
        const normalizedTrade: BotState['history'][0] = {
            ...trade,
            userId: trade.userId || resolvedUserId || undefined,
            source: trade.source || (trade.profileId ? 'BOT' : 'MANUAL')
        };

        this.state.history.push(normalizedTrade);
        if (this.state.history.length > 100) {
            this.state.history = this.state.history.slice(-100);
        }
        this.broadcastHistoryUpdate(normalizedTrade);
        this.saveState();
    }

    public updateAccountSnapshot(snapshot: AccountSnapshot) {
        const enrichedSnapshot: AccountSnapshot = {
            ...snapshot,
            timestamp: snapshot.timestamp || Date.now()
        };
        this.state.accountSnapshot = enrichedSnapshot;
        this.accountSnapshotCache = [...this.accountSnapshotCache, enrichedSnapshot].slice(-25);
        const owner = this.resolveProfileOwner(snapshot.profileId, snapshot.userId);
        if (owner) {
            this.emitToUser(owner, 'account_snapshot', enrichedSnapshot);
        } else {
            this.emitToConnectedUsers('account_snapshot', () => enrichedSnapshot);
        }
    }

    public broadcast(event: string, data: any) {
        this.emitToConnectedUsers(event, () => data);
    }

    public recordOrderFailure(failure: OrderFailureRecord) {
        const enrichedFailure: OrderFailureRecord = {
            ...failure,
            timestamp: failure.timestamp || Date.now()
        };
        this.state.orderFailures = [...this.state.orderFailures, enrichedFailure].slice(-30);
        const owner = this.resolveProfileOwner(failure.profileId, failure.userId);
        if (owner) {
            this.emitToUser(owner, 'order_failure', enrichedFailure);
        } else {
            this.emitToConnectedUsers('order_failure', () => enrichedFailure);
        }
    }

    public updateSettings(settings: Partial<BotState['settings']>) {
        this.state.settings = { ...this.state.settings, ...settings };
        this.broadcastSettingsUpdate();
        this.saveState();
    }

    public updateRuntimeHealth(update: Partial<RuntimeHealth>) {
        this.runtimeHealth = {
            ...this.runtimeHealth,
            ...update
        };
    }

    public publishHealthSnapshot(options?: { broadcast?: boolean; force?: boolean }): HealthSnapshot {
        const snapshot = healthTracker.getSnapshot();
        this.state.health = snapshot;

        if (options?.broadcast) {
            const now = Date.now();
            const shouldBroadcast = options.force || (now - this.lastHealthBroadcastAt) >= this.healthBroadcastMinIntervalMs;
            if (shouldBroadcast) {
                this.lastHealthBroadcastAt = now;
                this.emitToConnectedUsers('health_update', () => snapshot);
            }
        }

        return snapshot;
    }

    public pruneSymbols(activeSymbols: string[]) {
        const currentSymbols = Object.keys(this.state.symbols);
        let changed = false;

        currentSymbols.forEach(s => {
            if (!activeSymbols.includes(s)) {
                delete this.state.symbols[s];
                changed = true;
                logger.info(`[API] Pruned legacy symbol: ${s}`);
            }
        });

        if (changed) {
            this.saveState();
        }
    }

    public getState(): BotState {
        return this.state;
    }

    public async stop(): Promise<void> {
        if (!this.httpServer.listening) {
            return;
        }

        this.io.close();
        await new Promise<void>((resolve, reject) => {
            this.httpServer.close((err?: Error) => {
                if (err) {
                    reject(err);
                    return;
                }
                resolve();
            });
        });
    }
}
