import { config } from '../config/index.js';
import { IExchangeConnector, Candle } from '../connectors/types.js';
import { Indicators } from '../utils/indicators.js';
import { TrendBiasRule } from './rules/TrendBiasRule.js';
import { MomentumRule } from './rules/MomentumRule.js';
import { ZoneRule } from './rules/ZoneRule.js';
import { SessionRule } from './rules/SessionRule.js';
import { RiskManagementRule } from './rules/RiskManagementRule.js';
import { EntryTriggerRule } from './rules/EntryTriggerRule.js';
import { AIAnalysisRule } from './rules/AIAnalysisRule.js';
import { IRule, MarketContext, RuleResult, SignalDirection } from './rules/types.js';
import logger from '../utils/logger.js';

export class ProStrategyEngine {
    private rules: IRule[] = [];

    constructor(private exchange: IExchangeConnector) {
        this.initializeRules();
    }

    private initializeRules() {
        this.rules = [
            new TrendBiasRule(),
            new MomentumRule(),
            new ZoneRule(),
            new SessionRule(),
            new EntryTriggerRule(),
            new RiskManagementRule(),
            new AIAnalysisRule()
        ];
        const enabled = this.rules.filter(r => r.isEnabled()).map(r => r.name);
        logger.info(`[ProEngine] Rules initialized. Enabled: ${enabled.join(', ')}`);
    }

    public getEnabledRules(): string[] {
        return this.rules.filter(r => r.isEnabled()).map(r => r.name);
    }

    /**
     * Execute strategy for a symbol.
     * @param symbol - Trading pair (e.g. BTC/USDT)
     * @param profileRules - Optional per-profile rule overrides from strategy_config.rules[].
     *   If provided, only rules with enabled=true in this array will execute.
     *   If not provided, falls back to global ENABLED_RULES from env config.
     */
    public async execute(symbol: string, profileRules?: { ruleId: string; enabled: boolean; params?: Record<string, any> }[]): Promise<RuleResult | null> {
        logger.info(`[ProEngine] Analyzing ${symbol} with ${profileRules ? 'profile-specific' : 'global'} rules...`);
        const context = await this.buildMarketContext(symbol);
        if (!context) return null;
        return this.evaluateContext(context, profileRules);
    }

    public async buildMarketContext(symbol: string): Promise<MarketContext | null> {
        const candles4h = await this.exchange.fetchOHLCV(symbol, '4h', 300);
        const candles1h = await this.exchange.fetchOHLCV(symbol, '1h', 100);
        const candles15m = await this.exchange.fetchOHLCV(symbol, '15m', 100);

        if (!candles4h.length || !candles1h.length || !candles15m.length) {
            logger.warn(`[ProEngine] Insufficient data for ${symbol} analysis`);
            return null;
        }

        const context = this.buildContext(symbol, candles4h, candles1h, candles15m);
        logger.info(`[ProEngine] Data Loaded. Price: ${context.currentPrice}, EMA4H: ${context.ema50_4h?.toFixed(2)}/${context.ema200_4h?.toFixed(2)}, RSI: ${context.rsi_1h?.toFixed(2)}`);
        return context;
    }

