import { config } from '../config/index.js';
import { MarketContext, RuleResult, SignalDirection } from '../strategies/rules/types.js';
import { RiskEngine } from './riskEngine.js';
import { TradeExecutor } from './TradeExecutor.js';
import logger from '../utils/logger.js';
import { SymbolMapper } from '../utils/symbolMapper.js';
import { IExchangeConnector } from '../connectors/types.js';
import { supabaseService } from './SupabaseService.js';
import { healthTracker } from './healthTracker.js';

export interface PortfolioGuardInput {
    symbol: string;
    profileSettings?: any;
    signal?: SignalDirection;
}

export interface PortfolioGuardResult {
    allowed: boolean;
    reason?: string;
}

export type AutoTraderExecutionStatus = 'EXECUTED' | 'BLOCKED' | 'SKIPPED';

export interface AutoTraderExecutionOutcome {
    status: AutoTraderExecutionStatus;
    code: string;
    reason: string;
    orderId?: string;
}

export class AutoTrader {
    private riskEngine: RiskEngine;

    constructor(
        private executor: TradeExecutor,
        private exchange: IExchangeConnector, // Needed for pre-check
        private portfolioGuard?: (input: PortfolioGuardInput) => Promise<PortfolioGuardResult> | PortfolioGuardResult
    ) {
        this.riskEngine = new RiskEngine();
    }

    private outcome(
        status: AutoTraderExecutionStatus,
        code: string,
        reason: string,
        orderId?: string
    ): AutoTraderExecutionOutcome {
        return { status, code, reason, orderId };
    }

    private evaluateProfileSymbolScope(symbol: string, profileSettings?: any): { allowed: boolean; reason?: string } {
        if (!profileSettings || profileSettings.symbols === undefined || profileSettings.symbols === null) {
            return { allowed: true };
        }

        const rawSymbols = Array.isArray(profileSettings.symbols)
            ? profileSettings.symbols
            : String(profileSettings.symbols).split(',');
        const allowedSymbols = rawSymbols
            .map((value: string) => String(value).trim())
            .filter(Boolean);

        if (!allowedSymbols.length) {
            return { allowed: false, reason: 'Profile watchlist is empty.' };
        }

        const executionSymbol = SymbolMapper.toExecutionScopeSymbol(symbol, config.EXECUTION_PROVIDER);
        const normalizedAllowed = allowedSymbols.map((item: string) =>
            SymbolMapper.toExecutionScopeSymbol(item, config.EXECUTION_PROVIDER)
        );
        if (normalizedAllowed.includes(executionSymbol)) {
            return { allowed: true };
        }

        return {
            allowed: false,
            reason: `${symbol} is outside profile watchlist (${allowedSymbols.join(', ')})`
        };
    }

