import assert from 'node:assert/strict';
import { TradeExecutor } from '../src/services/TradeExecutor.js';
import { SignalDirection } from '../src/strategies/rules/types.js';
import { Candle, IExchangeConnector } from '../src/connectors/types.js';
import { supabaseService } from '../src/services/SupabaseService.js';
import { distributedLockService } from '../src/services/distributedLockService.js';
import { observabilityService } from '../src/services/observabilityService.js';
import { capitalLedger } from '../src/services/CapitalLedger.js';

const normalizeError = (reason: unknown): Error => {
    if (reason instanceof Error) return reason;
    if (reason && typeof reason === 'object') return new Error(JSON.stringify(reason));
    return new Error(String(reason ?? 'Unknown error'));
};

const logAndRethrow = (kind: string, reason: unknown) => {
    const err = normalizeError(reason);
    console.error(`[testTradeExecutorLifecycle] ${kind}`, err);
    throw err;
};

process.on('unhandledRejection', (reason) => logAndRethrow('unhandled rejection', reason));
process.on('uncaughtException', (error) => logAndRethrow('uncaught exception', error));

console.log('[testTradeExecutorLifecycle] script started');

class MockExchangeConnector implements IExchangeConnector {
    private statuses: string[] = [];
    private orderSeq = 0;
    private cancelSuccess = true;
    private syncedPosition: any | null = null;

    public setCancelResult(shouldSucceed: boolean) {
        this.cancelSuccess = shouldSucceed;
    }

    public setPosition(position: any | null) {
        this.syncedPosition = position;
    }

    public queueStatuses(...statuses: string[]) {
        this.statuses.push(...statuses.map((s) => s.toLowerCase()));
    }

    fetchOHLCV = async (_symbol: string, _timeframe: string, _limit?: number): Promise<Candle[]> => {
        return [
            {
                timestamp: Date.now(),
                open: 100,
                high: 101,
                low: 99,
                close: 100,
                volume: 1
            }
        ];
    };
    placeOrder = async (symbol: string, side: 'buy' | 'sell', qty: number, type: 'market' | 'limit', price?: number): Promise<any> => {
        this.orderSeq += 1;
        return {
            id: `MOCK-${this.orderSeq}`,
            symbol,
            side,
            qty,
            type,
            status: 'pending_new',
            filled_avg_price: price || 100,
            filled_qty: `${qty}`
        };
    }

    getPosition = async (_symbol: string): Promise<any> => this.syncedPosition;

    getOrder = async (orderId: string): Promise<any> => {
        const status = this.statuses.shift() || 'filled';
        return {
            id: orderId,
            status,
            filled_avg_price: 100,
            filled_qty: '1'
        };
    }

    cancelOrder = async (_orderId: string, _symbol?: string): Promise<boolean> => this.cancelSuccess;
    getCapabilities = () => ({
        fetchOpenOrders: true,
        fetchOrders: true,
        fetchClosedOrders: true,
        fetchMyTrades: true,
        fetchOHLCV: true,
        cancelOrder: true,
        createOrder: true,
        editOrder: false,
        fetchBalance: true,
        fetchLedger: false,
        fetchTicker: true,
        fetchTickers: true,
        fetchPosition: true,
        fetchPositions: true,
        shorting: true
    });
}

process.on('unhandledRejection', (reason) => {
    console.error('[trade-executor-lifecycle] unhandled rejection', reason);
});

process.on('uncaughtException', (error) => {
    console.error('[trade-executor-lifecycle] uncaught exception', error);
});

