import { createHash } from 'crypto';
import { config } from '../config/index.js';
import logger from '../utils/logger.js';
import { healthTracker } from './healthTracker.js';
import { observabilityService } from './observabilityService.js';
import { supabaseService } from './SupabaseService.js';
import type { TradeExecutor } from './TradeExecutor.js';
import { buildAlpacaSubTag } from '../utils/alpacaSubTag.js';
import { normalizeBotSymbolToken } from '../utils/botSymbolScope.js';

type TradeSide = 'BUY' | 'SELL';

type TradeParityState = {
    streak: number;
    lastMismatchAt: number;
    quarantined: boolean;
    quarantinedReason?: string;
    lastSyntheticOrderId?: string;
};

type TradeSlice = {
    symbol: string;
    side: TradeSide;
    qty: number;
    entryPrice: number;
    profileId: string;
    userId?: string;
    tradeId: string;
};

export interface ReconciliationParityHeartbeatContext {
    profileId: string;
    userId: string;
    executor: TradeExecutor;
    monitoredSymbols?: string[];
}

export interface ReconciliationParityHeartbeatResult {
    attempted: boolean;
    skippedReason?: string;
    symbolsChecked: number;
    mismatchTrades: number;
    quarantinedTrades: number;
    autoClosedTrades: number;
    maxMismatchNotionalUsd: number;
    totalMismatchNotionalUsd: number;
    integrityWatchdogTriggered: boolean;
}

const toNumber = (value: unknown): number => {
    const parsed = Number(value);
    return Number.isFinite(parsed) ? parsed : 0;
};

const toTradeSide = (value: unknown): TradeSide => {
    const side = String(value || '').trim().toUpperCase();
    return side === 'SELL' ? 'SELL' : 'BUY';
};

const expectedExitSide = (entrySide: TradeSide): TradeSide => (entrySide === 'BUY' ? 'SELL' : 'BUY');

const resolveSafeSymbols = (symbols: string[] | undefined): string[] => {
    const source = Array.isArray(symbols) && symbols.length > 0 ? symbols : config.SYMBOLS;
    return Array.from(new Set(
        source
            .map((symbol) => String(symbol || '').trim())
            .filter(Boolean)
    )).sort((a, b) => a.localeCompare(b));
};

const buildTradeStateKey = (profileId: string, symbol: string, tradeId: string): string => {
    const normalizedProfile = String(profileId || '').trim();
    const normalizedSymbol = normalizeBotSymbolToken(symbol);
    const normalizedTradeId = String(tradeId || '').trim();
    return `${normalizedProfile}::${normalizedSymbol}::${normalizedTradeId}`;
};

const buildSyntheticExitOrderId = (profileId: string, symbol: string, tradeId: string): string => {
    const digest = createHash('md5')
        .update(`${String(profileId || '').trim()}:${normalizeBotSymbolToken(symbol)}:${String(tradeId || '').trim()}`)
        .digest('hex')
        .toUpperCase();
    return `SYNEXIT-${digest}`;
};

const calcDustThreshold = (openQty: number): number => {
    const absDust = Math.max(0, toNumber(config.RECON_EXIT_BACKFILL_DUST_ABS_QTY));
    const relDust = Math.max(0, toNumber(config.RECON_EXIT_BACKFILL_DUST_REL_PCT)) * Math.max(0, openQty);
    const parityAbsFloor = Math.max(0, toNumber(config.RECON_POSITION_PARITY_DUST_ABS_QTY));
    return Math.max(absDust, relDust, parityAbsFloor);
};

const computeExchangePositionQty = (position: any): number => {
    const candidates = [
        position?.qty,
        position?.size,
        position?.position_qty,
        position?.contracts
    ];
    for (const candidate of candidates) {
        const parsed = Math.abs(toNumber(candidate));
        if (parsed > 0) return parsed;
    }
    return 0;
};

const normalizeTradeSlices = async (
    profileId: string,
    symbol: string
): Promise<TradeSlice[]> => {
    const virtualPosition = await supabaseService.getVirtualOpenPosition(profileId, symbol);
    if (!virtualPosition || !(toNumber(virtualPosition.qty) > 0)) return [];

    const tradeIds = Array.from(new Set(
        (virtualPosition.tradeIds || [virtualPosition.tradeId])
            .map((tradeId) => String(tradeId || '').trim())
            .filter(Boolean)
    ));
    if (tradeIds.length === 0) return [];

    const slices: TradeSlice[] = [];
    for (const tradeId of tradeIds) {
        const slice = await supabaseService.getVirtualOpenPositionForTrade(profileId, symbol, tradeId);
        if (!slice || !(toNumber(slice.qty) > 0)) continue;
        slices.push({
            symbol: String(slice.symbol || symbol).trim() || symbol,
            side: toTradeSide(slice.side),
            qty: Math.max(0, toNumber(slice.qty)),
            entryPrice: Math.max(0, toNumber(slice.entryPrice)),
            profileId: String(slice.profileId || profileId).trim() || profileId,
            userId: String(slice.userId || '').trim() || undefined,
            tradeId: String(slice.tradeId || tradeId).trim() || tradeId
        });
    }
    return slices;
};

