import assert from 'node:assert/strict';
import { config } from '../src/config/index.js';
import { AIClient } from '../src/services/aiClient.js';
import { AlpacaConnector } from '../src/connectors/alpaca.js';
import { CCXTConnector } from '../src/connectors/ccxt.js';
import { ConnectorFactory } from '../src/connectors/factory.js';
import type { Candle } from '../src/connectors/types.js';

async function* generateBars(rows: any[]) {
    for (const row of rows) {
        yield row;
    }
}

async function testAIClient() {
    const originalAiConfig = {
        OPENAI_API_KEY: config.AI.OPENAI_API_KEY,
        PERPLEXITY_API_KEY: config.AI.PERPLEXITY_API_KEY,
        GEMINI_API_KEY: config.AI.GEMINI_API_KEY,
        FALLBACK_LIST: [...config.AI.FALLBACK_LIST],
        MODEL: config.AI.MODEL
    };

    try {
        config.AI['OPENAI_API_KEY'] = '';
        config.AI['PERPLEXITY_API_KEY'] = '';
        config.AI['GEMINI_API_KEY'] = '';
        config.AI.FALLBACK_LIST = ['openai', 'perplexity', 'gemini'];

        const clientNoKeys = new AIClient();
        const noResult = await clientNoKeys.generateAnalysis('prompt');
        assert.equal(noResult, null, 'generateAnalysis should return null when no provider keys are configured');

        const noProbeHealth = await clientNoKeys.getProviderHealth(false);
        assert.equal(noProbeHealth.length, 3);
        assert(noProbeHealth.every((item) => item.status === 'missing_key'));

        // Override provider call paths to avoid network and validate fallback routing
        const clientWithMocks = new AIClient() as any;
        config.AI['OPENAI_API_KEY'] = 'key-openai';
        config.AI['PERPLEXITY_API_KEY'] = 'key-perplexity';
        config.AI['GEMINI_API_KEY'] = 'key-gemini';
        config.AI.FALLBACK_LIST = ['unsupported' as any, 'openai', 'perplexity', 'gemini'];

        clientWithMocks.callOpenAI = async () => '{"action":"BUY","confidence":75,"reasoning":"ok"}';
        clientWithMocks.callPerplexity = async () => '{"action":"SELL","confidence":65,"reasoning":"ok"}';
        clientWithMocks.callGemini = async () => '{"action":"HOLD","confidence":50,"reasoning":"ok"}';

        const mockedResult = await clientWithMocks.generateAnalysis('probe prompt');
        assert.equal(typeof mockedResult, 'string');
        assert((mockedResult as string).includes('BUY'));

        const probeHealth = await clientWithMocks.getProviderHealth(true);
        assert.equal(probeHealth.length, 3);
        assert(probeHealth.every((item: any) => item.status === 'ok'));

        // probeProvider unsupported branch
        await assert.rejects(async () => {
            await clientWithMocks.probeProvider('unsupported');
        });
    } finally {
        config.AI['OPENAI_API_KEY'] = originalAiConfig.OPENAI_API_KEY;
        config.AI['PERPLEXITY_API_KEY'] = originalAiConfig.PERPLEXITY_API_KEY;
        config.AI['GEMINI_API_KEY'] = originalAiConfig.GEMINI_API_KEY;
        config.AI.FALLBACK_LIST = originalAiConfig.FALLBACK_LIST;
        config.AI.MODEL = originalAiConfig.MODEL;
    }
}

async function testAlpacaConnector() {
    const originalAssetClass = config.ASSET_CLASS;

    try {
        const connector = new AlpacaConnector('k', 's') as any;

        const bars = [
            { Timestamp: '2026-02-16T00:00:00Z', OpenPrice: 100, HighPrice: 101, LowPrice: 99, ClosePrice: 100, Volume: 10 },
            { Timestamp: '2026-02-16T01:00:00Z', OpenPrice: 101, HighPrice: 102, LowPrice: 100, ClosePrice: 101, Volume: 11 },
            { Timestamp: '2026-02-16T02:00:00Z', OpenPrice: 102, HighPrice: 103, LowPrice: 101, ClosePrice: 102, Volume: 12 },
            { Timestamp: '2026-02-16T03:00:00Z', OpenPrice: 103, HighPrice: 104, LowPrice: 102, ClosePrice: 103, Volume: 13 }
        ];

        connector.client = {
            getBarsV2: (_symbol: string, _opts: any) => generateBars(bars),
            createOrder: async (opts: any) => ({ id: 'alp-1', ...opts }),
            getOrder: async (_id: string) => ({ id: 'alp-1', status: 'filled' }),
            cancelOrder: async (_id: string) => undefined,
            getPosition: async (_symbol: string) => ({ side: 'long', avg_entry_price: '100', qty: '2' }),
            getClock: async () => ({ is_open: true })
        };

        const mappedTf = connector.mapTimeframe('1m');
        assert.equal(mappedTf, '1Min');
        assert.equal(connector.mapTimeframe('unknown'), 'unknown');

        const aggregated = connector.aggregateBars([
            { timestamp: 1, open: 1, high: 2, low: 0.5, close: 1.5, volume: 5 },
            { timestamp: 2, open: 1.5, high: 3, low: 1, close: 2.5, volume: 7 }
        ], 2);
        assert.equal(aggregated.length, 1);
        assert.equal(aggregated[0].high, 3);
        assert.equal(aggregated[0].volume, 12);

        const candles4h = await connector.fetchOHLCV('BTC/USD', '4h', 1);
        assert.equal(candles4h.length, 1);

        config.ASSET_CLASS = 'us_equity';
        const orderWithBracket = await connector.placeOrder('AAPL', 'buy', 1, 'limit', 200, 190, 220);
        assert.equal(orderWithBracket.order_class, 'bracket');

        config.ASSET_CLASS = 'crypto';
        const cryptoOrder = await connector.placeOrder('BTC/USD', 'sell', 0.5, 'market');
        assert.equal(cryptoOrder.side, 'sell');

        const order = await connector.getOrder('ord-1');
        assert.equal(order.status, 'filled');

        const cancelOk = await connector.cancelOrder('ord-1');
        assert.equal(cancelOk, true);

        const position = await connector.getPosition('BTC/USD');
        assert.equal(position.qty, '2');

        const marketOpen = await connector.isTradingWindowOpen();
        assert.equal(marketOpen, true);

        connector.client.getOrder = async () => { throw new Error('boom'); };
        const missingOrder = await connector.getOrder('ord-2');
        assert.equal(missingOrder, null);

        connector.client.cancelOrder = async () => { throw new Error('boom'); };
        const cancelFail = await connector.cancelOrder('ord-2');
        assert.equal(cancelFail, false);

        connector.client.getPosition = async () => { throw new Error('404 not found'); };
        const noPosition = await connector.getPosition('ETH/USD');
        assert.equal(noPosition, null);

        connector.client.getPosition = async () => { throw new Error('500 server'); };
        await assert.rejects(async () => {
            await connector.getPosition('ETH/USD');
        });

        config.ASSET_CLASS = 'us_equity';
        connector.client.getClock = async () => ({ is_open: false });
        const marketClosed = await connector.isTradingWindowOpen();
        assert.equal(marketClosed, false);

        connector.client.getClock = async () => ({ nope: true });
        const marketUnknown = await connector.isTradingWindowOpen();
        assert.equal(marketUnknown, null);

        connector.client.getClock = async () => { throw new Error('clock-fail'); };
        const clockFail = await connector.isTradingWindowOpen();
        assert.equal(clockFail, null);
    } finally {
        config.ASSET_CLASS = originalAssetClass;
    }
}