    public async evaluateContext(
        context: MarketContext,
        profileRules?: { ruleId: string; enabled: boolean; params?: Record<string, any>; ruleType?: 'mandatory' | 'voting' }[],
        minRulePassRatio: number = 1.0
    ): Promise<RuleResult | null> {
        const symbol = context.symbol;

        // Determine which rules to run and in what order
        const defaultOrder = ['TrendBiasRule', 'SessionRule', 'ZoneRule', 'MomentumRule', 'EntryTriggerRule', 'RiskManagementRule', 'AIAnalysisRule'];

        // If profile provides rules[], use their order (preserves dashboard drag-and-drop ordering)
        const executionOrder = (profileRules && profileRules.length > 0)
            ? profileRules.map(r => r.ruleId)
            : defaultOrder;

        // Build a set of enabled rule IDs for this execution
        const enabledRuleIds = new Set<string>();
        const profileRuleMap = new Map<string, { enabled: boolean; params?: Record<string, any>; ruleType?: 'mandatory' | 'voting' }>();
        if (profileRules && profileRules.length > 0) {
            // Per-profile: only run rules the profile has enabled
            for (const pr of profileRules) {
                profileRuleMap.set(pr.ruleId, { enabled: pr.enabled, params: pr.params, ruleType: pr.ruleType });
                if (pr.enabled) enabledRuleIds.add(pr.ruleId);
            }
        } else {
            // Global fallback: use all rules that are enabled in global config
            for (const rule of this.rules) {
                if (rule.isEnabled()) enabledRuleIds.add(rule.name);
            }
        }

        let finalSignal = SignalDirection.NONE;
        let reasons: string[] = [];
        let accumulatedMetadata: any = {};
        let ruleStatuses: any = {};

        // Pre-populate with 'Pending' for enabled rules
        for (const rName of executionOrder) {
            if (enabledRuleIds.has(rName)) {
                ruleStatuses[rName] = { passed: false, reason: 'Pending...', isPending: true };
            }
        }

        let firstFailure: { ruleName: string; reason: string } | null = null;
        let totalVotingRules = 0;
        let passedVotingRules = 0;
        const MANDATORY_RULES = ['RiskManagementRule', 'SessionRule'];
        let hasConflict = false;

        // 4. Sequential Rule Execution
        for (const ruleName of executionOrder) {
            const rule = this.rules.find(r => r.name === ruleName);
            if (!rule || !enabledRuleIds.has(ruleName)) continue;

            // Determine if rule is mandatory: profile setting takes priority, else default list
            const profileSetting = profileRuleMap.get(ruleName)?.ruleType;
            const isMandatory = profileSetting === 'mandatory' || (profileSetting === undefined && MANDATORY_RULES.includes(ruleName));

            if (!isMandatory) totalVotingRules++;

            const ruleResult = await rule.check(context, profileRuleMap.get(ruleName)?.params);

            // Track status for dashboard
            ruleStatuses[rule.name] = {
                passed: ruleResult.passed,
                reason: ruleResult.reason,
                metadata: ruleResult.metadata,
                isPending: false,
                isSkipped: false
            };

            if (!ruleResult.passed) {
                if (isMandatory) {
                    if (!firstFailure) {
                        firstFailure = { ruleName: rule.name, reason: ruleResult.reason || 'Unknown reason' };
                    }
                    logger.info(`[ProEngine] ❌ Mandatory Rule Failed: ${rule.name} -> ${ruleResult.reason}`);
                    break; // Short-circuit on mandatory failure
                } else {
                    if (!firstFailure) {
                        firstFailure = { ruleName: rule.name, reason: ruleResult.reason || 'Unknown reason' };
                    }
                    logger.info(`[ProEngine] ❌ Voting Rule Failed: ${rule.name} -> ${ruleResult.reason}`);
                }
            } else {
                logger.info(`[ProEngine] ✅ Rule Passed: ${rule.name} -> ${ruleResult.reason}`);
                if (!isMandatory) passedVotingRules++;

                // Track signal if rule provides one
                if (ruleResult.signal && ruleResult.signal !== SignalDirection.NONE) {
                    if (finalSignal === SignalDirection.NONE) {
                        finalSignal = ruleResult.signal;
                    } else if (finalSignal !== ruleResult.signal) {
                        logger.info(`[ProEngine] ⚠️ Signal Conflict! Existing: ${finalSignal}, New: ${ruleResult.signal} from ${ruleName}`);
                        hasConflict = true;
                        ruleStatuses[ruleName].passed = false;
                        ruleStatuses[ruleName].reason = `Signal (${ruleResult.signal}) conflicts with established bias (${finalSignal})`;

                        passedVotingRules--; // Undo passing score due to conflict

                        if (!firstFailure) {
                            firstFailure = { ruleName: ruleName, reason: `Signal conflict: ${ruleResult.signal} vs ${finalSignal}` };
                        }
                    }
                }
                reasons.push(ruleResult.reason || 'No reason provided');
            }

            if (ruleResult.metadata) {
                accumulatedMetadata = { ...accumulatedMetadata, ...ruleResult.metadata };
            }
        }

        // Evaluate Voting Threshold
        const rulePassRatio = totalVotingRules > 0 ? passedVotingRules / totalVotingRules : 1.0;
        const passedThreshold = rulePassRatio >= minRulePassRatio;

        if (firstFailure && MANDATORY_RULES.includes(firstFailure.ruleName)) {
            return {
                ruleName: firstFailure.ruleName,
                passed: false,
                signal: SignalDirection.NONE,
                reason: `Mandatory rule failed: ${firstFailure.ruleName} - ${firstFailure.reason}`,
                metadata: { ...context, ...accumulatedMetadata, ruleStatuses }
            };
        }

        if (hasConflict) {
            return {
                ruleName: firstFailure ? firstFailure.ruleName : 'ConflictResolution',
                passed: false,
                signal: SignalDirection.NONE,
                reason: `Signal conflict detected among voting rules.`,
                metadata: { ...context, ...accumulatedMetadata, ruleStatuses }
            };
        }

        if (!passedThreshold) {
            return {
                ruleName: 'VotingThreshold',
                passed: false,
                signal: SignalDirection.NONE,
                reason: `Voting rules threshold not met: ${passedVotingRules}/${totalVotingRules} (${(rulePassRatio * 100).toFixed(0)}% < ${(minRulePassRatio * 100).toFixed(0)}%)`,
                metadata: { ...context, ...accumulatedMetadata, ruleStatuses }
            };
        }

        return {
            ruleName: 'ProStrategyEngine',
            passed: true,
            signal: finalSignal,
            reason: `ALL RULES PASSED:\n- ${reasons.join('\n- ')}`,
            metadata: {
                accumulatedMetadata,
                context,
                ruleStatuses // Always include full status on pass too
            }
        };
    }