const roundQty = (value: number): number => Number(value.toFixed(8));
const roundPrice = (value: number): number => Number(value.toFixed(8));

export class ReconciliationParityHeartbeatService {
    private tradeParityState = new Map<string, TradeParityState>();
    private eventThrottleByKey = new Map<string, number>();
    private watchdogThrottleByProfile = new Map<string, number>();

    private shouldEmitEvent(key: string, throttleMs: number): boolean {
        const now = Date.now();
        const previous = this.eventThrottleByKey.get(key) || 0;
        if (throttleMs > 0 && (now - previous) < throttleMs) {
            return false;
        }
        this.eventThrottleByKey.set(key, now);
        return true;
    }

    private cleanupSymbolState(profileId: string, symbol: string): void {
        const prefix = `${String(profileId || '').trim()}::${normalizeBotSymbolToken(symbol)}::`;
        for (const key of Array.from(this.tradeParityState.keys())) {
            if (key.startsWith(prefix)) {
                this.tradeParityState.delete(key);
            }
        }
    }

    private maybePauseTradingForHighRiskMismatch(
        profileId: string,
        userId: string,
        symbol: string,
        cumulativeMismatchNotionalUsd: number,
        allocatedCapitalUsd: number
    ): boolean {
        if (!config.ENABLE_RECON_INTEGRITY_WATCHDOG) return false;
        if (allocatedCapitalUsd <= 0) return false;

        const ratio = cumulativeMismatchNotionalUsd / allocatedCapitalUsd;
        const threshold = Math.max(0.01, toNumber(config.RECON_POSITION_PARITY_MAX_NOTIONAL_PCT || 0.5));
        if (ratio < threshold) return false;

        const profileKey = String(profileId || '').trim() || 'global';
        const throttleMs = Math.max(0, toNumber(config.RECON_INTEGRITY_WATCHDOG_THROTTLE_MS || 600_000));
        const now = Date.now();
        const previous = this.watchdogThrottleByProfile.get(profileKey) || 0;
        if (throttleMs > 0 && (now - previous) < throttleMs) {
            return true;
        }
        this.watchdogThrottleByProfile.set(profileKey, now);

        const message = `Integrity watchdog paused trading for ${profileId} (${symbol}): cumulative_mismatch_notional=${cumulativeMismatchNotionalUsd.toFixed(2)} exceeds ${(threshold * 100).toFixed(1)}% of allocated capital ${allocatedCapitalUsd.toFixed(2)}.`;
        logger.error(`[ReconcileParity] ${message}`);
        observabilityService.emitEvent({
            type: 'RECONCILIATION_DEGRADED',
            severity: 'ERROR',
            message,
            profileId,
            userId,
            symbol
        });
        if (!healthTracker.isPaused()) {
            healthTracker.recordTradingControl({
                mode: 'PAUSED',
                lastChangedBy: 'system:recon_parity_watchdog',
                lastChangedAt: now,
                reason: message
            });
        }
        return true;
    }

    private buildSyntheticExitPayload(ctx: ReconciliationParityHeartbeatContext, trade: TradeSlice) {
        const orderId = buildSyntheticExitOrderId(ctx.profileId, trade.symbol, trade.tradeId);
        const exitPrice = trade.entryPrice > 0 ? trade.entryPrice : 0;
        const qty = roundQty(trade.qty);
        const side = expectedExitSide(trade.side);
        const subTag = buildAlpacaSubTag({
            profileId: ctx.profileId,
            tradeId: trade.tradeId,
            intent: 'EXIT'
        }) || undefined;
        return {
            orderId,
            payload: {
                user_id: trade.userId || ctx.userId,
                profile_id: ctx.profileId,
                order_id: orderId,
                symbol: trade.symbol,
                type: 'market',
                side,
                qty,
                quantity: qty,
                price: roundPrice(exitPrice),
                status: 'filled',
                timestamp: Date.now(),
                trade_id: trade.tradeId,
                action: 'EXIT' as const,
                source: 'BOT' as const,
                sub_tag: subTag
            }
        };
    }