async function testCCXTConnectorAndFactory() {
    const originalExchange = config.EXCHANGE;
    const originalProvider = config.PROVIDER;

    try {
        // Unsupported exchange branch
        config.EXCHANGE = 'definitely_unsupported_exchange';
        await assert.rejects(async () => {
            new CCXTConnector('k', 's');
        });

        config.EXCHANGE = 'binance';
        const ccxtConnector = new CCXTConnector('k', 's') as any;
        ccxtConnector.client = {
            fetchOHLCV: async (_symbol: string, _tf: string, _since: any, _limit: number) => [
                [1, 100, 110, 90, 105, 1000],
                [2, 105, 112, 100, 111, 1200]
            ],
            createOrder: async (_symbol: string, _type: string, _side: string, _qty: number, _price: number | undefined) => ({ id: 'ccxt-1' }),
            fetchOrder: async (_id: string, _symbol?: string) => ({ id: 'ccxt-1', status: 'closed' }),
            fetchPositions: async (_symbols: string[]) => [{ symbol: 'BTC/USDT', contracts: 1 }],
            cancelOrder: async (_id: string, _symbol?: string) => undefined
        };

        const candles = await ccxtConnector.fetchOHLCV('BTC/USDT', '1Hour', 2);
        assert.equal(candles.length, 2);
        assert.equal((candles[0] as Candle).close, 105);

        const order = await ccxtConnector.placeOrder('BTC/USDT', 'buy', 1, 'market');
        assert.equal(order.id, 'ccxt-1');

        const fetchedOrder = await ccxtConnector.getOrder('ccxt-1', 'BTC/USDT');
        assert.equal(fetchedOrder.status, 'closed');

        const position = await ccxtConnector.getPosition('BTC/USDT');
        assert.equal(position.symbol, 'BTC/USDT');

        const isOpen = await ccxtConnector.isTradingWindowOpen();
        assert.equal(isOpen, true);

        const cancelOk = await ccxtConnector.cancelOrder('ccxt-1', 'BTC/USDT');
        assert.equal(cancelOk, true);

        ccxtConnector.client.fetchOrder = async () => { throw new Error('fetch-order-fail'); };
        const missingOrder = await ccxtConnector.getOrder('ccxt-2', 'BTC/USDT');
        assert.equal(missingOrder, null);

        ccxtConnector.client.fetchPositions = async () => { throw new Error('position-fail'); };
        await assert.rejects(async () => {
            await ccxtConnector.getPosition('BTC/USDT');
        });

        ccxtConnector.client.cancelOrder = async () => { throw new Error('cancel-fail'); };
        const cancelFail = await ccxtConnector.cancelOrder('ccxt-2', 'BTC/USDT');
        assert.equal(cancelFail, false);

        // Factory branches
        config.PROVIDER = 'alpaca';
        const defaultConnector = ConnectorFactory.getConnector();
        assert(defaultConnector instanceof AlpacaConnector);

        const customCcxt = ConnectorFactory.getCustomConnector('ccxt', 'k', 's');
        assert(customCcxt instanceof CCXTConnector);

        const customAlpaca = ConnectorFactory.getCustomConnector('alpaca', 'k', 's');
        assert(customAlpaca instanceof AlpacaConnector);

        await assert.rejects(async () => {
            ConnectorFactory.getCustomConnector('unknown-provider');
        });
    } finally {
        config.EXCHANGE = originalExchange;
        config.PROVIDER = originalProvider;
    }
}

async function run() {
    await testAIClient();
    await testAlpacaConnector();
    await testCCXTConnectorAndFactory();

    console.log('[connector-ai-coverage] OK: connector and AI client branches validated');
}

await run();