    private buildContext(symbol: string, candles4h: Candle[], candles1h: Candle[], candles15m: Candle[]): MarketContext {
        const close4h = candles4h.map(c => c.close);
        const close1h = candles1h.map(c => c.close);
        const close15m = candles15m.map(c => c.close);

        // CHANGE: Use Closed Candle Logic (Index - 2 is the last fully completed candle)
        // Index - 1 is the "current" forming candle (if fetching limit=100)
        // ACTUALLY: ccxt fetchOHLCV most recent is usually the current (incomplete) one.
        // So we strictly take the one BEFORE the last one.

        // Default execution price depends on Config, but context should have all.
        // Let's use 1h for "currentPrice" display or the finest grain?
        // Finest grain (15m) is safer for display if we are scalping.

        const latestPrice = close15m[close15m.length - 2] || close1h[close1h.length - 2];

        // We need to slice arrays to match the "Closed" perspective for indicators?
        // Usually indicators are calculated on the whole array, and we just look at the value at [length-2].
        // But for simplicity in "Context", let's pass the whole array, but indicators utils should handle it?
        // Wait, the Utils calculate on the array. We just need to grab the right index.

        // Let's optimize: The "Context" indicators (ema20_1h) should be the value at [length-2].

        const closedIndex = close1h.length - 2;
        const closedIndex4h = close4h.length - 2;
        const closedIndex15m = close15m.length - 2;

        const closed4hData = close4h.slice(0, closedIndex4h + 1);
        const closed1hData = close1h.slice(0, closedIndex + 1);
        const closed15mData = close15m.slice(0, closedIndex15m + 1);

        logger.info(`[ProEngine] Context Build: Original 1H len=${close1h.length}, Sliced len=${closed1hData.length}`);

        const price24hAgo = close1h[close1h.length - 25] || close1h[0];
        const change24h = ((latestPrice - price24hAgo) / price24hAgo) * 100;

        // Calculate Today's Change (since 00:00 UTC)
        const now = new Date();
        const startOfDay = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())).getTime();
        const candleToday = candles1h.find(c => c.timestamp >= startOfDay) || candles1h[0];
        const priceTodayOpen = candleToday.open;
        const changeToday = ((latestPrice - priceTodayOpen) / priceTodayOpen) * 100;

        // Determine Active Session
        const utcHour = now.getUTCHours();
        let sessions = [];
        let isMajorSession = false;

        if (utcHour >= 0 && utcHour < 9) sessions.push('TOK');
        if (utcHour >= 22 || utcHour < 7) sessions.push('SYD');

        if (utcHour >= 7 && utcHour < 16) {
            sessions.push('LDN');
            isMajorSession = true;
        }
        if (utcHour >= 13 && utcHour < 22) {
            sessions.push('NY');
            isMajorSession = true;
        }
        const sessionStr = sessions.length > 0 ? sessions.join(' | ') : 'OFF';

        // Calculate Volatility (ATR % of Price)
        const atr = Indicators.calculateATR(candles1h.slice(0, closedIndex + 1), 14);
        const atrPercent = (atr / latestPrice) * 100;
        let volatility = 'Low';
        if (atrPercent > 0.5) volatility = 'Med';
        if (atrPercent > 1.0) volatility = 'High';

        return {
            symbol,
            candles4h: candles4h.slice(0, closedIndex4h + 1),
            candles1h: candles1h.slice(0, closedIndex + 1),
            candles15m: candles15m.slice(0, closedIndex15m + 1),

            currentPrice: latestPrice,
            change24h,
            changeToday,
            session: sessionStr,
            isMajorSession,
            volatility,
            latestSignal: SignalDirection.NONE, // Placeholder

            // 4H Indicators (Closed)
            ema50_4h: Indicators.calculateEMA(closed4hData, 50),
            ema200_4h: Indicators.calculateEMA(closed4hData, 200),

            // 1H Indicators (Closed)
            ema20_1h: Indicators.calculateEMA(closed1hData, 20),
            rsi_1h: Indicators.calculateRSI(closed1hData, 14),

            // 15m Indicators (Closed)
            ema20_15m: Indicators.calculateEMA(closed15mData, 20),
            rsi_15m: Indicators.calculateRSI(closed15mData, 14),
        };
    }
}