    public async runProfile(ctx: ReconciliationParityHeartbeatContext): Promise<ReconciliationParityHeartbeatResult> {
        const enabled = Boolean(config.ENABLE_RECON_POSITION_PARITY_HEARTBEAT);
        const dryRun = Boolean(config.RECON_POSITION_PARITY_DRY_RUN);
        if (!enabled) {
            return {
                attempted: false,
                skippedReason: 'disabled',
                symbolsChecked: 0,
                mismatchTrades: 0,
                quarantinedTrades: 0,
                autoClosedTrades: 0,
                maxMismatchNotionalUsd: 0,
                totalMismatchNotionalUsd: 0,
                integrityWatchdogTriggered: false
            };
        }

        const profileId = String(ctx.profileId || '').trim();
        if (!profileId) {
            return {
                attempted: false,
                skippedReason: 'missing_profile',
                symbolsChecked: 0,
                mismatchTrades: 0,
                quarantinedTrades: 0,
                autoClosedTrades: 0,
                maxMismatchNotionalUsd: 0,
                totalMismatchNotionalUsd: 0,
                integrityWatchdogTriggered: false
            };
        }

        const monitoredSymbols = resolveSafeSymbols(ctx.monitoredSymbols);
        const confirmationThreshold = Math.max(1, Math.floor(toNumber(config.RECON_POSITION_PARITY_CONFIRMATIONS || 3)));
        const throttleMs = Math.max(0, toNumber(config.RECON_INTEGRITY_WATCHDOG_THROTTLE_MS || 600_000));
        const requireSubTagAttribution = Boolean(config.RECON_POSITION_PARITY_REQUIRE_SUBTAG_ATTRIBUTION);
        const allowLegacyEntryAttribution = Boolean(config.RECON_POSITION_PARITY_ALLOW_LEGACY_ENTRY_ATTRIBUTION);
        const profileCapital = await supabaseService.getProfileCapital(profileId);
        const allocatedCapitalUsd = Math.max(0, toNumber(profileCapital?.allocatedCapital));

        let mismatchTrades = 0;
        let quarantinedTrades = 0;
        let autoClosedTrades = 0;
        let maxMismatchNotionalUsd = 0;
        let totalMismatchNotionalUsd = 0;
        let integrityWatchdogTriggered = false;

        for (const symbol of monitoredSymbols) {
            const slices = await normalizeTradeSlices(profileId, symbol);
            if (!slices.length) {
                this.cleanupSymbolState(profileId, symbol);
                continue;
            }

            const exchangePosition = await ctx.executor.fetchExchangePosition(symbol);
            const exchangeQty = computeExchangePositionQty(exchangePosition);
            const totalVirtualQty = slices.reduce((sum, item) => sum + Math.max(0, item.qty), 0);
            const symbolDustThreshold = calcDustThreshold(totalVirtualQty);

            // Parity is healthy for this symbol if exchange quantity is not effectively flat.
            if (exchangeQty > symbolDustThreshold) {
                this.cleanupSymbolState(profileId, symbol);
                continue;
            }

            let symbolAttributableNotionalUsd = 0;

            for (const trade of slices) {
                const tradeDustThreshold = calcDustThreshold(trade.qty);
                if (trade.qty <= tradeDustThreshold) {
                    this.tradeParityState.delete(buildTradeStateKey(profileId, trade.symbol, trade.tradeId));
                    continue;
                }

                mismatchTrades += 1;
                const stateKey = buildTradeStateKey(profileId, trade.symbol, trade.tradeId);
                const existing = this.tradeParityState.get(stateKey);
                const nextState: TradeParityState = {
                    streak: (existing?.streak || 0) + 1,
                    lastMismatchAt: Date.now(),
                    quarantined: false,
                    quarantinedReason: undefined,
                    lastSyntheticOrderId: existing?.lastSyntheticOrderId
                };

                if (requireSubTagAttribution) {
                    let attributed = await supabaseService.hasLifecycleEntryOrderWithProfileSubTag(
                        trade.tradeId,
                        profileId,
                        trade.symbol
                    );
                    let attributionMode: 'subtag' | 'legacy_entry' | null = attributed ? 'subtag' : null;

                    if (!attributed && allowLegacyEntryAttribution) {
                        const legacyAttributed = await supabaseService.hasLifecycleEntryOrder(
                            trade.tradeId,
                            profileId,
                            trade.symbol
                        );
                        if (legacyAttributed) {
                            attributed = true;
                            attributionMode = 'legacy_entry';
                        }
                    }

                    if (!attributed) {
                        nextState.quarantined = true;
                        nextState.quarantinedReason = 'missing_profile_subtag_attribution';
                        this.tradeParityState.set(stateKey, nextState);
                        quarantinedTrades += 1;

                        if (this.shouldEmitEvent(`${stateKey}:quarantine`, throttleMs)) {
                            const message = `Parity mismatch quarantined for ${trade.symbol} trade=${trade.tradeId}: missing profile-attributed sub-tag evidence.`;
                            logger.warn(`[ReconcileParity] ${message}`);
                            observabilityService.emitEvent({
                                type: 'RECONCILIATION_DEGRADED',
                                severity: 'WARN',
                                message,
                                profileId,
                                userId: ctx.userId,
                                symbol: trade.symbol
                            });
                        }
                        continue;
                    }

                    if (attributionMode === 'legacy_entry' && this.shouldEmitEvent(`${stateKey}:legacy_attribution`, throttleMs)) {
                        const message = `Parity mismatch accepted for ${trade.symbol} trade=${trade.tradeId} using legacy entry attribution fallback (missing profile sub-tag).`;
                        logger.warn(`[ReconcileParity] ${message}`);
                        observabilityService.emitEvent({
                            type: 'RECONCILIATION_DEGRADED',
                            severity: 'WARN',
                            message,
                            profileId,
                            userId: ctx.userId,
                            symbol: trade.symbol
                        });
                    }
                }

                const tradeNotionalUsd = Math.max(0, trade.qty) * Math.max(0, trade.entryPrice);
                symbolAttributableNotionalUsd += tradeNotionalUsd;
                totalMismatchNotionalUsd += tradeNotionalUsd;
                this.tradeParityState.set(stateKey, nextState);
                if (nextState.streak < confirmationThreshold) {
                    continue;
                }

                const synthetic = this.buildSyntheticExitPayload(ctx, trade);
                const existingIds = await supabaseService.getExistingOrderIds([synthetic.orderId], profileId);
                if (existingIds.has(synthetic.orderId)) {
                    this.tradeParityState.delete(stateKey);
                    continue;
                }

                if (dryRun) {
                    if (this.shouldEmitEvent(`${stateKey}:dry_run`, throttleMs)) {
                        const message = `Parity dry-run would close ${trade.symbol} trade=${trade.tradeId} after ${nextState.streak} confirmations.`;
                        logger.warn(`[ReconcileParity] ${message}`);
                        observabilityService.emitEvent({
                            type: 'PARITY_WARNING',
                            severity: 'WARN',
                            message,
                            profileId,
                            userId: ctx.userId,
                            symbol: trade.symbol
                        });
                    }
                    continue;
                }

                await supabaseService.logOrder(synthetic.payload);
                await ctx.executor.reconcileExitFill(
                    synthetic.payload,
                    Number(synthetic.payload.price || 0),
                    Number(synthetic.payload.qty || 0)
                );
                autoClosedTrades += 1;
                this.tradeParityState.delete(stateKey);

                if (this.shouldEmitEvent(`${stateKey}:auto_closed`, throttleMs)) {
                    const message = `Parity heartbeat auto-closed ghost trade ${trade.tradeId} for ${trade.symbol} after ${nextState.streak} confirmations.`;
                    logger.warn(`[ReconcileParity] ${message}`);
                    observabilityService.emitEvent({
                        type: 'PARITY_WARNING',
                        severity: 'WARN',
                        message,
                        profileId,
                        userId: ctx.userId,
                        symbol: trade.symbol
                    });
                }
            }

            maxMismatchNotionalUsd = Math.max(maxMismatchNotionalUsd, symbolAttributableNotionalUsd);
            if (symbolAttributableNotionalUsd > 0) {
                if (this.maybePauseTradingForHighRiskMismatch(profileId, ctx.userId, symbol, totalMismatchNotionalUsd, allocatedCapitalUsd)) {
                    integrityWatchdogTriggered = true;
                }
            }
        }

        return {
            attempted: true,
            symbolsChecked: monitoredSymbols.length,
            mismatchTrades,
            quarantinedTrades,
            autoClosedTrades,
            maxMismatchNotionalUsd: Number(maxMismatchNotionalUsd.toFixed(2)),
            totalMismatchNotionalUsd: Number(totalMismatchNotionalUsd.toFixed(2)),
            integrityWatchdogTriggered
        };
    }
}

export const reconciliationParityHeartbeatService = new ReconciliationParityHeartbeatService();
