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

class FlakyExchange implements IExchangeConnector {
    private failPlaceOrder = false;
    private failGetOrder = false;

    setPlaceOrderFailure(flag: boolean) {
        this.failPlaceOrder = flag;
    }

    setGetOrderFailure(flag: boolean) {
        this.failGetOrder = flag;
    }

    async fetchOHLCV(_symbol: string, _timeframe: string, _limit?: number): Promise<Candle[]> {
        return [{
            timestamp: Date.now(),
            open: 100,
            high: 100,
            low: 100,
            close: 100,
            volume: 1
        }];
    }

    async placeOrder(_symbol: string, _side: 'buy' | 'sell', qty: number, _type: 'market' | 'limit', price?: number): Promise<any> {
        if (this.failPlaceOrder) {
            throw new Error('simulated-exchange-flap: placeOrder');
        }
        return {
            id: `mock-${Date.now()}`,
            status: 'filled',
            filled_avg_price: price || 100,
            filled_qty: `${qty}`
        };
    }

    async getPosition(_symbol: string): Promise<any> {
        return null;
    }

    async getOrder(_orderId: string): Promise<any> {
        if (this.failGetOrder) {
            throw new Error('simulated-exchange-flap: getOrder');
        }
        return { status: 'filled', filled_avg_price: 100, filled_qty: '1' };
    }
}

async function run() {
    const exchange = new FlakyExchange();

    // Failure injection 1: entry should fail safely on exchange placeOrder flap.
    const executor = new TradeExecutor(exchange, undefined, 'global', 'failure-test');
    exchange.setPlaceOrderFailure(true);
    const result = await executor.openPosition('BTC/USD', SignalDirection.BUY, 1, 'market');
    assert.equal(result.success, false, 'openPosition must fail safely when exchange placeOrder throws.');
    assert.equal(executor.getActivePosition('BTC/USD'), null, 'No local position should be created on failed order placement.');

    // Failure injection 2: stale-order sync should absorb getOrder flap without crashing or mutating status.
    const originalGetStaleOrders = (supabaseService as any).getStaleOrders;
    const originalUpdateOrderStatus = (supabaseService as any).updateOrderStatus;
    const statusWrites: string[] = [];

    (supabaseService as any).getStaleOrders = async () => [{
        order_id: 'ord-flaky',
        symbol: 'BTC/USD',
        status: 'pending_new',
        created_at: new Date(Date.now() - 10 * 60 * 1000).toISOString()
    }];
    (supabaseService as any).updateOrderStatus = async (_orderId: string, status: string) => {
        statusWrites.push(status);
    };

    exchange.setGetOrderFailure(true);
    try {
        const sync = new OrderStatusSyncService(exchange, 5_000);
        await sync.triggerSync();
        assert.equal(statusWrites.length, 0, 'Order status should not be mutated when exchange getOrder fails transiently.');
    } finally {
        (supabaseService as any).getStaleOrders = originalGetStaleOrders;
        (supabaseService as any).updateOrderStatus = originalUpdateOrderStatus;
    }

    console.log('[failure-injection] OK: exchange/API flap paths handled safely');
}

await run();
