import * as ccxt from 'ccxt';
import { config } from '../config/index.js';
import logger from '../utils/logger.js';
import { IExchangeConnector, Candle, ExchangeCapabilities, ExchangeOrderCorrelation } from './types.js';

export class CCXTConnector implements IExchangeConnector {
    private client: ccxt.Exchange;

    constructor(apiKey?: string, apiSecret?: string) {
        const exchangeId = config.EXCHANGE as keyof typeof ccxt;
        const exchangeClass = ccxt[exchangeId] as any;

        if (!exchangeClass) {
            throw new Error(`Exchange ${config.EXCHANGE} not supported by CCXT`);
        }

        const auth: any = {};
        const resolvedApiKey = apiKey || config.CCXT_API_KEY;
        const resolvedApiSecret = apiSecret || config.CCXT_API_SECRET;

        if (resolvedApiKey && resolvedApiKey !== 'your_key') {
            auth.apiKey = resolvedApiKey;
        }
        if (resolvedApiSecret && resolvedApiSecret !== 'your_secret') {
            auth.secret = resolvedApiSecret;
        }

        this.client = new exchangeClass({
            ...auth,
            enableRateLimit: true,
        });
    }

    public getCapabilities(): ExchangeCapabilities {
        return {
            fetchOpenOrders: !!this.client.has['fetchOpenOrders'],
            fetchClosedOrders: !!this.client.has['fetchClosedOrders'],
            shorting: !!this.client.has['createMarketBuyOrder'] && !!this.client.has['createMarketSellOrder'],
            margin: !!this.client.has['margin'],
            leverage: !!this.client.has['setLeverage'],
            tradingWindow: false // CCXT doesn't have a unified market clock usually
        };
    }

    async fetchOHLCV(symbol: string, timeframe: string, limit: number = 100): Promise<Candle[]> {
        try {
            // Translate Alpaca style '1Min' to CCXT style '1m'
            let translatedTimeframe = timeframe;
            if (timeframe === '1Min') translatedTimeframe = '1m';
            if (timeframe === '5Min') translatedTimeframe = '5m';
            if (timeframe === '15Min') translatedTimeframe = '15m';
            if (timeframe === '1Hour') translatedTimeframe = '1h';
            if (timeframe === '1Day') translatedTimeframe = '1d';

            logger.info(`[CCXT] Fetching data for ${symbol} via ${config.EXCHANGE} (${translatedTimeframe})...`);
            const candles = await this.client.fetchOHLCV(symbol, translatedTimeframe, undefined, limit);
            return candles.map((c: any) => ({
                timestamp: c[0],
                open: c[1],
                high: c[2],
                low: c[3],
                close: c[4],
                volume: c[5],
            }));
        } catch (error) {
            logger.error(`[CCXT] Error: ${error}`);
            throw error;
        }
    }

    async placeOrder(
        symbol: string,
        side: 'buy' | 'sell',
        qty: number,
        type: 'market' | 'limit',
        price?: number,
        stopLoss?: number,
        takeProfit?: number,
        clientOrderId?: string,
        correlation?: ExchangeOrderCorrelation
    ): Promise<any> {
        try {
            logger.info(`[CCXT] Placing ${side} ${type} order for ${qty} ${symbol}...`);
            const params: any = {};
            if (clientOrderId) params.clientOrderId = clientOrderId;
            if (stopLoss !== undefined) params.stopLoss = stopLoss;
            if (takeProfit !== undefined) params.takeProfit = takeProfit;
            return await this.client.createOrder(symbol, type, side, qty, price, params);
        } catch (error) {
            logger.error(`[CCXT] Order Error: ${error}`);
            throw error;
        }
    }


    async getOrder(orderId: string, symbol?: string): Promise<any> {
        try {
            logger.info(`[CCXT] Fetching order ${orderId} (${symbol || 'unknown symbol'})...`);
            // CCXT often requires the symbol for fetchOrder
            return await this.client.fetchOrder(orderId, symbol);
        } catch (error: any) {
            logger.error(`[CCXT] GetOrder Error for ${orderId}: ${error.message || error}`);
            return null;
        }
    }

    async getPosition(symbol: string): Promise<any> {
        try {
            const positions = await this.client.fetchPositions([symbol]);
            return positions.find(p => p.symbol === symbol) || null;
        } catch (error: any) {
            logger.error(`[CCXT] GetPosition Error for ${symbol}: ${error.message || error}`);
            throw error;
        }
    }

    async isTradingWindowOpen(): Promise<boolean | null> {
        // Crypto venues are effectively 24/7 for this bot's supported flow.
        return true;
    }

    private normalizeDataSymbol(symbol?: string): string {
        const candidate = String(symbol || '').toUpperCase();
        if (!candidate) return candidate;
        if (candidate.includes('/')) {
            return candidate;
        }
        if (candidate.endsWith('USDT')) {
            return `${candidate.slice(0, -4)}/USDT`;
        }
        if (candidate.endsWith('USD')) {
            return `${candidate.slice(0, -3)}/USD`;
        }
        return candidate;
    }

    async cancelOrder(orderId: string, symbol?: string): Promise<boolean> {
        try {
            logger.info(`[CCXT] Cancelling order ${orderId} (${symbol || 'unknown symbol'})...`);
            await this.client.cancelOrder(orderId, symbol);
            return true;
        } catch (error: any) {
            logger.error(`[CCXT] CancelOrder Error for ${orderId}: ${error.message || error}`);
            return false;
        }
    }

    async fetchOpenOrders(symbols?: string[]): Promise<any[]> {
        if (!this.client.fetchOpenOrders) return [];
        try {
            const orders = await this.client.fetchOpenOrders();
            if (!symbols || symbols.length === 0) return orders;

            const normalizedTargets = new Set(symbols.map((s) => this.normalizeDataSymbol(s)));
            return orders.filter((order: any) => normalizedTargets.has(this.normalizeDataSymbol(order.symbol)));
        } catch (error: any) {
            logger.error(`[CCXT] Fetch open orders failed: ${error.message || error}`);
            return [];
        }
    }

    async fetchClosedOrders(
        symbols?: string[],
        options?: {
            after?: Date;
            limit?: number;
            maxPages?: number;
        }
    ): Promise<any[]> {
        if (!this.client.fetchClosedOrders) return [];
        const since = options?.after instanceof Date && Number.isFinite(options.after.getTime())
            ? options.after.getTime()
            : undefined;
        const limit = Math.max(1, Math.min(500, Math.floor(Number(options?.limit || 500))));

        try {
            if (!symbols || symbols.length === 0) {
                return await this.client.fetchClosedOrders(undefined, since, limit);
            }

            const collected: any[] = [];
            for (const symbol of symbols) {
                try {
                    const rows = await this.client.fetchClosedOrders(symbol, since, limit);
                    if (Array.isArray(rows)) {
                        collected.push(...rows);
                    }
                } catch (symbolError: any) {
                    logger.warn(`[CCXT] Fetch closed orders failed for ${symbol}: ${symbolError.message || symbolError}`);
                }
            }
            return collected;
        } catch (error: any) {
            logger.error(`[CCXT] Fetch closed orders failed: ${error.message || error}`);
            return [];
        }
    }
}