async function run() {
    // Keep this test deterministic and offline.
    let logOrderWrites = 0;
    let updateOrderWritesObservedAfterLog = false;
    const pushedPositionSnapshots: Array<any[]> = [];
    const profileId = '00000000-0000-0000-0000-000000000000';

    (supabaseService as any).getClient = () => null;
    (observabilityService as any).emitEvent = () => { };
    (supabaseService as any).updateOrderStatus = async () => {
        if (logOrderWrites > 0) {
            updateOrderWritesObservedAfterLog = true;
        }
    };
    (supabaseService as any).logOrder = async () => {
        await new Promise((resolve) => setTimeout(resolve, 15));
        logOrderWrites += 1;
    };

    (supabaseService as any).logTransaction = async () => { };
    (supabaseService as any).getVirtualOpenPosition = async () => null;
    (supabaseService as any).getPendingOrdersForProfile = async () => [];
    (supabaseService as any).hasActiveOrderForTradeId = async () => false;
    (supabaseService as any).hasFinalizedTradeHistory = async () => false;
    (capitalLedger as any).getAvailableCapital = async () => 1_000_000;
    (capitalLedger as any).reserveForOrder = async () => true;
    (capitalLedger as any).releaseOrderReservation = async () => { };
    (capitalLedger as any).adjustPositionReservation = async () => { };
    (capitalLedger as any).recordRealizedPnl = async () => { };
    (capitalLedger as any).rebuildLedger = async () => { };
    (capitalLedger as any).getLedger = async () => ({
        profile_id: profileId,
        allocated_capital: 1_000_000,
        reserved_for_orders: 0,
        reserved_for_positions: 0,
        realized_pnl: 0,
        updated_at: new Date().toISOString()
    });

    const apiServerStub = {
        updatePositions: (positions: any[]) => {
            pushedPositionSnapshots.push(positions.map((position) => ({ ...position })));
        },
        updateOrders: () => { },
        addHistory: () => { },
        recordOrderFailure: () => { },
        getState: () => ({ accountSnapshot: null }),
        updateAccountSnapshot: () => { },
        updateAccountSnapshotCache: () => { }
    };

    const connector = new MockExchangeConnector();
    (distributedLockService as any).tryAcquireRowLock = async () => true;
    (distributedLockService as any).releaseRowLock = async () => true;
    (distributedLockService as any).isEntryInProgress = async () => false;
    const executor = new TradeExecutor(connector, apiServerStub as any, 'test-user', profileId);

    connector.queueStatuses('filled');
    let openResult;
    try {
        openResult = await executor.openPosition('BTC/USD', SignalDirection.BUY, 1, 'market');
    } catch (err) {
        console.error('[trade-executor-lifecycle] openPosition threw', err);
        throw err;
    }
    console.log('[trade-executor-lifecycle] openResult', openResult);

    assert.equal(openResult.success, true, 'Expected openPosition to succeed');
    assert(executor.getActivePosition('BTC/USD'), 'Active position missing after filled entry');
    assert(
        pushedPositionSnapshots.some((snapshot) => snapshot.some((position) => position.symbol === 'BTC/USD')),
        'Dashboard position snapshot should be pushed immediately after entry fill.'
    );

    connector.setPosition({
        side: 'long',
        qty: '1',
        avg_entry_price: '100'
    });
    connector.queueStatuses('rejected');
    const rejectedExit = await executor.closePosition('BTC/USD', 'test-rejected-exit');

    assert.equal(rejectedExit.success, false, 'Expected rejected exit to fail');
    assert.equal(executor.getExitLifecycle('BTC/USD').state, 'failed', 'Exit lifecycle should be failed after rejected exit');
    assert(executor.getActivePosition('BTC/USD'), 'Position should remain open after rejected exit');

    connector.queueStatuses('filled');
    const secondOpen = await executor.openPosition('ETH/USD', SignalDirection.BUY, 1, 'market');
    assert.equal(secondOpen.success, true, 'Expected second openPosition to succeed');

    connector.setCancelResult(false);
    connector.queueStatuses('filled');
    const unknownOpen = await executor.openPosition('XRP/USD', SignalDirection.BUY, 1, 'market');
    assert.equal(unknownOpen.success, true, 'Expected unknown-path openPosition to succeed');
    // Unknown exit path
    connector.setCancelResult(false);
    let getPosCount = 0;
    const originalGetPosition = connector.getPosition;
    connector.getPosition = async () => {
        getPosCount++;
        if (getPosCount === 1) return { qty: 1, side: 'long', avg_entry_price: 100 };
        return null;
    };
    connector.queueStatuses('new', 'new', 'new');
    const unknownExit = await executor.closePosition('XRP/USD', 'test-unknown-exit');
    connector.getPosition = originalGetPosition; // Restore

    assert.equal(unknownExit.success, false, 'Expected unknown exit to fail');
    assert.equal(executor.getExitLifecycle('XRP/USD').state, 'quarantined', 'Unknown exit should enter quarantined state');
    assert(executor.getActivePosition('XRP/USD'), 'Position should remain open after unknown exit');


    connector.setCancelResult(true);
    connector.setPosition({
        side: 'long',
        qty: '1',
        avg_entry_price: '100'
    });
    connector.queueStatuses('filled');
    const filledExit = await executor.closePosition('ETH/USD', 'test-filled-exit');
    assert.equal(filledExit.success, true, 'Expected filled exit to succeed');
    assert.equal(executor.getExitLifecycle('ETH/USD').state, 'filled', 'Exit lifecycle should be marked filled after final close');
    assert.equal(executor.getActivePosition('ETH/USD'), null, 'Position should be removed after successful exit');


    const retainTradeId = 'TRD-retain-local';
    const internalPositions = executor.getAllPositions();
    internalPositions.set(`SOL/USD::${retainTradeId}`, {
        symbol: 'SOL/USD',
        side: SignalDirection.BUY,
        entryPrice: 100,
        size: 1,
        stopLoss: 95,
        takeProfit: 110,
        peakPrice: 100,
        userId: 'test-user',
        profileId,
        tradeId: retainTradeId
    });
    connector.setPosition({
        side: 'long',
        qty: '1',
        avg_entry_price: '100'
    });
    await executor.syncPositions(['SOL/USD']);
    assert(
        executor.getActivePosition('SOL/USD', retainTradeId),
        'Dedicated profile sync must retain local lifecycle state when exchange is open but virtual lookup is temporarily empty.'
    );

    console.log('[trade-executor-lifecycle] OK: entry/exit terminal-state checks passed');
}

try {
    await run();
} catch (error: any) {
    console.error('[trade-executor-lifecycle] Test failed', error?.message, error);
    process.exit(1);
}