    public async handleSignal(
        symbol: string,
        result: RuleResult,
        context: MarketContext,
        profileSettings?: any
    ): Promise<AutoTraderExecutionOutcome> {
        if (!config.ENABLE_TRADING) {
            logger.info(`[AutoTrader] Trading disabled. Alert-only mode for ${symbol}.`);
            return this.outcome('SKIPPED', 'trading_disabled', 'Trading is disabled globally.');
        }

        // --- Profile Level Filtering ---
        const scopeCheck = this.evaluateProfileSymbolScope(symbol, profileSettings);
        if (!scopeCheck.allowed) {
            return this.outcome(
                'BLOCKED',
                'symbol_not_in_profile_scope',
                scopeCheck.reason || `Symbol ${symbol} not allowed by profile watchlist.`
            );
        }

        const activePositions = this.executor.getActivePositions(symbol);
        const hasActivePositions = activePositions.length > 0;

        // --- EXIT LOGIC ---
        if (hasActivePositions) {
            const hasOpposite = activePositions.some((pos) =>
                (pos.side === SignalDirection.BUY && result.signal === SignalDirection.SELL)
                || (pos.side === SignalDirection.SELL && result.signal === SignalDirection.BUY)
            );

            if (hasOpposite) {
                for (const pos of activePositions) {
                    await this.executor.closePosition(symbol, 'Strategy Signal Flip', pos.tradeId);
                }
                return this.outcome(
                    'EXECUTED',
                    'exit_on_signal_flip',
                    `Closed ${activePositions.length} position(s) on signal flip.`
                );
            }

            const profitExitThreshold = profileSettings?.strategy_config?.execution?.profitExitPercent
                ?? config.PROFIT_EXIT_PERCENT;
            let closedForNeutral = 0;
            for (const activePos of activePositions) {
                const profitPercent = ((context.currentPrice - activePos.entryPrice) / activePos.entryPrice) * 100 * (activePos.side === SignalDirection.BUY ? 1 : -1);
                if (profitPercent >= profitExitThreshold && result.signal === SignalDirection.NONE) {
                    await this.executor.closePosition(symbol, `Trend Neutralized (>${profitExitThreshold}% profit)`, activePos.tradeId);
                    closedForNeutral += 1;
                }
            }
            if (closedForNeutral > 0) {
                return this.outcome(
                    'EXECUTED',
                    'exit_on_neutral_profit',
                    `Closed ${closedForNeutral} position(s) on neutralized trend profit exit.`
                );
            }

            for (const activePos of activePositions) {
                if (activePos.side === SignalDirection.BUY) {
                    activePos.peakPrice = Math.max(activePos.peakPrice, context.currentPrice);
                } else {
                    activePos.peakPrice = Math.min(activePos.peakPrice, context.currentPrice);
                }
            }

            const allowPyramiding = profileSettings?.strategy_config?.execution?.allowPyramiding !== false;
            if (!allowPyramiding) {
                return this.outcome(
                    'SKIPPED',
                    'pyramiding_disabled',
                    'Active position exists and pyramiding is disabled.'
                );
            }

            const hasSameDirectionExposure = activePositions.some((pos) => pos.side === result.signal);
            if (!hasSameDirectionExposure) {
                return this.outcome(
                    'SKIPPED',
                    'active_position_no_same_direction',
                    'Active position exists in opposite direction; waiting for flip/exit handling.'
                );
            }
        }

        // --- ENTRY LOGIC ---
        if (result.signal === SignalDirection.NONE || !result.passed) {
            return this.outcome(
                'SKIPPED',
                'no_actionable_signal',
                result.reason || 'No actionable entry signal.'
            );
        }

        if (healthTracker.isPaused()) {
            logger.info(`[AutoTrader] Entry blocked for ${symbol}: bot is paused by control plane.`);
            return this.outcome('BLOCKED', 'trading_paused', 'Trading is paused by control plane.');
        }

        const entryMode = this.resolveEntryMode(profileSettings);
        if (entryMode === 'long_only' && result.signal === SignalDirection.SELL) {
            logger.info(`[AutoTrader] Entry blocked for ${symbol}: profile is long_only and signal is SELL.`);
            return this.outcome('BLOCKED', 'long_only_sell_block', 'Profile is long_only; SELL entries are blocked.');
        }

        if (this.portfolioGuard) {
            const portfolioCheck = await this.portfolioGuard({
                symbol,
                profileSettings,
                signal: result.signal
            });
            if (!portfolioCheck.allowed) {
                logger.info(`[AutoTrader] Portfolio guard blocked ${symbol}: ${portfolioCheck.reason || 'guard rejection'}`);
                return this.outcome(
                    'BLOCKED',
                    'portfolio_guard_blocked',
                    portfolioCheck.reason || 'Portfolio guard rejected entry.'
                );
            }
        }

        // 1. Checks
        // Max open trades: per-profile > global config
        const maxOpenTrades = profileSettings?.strategy_config?.riskLimits?.maxOpenTrades
            ?? config.MAX_OPEN_TRADES;
        if (this.executor.getOpenPositionCount() >= maxOpenTrades) {
            logger.info(`[AutoTrader] Max open trades reached (${maxOpenTrades}).`);
            return this.outcome(
                'BLOCKED',
                'max_open_trades_reached',
                `Max open trades reached (${maxOpenTrades}).`
            );
        }

        // Cooldown: per-profile > global config
        const cooldownMs = profileSettings?.strategy_config?.execution?.cooldownMinutes
            ? profileSettings.strategy_config.execution.cooldownMinutes * 60_000
            : config.COOLDOWN_MS;
        if (this.executor.checkCooldown(symbol, cooldownMs)) {
            return this.outcome('BLOCKED', 'cooldown_active', 'Symbol is in cooldown window.');
        }

        const riskLimitGuard = await this.checkRuntimeRiskLimits(profileSettings);
        if (!riskLimitGuard.allowed) {
            logger.warn(`[AutoTrader] Runtime risk guard blocked ${symbol}: ${riskLimitGuard.reason || 'risk limit exceeded'}`);
            return this.outcome(
                'BLOCKED',
                'runtime_risk_limit_blocked',
                riskLimitGuard.reason || 'Runtime risk limit exceeded.'
            );
        }

        // 2. Safety Lock (Exchange Check)
        const profileId = this.executor.getProfileId();
        const isDedicatedProfileScope = !!profileId
            && profileId !== 'global'
            && !profileId.startsWith('default-');
        try {
            const tradeSymbol = SymbolMapper.toTradeSymbol(symbol, config.EXECUTION_PROVIDER);
            const position = await this.exchange.getPosition(tradeSymbol);
            if (position && !isDedicatedProfileScope) {
                logger.warn(`[AutoTrader] Existing position found on exchange for ${symbol}. Syncing.`);
                await this.executor.syncPositions([symbol]);
                return this.outcome(
                    'BLOCKED',
                    'exchange_position_already_open',
                    'Exchange already has an open position; synced local state and skipped entry.'
                );
            }
            if (position && isDedicatedProfileScope) {
                logger.info(`[AutoTrader] Exchange already has ${symbol}. Continuing with profile-scoped virtual sub-position flow for ${profileId}.`);
            }
        } catch (e) {
            logger.error(`[AutoTrader] Position check failed for ${symbol}. Skipping entry to avoid duplicate exposure.`, e);
            return this.outcome(
                'BLOCKED',
                'exchange_position_check_failed',
                'Exchange position check failed; entry skipped to avoid duplicate exposure.'
            );
        }

        // 3. Risk Calculation (PASS PROFILE OVERRIDES + estimated available capital)
        const allocatedCapital = profileSettings?.allocated_capital || config.TOTAL_CAPITAL;
        const committedCapital = Array.from(this.executor.getAllPositions().values())
            .reduce((sum, pos) => sum + (pos.size * pos.entryPrice), 0);
        const availableCapital = Math.max(0, allocatedCapital - committedCapital);

        const riskProfile = await this.riskEngine.calculateRiskProfile(
            symbol,
            result.signal as SignalDirection,
            context,
            profileSettings,
            availableCapital
        );
        if (!riskProfile) {
            return this.outcome('BLOCKED', 'risk_profile_unavailable', 'Risk profile calculation returned no executable sizing.');
        }

        // 4. Execute
        const execution = await this.executor.openPosition(
            symbol,
            riskProfile.action,
            riskProfile.positionSize,
            'market',
            context.currentPrice,
            riskProfile.stopLoss,
            riskProfile.takeProfit,
            profileSettings?.user_id // Ensure order is attributed to the user/profile owner
        );
        if (!execution.success) {
            return this.outcome(
                'BLOCKED',
                'executor_rejected_entry',
                execution.error || 'Trade executor rejected entry.'
            );
        }

        return this.outcome(
            'EXECUTED',
            'entry_submitted',
            'Entry submitted to execution engine.',
            execution.orderId
        );
    }

    private resolveEntryMode(profileSettings?: any): 'both' | 'long_only' {
        const execution = profileSettings?.strategy_config?.execution;
        const rawEntryMode = String(
            execution?.entryMode ?? (execution?.longOnly ? 'long_only' : 'both')
        ).toLowerCase();
        if (rawEntryMode === 'long_only' || rawEntryMode === 'longonly' || rawEntryMode === 'buy_only') {
            return 'long_only';
        }
        return 'both';
    }

    private async checkRuntimeRiskLimits(profileSettings?: any): Promise<PortfolioGuardResult> {
        const profileId = this.executor.getProfileId();
        if (!profileId || profileId === 'global' || profileId.startsWith('default-')) {
            return { allowed: true };
        }

        const riskLimits = profileSettings?.strategy_config?.riskLimits;
        if (!riskLimits) {
            return { allowed: true };
        }

        const maxDailyLossUsd = Number(riskLimits.maxDailyLossUsd);
        if (Number.isFinite(maxDailyLossUsd) && maxDailyLossUsd > 0) {
            const dailyLossUsd = await supabaseService.getProfileDailyLossUsd(profileId);
            if (dailyLossUsd >= maxDailyLossUsd) {
                return {
                    allowed: false,
                    reason: `maxDailyLossUsd reached (${dailyLossUsd.toFixed(2)} / ${maxDailyLossUsd.toFixed(2)})`
                };
            }
        }

        const dailyProfitTargetUsd = Number(riskLimits.dailyProfitTargetUsd);
        if (Number.isFinite(dailyProfitTargetUsd) && dailyProfitTargetUsd > 0) {
            const dailyNetPnl = await supabaseService.getProfileDailyNetPnlUsd(profileId);
            if (dailyNetPnl >= dailyProfitTargetUsd) {
                return {
                    allowed: false,
                    reason: `Daily profit target reached ($${dailyNetPnl.toFixed(2)} / $${dailyProfitTargetUsd.toFixed(2)})`
                };
            }
        }

        const maxConsecutiveLosses = Number(riskLimits.maxConsecutiveLosses);
        if (Number.isFinite(maxConsecutiveLosses) && maxConsecutiveLosses > 0) {
            const consecutiveLosses = await supabaseService.getProfileConsecutiveLosses(profileId);
            if (consecutiveLosses >= maxConsecutiveLosses) {
                return {
                    allowed: false,
                    reason: `maxConsecutiveLosses reached (${consecutiveLosses} / ${maxConsecutiveLosses})`
                };
            }
        }

        return { allowed: true };
    }
}
