import { config } from '../config/index.js';
import {
    normalizeOrderAction,
    normalizeOrderStatus,
    normalizeTradeSide
} from '../domain/tradingEnums.js';
import logger from '../utils/logger.js';
import { SymbolMapper } from '../utils/symbolMapper.js';
import {
    extractOrderSubTag,
    isBytelystSubTag,
    subTagBelongsToProfile,
    subTagHintsTrade
} from '../utils/alpacaSubTag.js';
import { healthTracker } from './healthTracker.js';
import { observabilityService } from './observabilityService.js';
import { supabaseService } from './SupabaseService.js';
import type { TradeExecutor } from './TradeExecutor.js';

type CoverageAction = 'ENTRY' | 'EXIT';

type MissingOrderCandidate = {
    orderId: string;
    timestampMs: number;
    payload: {
        user_id: string;
        profile_id: string;
        order_id: string;
        symbol: string;
        type: string;
        side: string;
        qty: number;
        price: number;
        status: string;
        timestamp: number;
        trade_id: string;
        action: CoverageAction;
        sub_tag?: string;
    };
};

type UnattributedOrderSample = {
    orderId: string;
    symbol: string;
    side: string;
    clientOrderId: string;
    subTag: string;
    submittedAt: string;
    timestampMs: number;
};

export interface ReconciliationOrderCoverageContext {
    profileId: string;
    userId: string;
    executor: TradeExecutor;
}

export interface ReconciliationOrderCoverageResult {
    attempted: boolean;
    skippedReason?: string;
    dryRun: boolean;
    scannedOrders: number;
    filledLikeOrders: number;
    botOwnedOrders: number;
    eligibleOrders: number;
    missingInDb: number;
    insertedRows: number;
    skippedNotBotOwned: number;
    skippedNotBotOwnedActionable: number;
    skippedNotBotOwnedLegacyInDb: number;
    skippedNotBotOwnedBeforeBaseline: number;
    skippedUnmappedTrade: number;
    skippedUnmappedAction: number;
    skippedMissingFillData: number;
    skippedMissingOrderId: number;
    skippedExisting: number;
    skippedMaxInsertLimit: number;
}

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

const resolveEpochMs = (value: unknown): number => {
    const parsed = Number(value);
    if (!Number.isFinite(parsed) || parsed <= 0) return 0;
    const normalized = parsed > 1_000_000_000_000 ? parsed : parsed * 1000;
    return Math.floor(normalized);
};

const resolveTimestampMs = (order: any): number => {
    const candidates = [
        order?.filled_at,
        order?.filledAt,
        order?.submitted_at,
        order?.submittedAt,
        order?.updated_at,
        order?.timestamp
    ];
    for (const candidate of candidates) {
        if (typeof candidate === 'number' && Number.isFinite(candidate) && candidate > 0) {
            return candidate > 1_000_000_000_000 ? candidate : candidate * 1000;
        }
        if (typeof candidate === 'string') {
            const parsed = Date.parse(candidate);
            if (Number.isFinite(parsed) && parsed > 0) return parsed;
            const numeric = Number(candidate);
            if (Number.isFinite(numeric) && numeric > 0) {
                return numeric > 1_000_000_000_000 ? numeric : numeric * 1000;
            }
        }
    }
    return Date.now();
};

const resolveOrderId = (order: any): string => {
    return String(order?.id || order?.order_id || '').trim();
};

const resolveClientOrderId = (order: any): string => {
    return String(order?.client_order_id || order?.clientOrderId || '').trim();
};

const resolveFilledQty = (order: any): number => {
    const candidates = [
        order?.filled_qty,
        order?.filledQty,
        order?.filled_quantity,
        order?.qty
    ];
    for (const candidate of candidates) {
        const parsed = toNumber(candidate);
        if (parsed > 0) return parsed;
    }
    return 0;
};

const resolveFilledPrice = (order: any): number => {
    const candidates = [
        order?.filled_avg_price,
        order?.avg_price,
        order?.price,
        order?.limit_price
    ];
    for (const candidate of candidates) {
        const parsed = toNumber(candidate);
        if (parsed > 0) return parsed;
    }
    return 0;
};

const startsWithCaseInsensitive = (value: string, prefix: string): boolean => {
    return value.slice(0, prefix.length).toLowerCase() === prefix.toLowerCase();
};

const parseTradeFromClientOrderId = (
    profileId: string,
    clientOrderIdRaw: string
): { tradeId: string; action?: CoverageAction } | null => {
    const clientOrderId = String(clientOrderIdRaw || '').trim();
    if (!clientOrderId) return null;
    const prefix = `bytelyst-${profileId}-`;
    if (!startsWithCaseInsensitive(clientOrderId, prefix)) return null;

    const tradeToken = clientOrderId.slice(prefix.length).trim();
    if (!tradeToken) return null;

    const lowerToken = tradeToken.toLowerCase();
    if (lowerToken.endsWith('-exit')) {
        const tradeId = tradeToken.slice(0, -5).trim();
        if (!tradeId) return null;
        return { tradeId, action: 'EXIT' };
    }
    if (lowerToken.endsWith('-entry')) {
        const tradeId = tradeToken.slice(0, -6).trim();
        if (!tradeId) return null;
        return { tradeId, action: 'ENTRY' };
    }

    return {
        tradeId: tradeToken,
        action: 'ENTRY'
    };
};

const resolveTradeFromSubTag = (
    subTag: string,
    profileId: string,
    knownTradeIds: string[]
): string => {
    if (!subTag || !isBytelystSubTag(subTag)) return '';
    if (!subTagBelongsToProfile(subTag, profileId)) return '';

    let match = '';
    let matchCount = 0;
    for (const tradeId of knownTradeIds) {
        if (!subTagHintsTrade(subTag, tradeId)) continue;
        match = tradeId;
        matchCount += 1;
        if (matchCount > 1) return '';
    }
    return matchCount === 1 ? match : '';
};

export class ReconciliationOrderCoverageService {
    private fetchCapAlertByProfile = new Map<string, number>();
    private unattributedAlertByScope = new Map<string, number>();

    private shouldEmitFetchCapAlert(profileId: string): boolean {
        const key = String(profileId || '').trim() || 'global';
        const now = Date.now();
        const throttleMs = Math.max(0, Number(config.RECONCILIATION_SLO_ALERT_THROTTLE_MS || 600_000));
        const last = this.fetchCapAlertByProfile.get(key) || 0;
        if (throttleMs > 0 && (now - last) < throttleMs) return false;
        this.fetchCapAlertByProfile.set(key, now);
        return true;
    }

    private shouldEmitUnattributedAlert(scope: string): boolean {
        const key = String(scope || '').trim() || 'global';
        const now = Date.now();
        const throttleMs = Math.max(0, Number(config.RECONCILIATION_SLO_ALERT_THROTTLE_MS || 600_000));
        const last = this.unattributedAlertByScope.get(key) || 0;
        if (throttleMs > 0 && (now - last) < throttleMs) return false;
        this.unattributedAlertByScope.set(key, now);
        return true;
    }

    async runProfile(ctx: ReconciliationOrderCoverageContext): Promise<ReconciliationOrderCoverageResult> {
        const dryRun = Boolean(config.RECON_ORDER_COVERAGE_DRY_RUN);
        if (!config.ENABLE_RECON_ORDER_COVERAGE_SYNC) {
            return {
                attempted: false,
                skippedReason: 'disabled',
                dryRun,
                scannedOrders: 0,
                filledLikeOrders: 0,
                botOwnedOrders: 0,
                eligibleOrders: 0,
                missingInDb: 0,
                insertedRows: 0,
                skippedNotBotOwned: 0,
                skippedNotBotOwnedActionable: 0,
                skippedNotBotOwnedLegacyInDb: 0,
                skippedNotBotOwnedBeforeBaseline: 0,
                skippedUnmappedTrade: 0,
                skippedUnmappedAction: 0,
                skippedMissingFillData: 0,
                skippedMissingOrderId: 0,
                skippedExisting: 0,
                skippedMaxInsertLimit: 0
            };
        }

        if (config.RECON_ORDER_COVERAGE_REQUIRE_PAUSE && !healthTracker.isPaused()) {
            return {
                attempted: false,
                skippedReason: 'requires_pause',
                dryRun,
                scannedOrders: 0,
                filledLikeOrders: 0,
                botOwnedOrders: 0,
                eligibleOrders: 0,
                missingInDb: 0,
                insertedRows: 0,
                skippedNotBotOwned: 0,
                skippedNotBotOwnedActionable: 0,
                skippedNotBotOwnedLegacyInDb: 0,
                skippedNotBotOwnedBeforeBaseline: 0,
                skippedUnmappedTrade: 0,
                skippedUnmappedAction: 0,
                skippedMissingFillData: 0,
                skippedMissingOrderId: 0,
                skippedExisting: 0,
                skippedMaxInsertLimit: 0
            };
        }

        const lookbackHours = Math.max(1, Math.floor(Number(config.RECON_ORDER_COVERAGE_LOOKBACK_HOURS || 72)));
        const fetchLimitPerPage = Math.max(1, Math.min(500, Math.floor(Number(config.RECON_ORDER_COVERAGE_FETCH_LIMIT_PER_PAGE || 500))));
        const fetchMaxPages = Math.max(1, Math.min(100, Math.floor(Number(config.RECON_ORDER_COVERAGE_MAX_FETCH_PAGES || 8))));
        const maxInserts = Math.max(1, Math.floor(Number(config.RECON_ORDER_COVERAGE_MAX_INSERTS_PER_PROFILE || 200)));
        const tradeIdLookbackRows = Math.max(100, Math.floor(Number(config.RECON_ORDER_COVERAGE_TRADE_ID_LOOKBACK_ROWS || 2000)));
        const requireSubTagAttribution = Boolean(config.RECON_ORDER_COVERAGE_REQUIRE_SUBTAG_ATTRIBUTION);
        const unattributedBaselineMs = Math.max(0, resolveEpochMs(config.RECON_ORDER_COVERAGE_UNATTRIBUTED_BASELINE_MS));
        const exchangeClosedOrders = await ctx.executor.fetchExchangeClosedOrders(undefined, lookbackHours, {
            limitPerPage: fetchLimitPerPage,
            maxPages: fetchMaxPages
        });
        const knownTradeIds = await supabaseService.getKnownTradeIdsForProfile(ctx.profileId, tradeIdLookbackRows);
        const candidateByOrderId = new Map<string, MissingOrderCandidate>();
        const unattributedRows: UnattributedOrderSample[] = [];

        const result: ReconciliationOrderCoverageResult = {
            attempted: true,
            dryRun,
            scannedOrders: exchangeClosedOrders.length,
            filledLikeOrders: 0,
            botOwnedOrders: 0,
            eligibleOrders: 0,
            missingInDb: 0,
            insertedRows: 0,
            skippedNotBotOwned: 0,
            skippedNotBotOwnedActionable: 0,
            skippedNotBotOwnedLegacyInDb: 0,
            skippedNotBotOwnedBeforeBaseline: 0,
            skippedUnmappedTrade: 0,
            skippedUnmappedAction: 0,
            skippedMissingFillData: 0,
            skippedMissingOrderId: 0,
            skippedExisting: 0,
            skippedMaxInsertLimit: 0
        };

        const approximateFetchCap = fetchLimitPerPage * fetchMaxPages;
        if (exchangeClosedOrders.length >= approximateFetchCap) {
            logger.warn('[ReconcileCoverage] Exchange closed-order fetch may be at page cap', {
                event: 'reconciliation_order_coverage_fetch_cap_reached',
                profileId: ctx.profileId,
                userId: ctx.userId,
                scannedOrders: exchangeClosedOrders.length,
                lookbackHours,
                fetchLimitPerPage,
                fetchMaxPages
            });
            if (this.shouldEmitFetchCapAlert(ctx.profileId)) {
                observabilityService.emitEvent({
                    type: 'RECONCILIATION_DEGRADED',
                    severity: 'WARN',
                    message: `Closed-order coverage fetch reached cap (${approximateFetchCap}) for profile ${ctx.profileId}. Consider increasing RECON_ORDER_COVERAGE_MAX_FETCH_PAGES.`,
                    profileId: ctx.profileId,
                    userId: ctx.userId
                });
            }
        }

        for (const order of exchangeClosedOrders || []) {
            const status = normalizeOrderStatus(String(order?.status || ''));
            if (status !== 'filled' && status !== 'partially_filled') continue;
            result.filledLikeOrders += 1;

            const orderId = resolveOrderId(order);
            if (!orderId) {
                result.skippedMissingOrderId += 1;
                continue;
            }

            const side = normalizeTradeSide(String(order?.side || 'BUY'));
            const subTag = extractOrderSubTag(order);
            const clientOrderId = resolveClientOrderId(order);
            const timestampMs = resolveTimestampMs(order);
            const clientCorrelation = parseTradeFromClientOrderId(ctx.profileId, clientOrderId);
            const ownedBySubTag = !!(subTag && isBytelystSubTag(subTag) && subTagBelongsToProfile(subTag, ctx.profileId));
            const ownedByClient = !!clientCorrelation;
            const orderIsAttributed = requireSubTagAttribution
                ? ownedBySubTag
                : (ownedBySubTag || ownedByClient);
            if (!orderIsAttributed) {
                result.skippedNotBotOwned += 1;
                unattributedRows.push({
                    orderId,
                    symbol: String(order?.symbol || order?.symbol_name || '').trim(),
                    side,
                    clientOrderId,
                    subTag,
                    submittedAt: String(order?.submitted_at || order?.filled_at || order?.updated_at || '').trim(),
                    timestampMs
                });
                continue;
            }
            result.botOwnedOrders += 1;

            let tradeId = String(order?.trade_id || order?.tradeId || '').trim();
            if (!tradeId && clientCorrelation?.tradeId) {
                tradeId = clientCorrelation.tradeId;
            }
            if (!tradeId && ownedBySubTag) {
                tradeId = resolveTradeFromSubTag(subTag, ctx.profileId, knownTradeIds);
            }
            if (!tradeId) {
                result.skippedUnmappedTrade += 1;
                continue;
            }

            const explicitAction = normalizeOrderAction(order?.action);
            const action = explicitAction || clientCorrelation?.action;
            if (!action) {
                result.skippedUnmappedAction += 1;
                continue;
            }

            const qty = resolveFilledQty(order);
            const price = resolveFilledPrice(order);
            if (!(qty > 0) || !(price > 0)) {
                result.skippedMissingFillData += 1;
                continue;
            }

            const rawSymbol = String(order?.symbol || order?.symbol_name || '').trim();
            const normalizedSymbol = rawSymbol
                ? SymbolMapper.toDataSymbol(rawSymbol, config.EXECUTION_PROVIDER)
                : '';
            if (!normalizedSymbol) {
                result.skippedUnmappedTrade += 1;
                continue;
            }
            candidateByOrderId.set(orderId, {
                orderId,
                timestampMs,
                payload: {
                    user_id: ctx.userId,
                    profile_id: ctx.profileId,
                    order_id: orderId,
                    symbol: normalizedSymbol,
                    type: String(order?.type || order?.order_type || 'market').toLowerCase(),
                    side,
                    qty,
                    price,
                    status,
                    timestamp: timestampMs,
                    trade_id: tradeId,
                    action,
                    sub_tag: subTag || undefined
                }
            });
        }

        let actionableUnattributedRows = unattributedRows;
        if (unattributedRows.length > 0) {
            const legacyKnownIds = await supabaseService.getExistingOrderIds(
                unattributedRows.map((row) => row.orderId),
                ctx.profileId
            );
            actionableUnattributedRows = unattributedRows.filter((row) => {
                if (legacyKnownIds.has(row.orderId)) {
                    result.skippedNotBotOwnedLegacyInDb += 1;
                    return false;
                }
                if (unattributedBaselineMs > 0 && row.timestampMs < unattributedBaselineMs) {
                    result.skippedNotBotOwnedBeforeBaseline += 1;
                    return false;
                }
                return true;
            });
        }
        result.skippedNotBotOwnedActionable = actionableUnattributedRows.length;

        if (result.skippedNotBotOwnedActionable > 0 && this.shouldEmitUnattributedAlert('global')) {
            const sampleSummary = actionableUnattributedRows
                .map((row) => `${row.orderId}:${row.symbol}:${row.side}`)
                .slice(0, 5)
                .join(', ');
            const message = `Unattributed filled exchange orders detected (global watchdog): actionable_unattributed=${result.skippedNotBotOwnedActionable}, total_unattributed=${result.skippedNotBotOwned}, legacy_db_suppressed=${result.skippedNotBotOwnedLegacyInDb}, baseline_suppressed=${result.skippedNotBotOwnedBeforeBaseline}, profile=${ctx.profileId}, require_profile_subtag_attribution=${requireSubTagAttribution}. Samples: ${sampleSummary || 'n/a'}`;
            logger.warn('[ReconcileCoverage] Filled orders missing bot attribution', {
                event: 'reconciliation_order_coverage_unattributed_fills',
                profileId: ctx.profileId,
                userId: ctx.userId,
                skippedNotBotOwned: result.skippedNotBotOwned,
                skippedNotBotOwnedActionable: result.skippedNotBotOwnedActionable,
                skippedNotBotOwnedLegacyInDb: result.skippedNotBotOwnedLegacyInDb,
                skippedNotBotOwnedBeforeBaseline: result.skippedNotBotOwnedBeforeBaseline,
                unattributedBaselineMs,
                requireSubTagAttribution,
                samples: actionableUnattributedRows.slice(0, 5)
            });
            observabilityService.emitEvent({
                type: 'RECONCILIATION_DEGRADED',
                severity: 'WARN',
                message,
                userId: ctx.userId
            });
        }
        const pauseThreshold = Math.max(1, Math.floor(Number(config.RECON_ORDER_COVERAGE_UNATTRIBUTED_PAUSE_MIN_COUNT || 1)));
        const shouldAutoPause = Boolean(config.RECON_ORDER_COVERAGE_AUTO_PAUSE_ON_UNATTRIBUTED_FILLS);
        if (
            shouldAutoPause
            && result.skippedNotBotOwnedActionable >= pauseThreshold
            && !healthTracker.isPaused()
        ) {
            const sampleSummary = actionableUnattributedRows
                .map((row) => `${row.orderId}:${row.symbol}:${row.side}`)
                .slice(0, 5)
                .join(', ');
            const reason = `Auto-paused by unattributed fill watchdog for profile ${ctx.profileId}: actionable_unattributed=${result.skippedNotBotOwnedActionable} (threshold=${pauseThreshold}), total_unattributed=${result.skippedNotBotOwned}, legacy_db_suppressed=${result.skippedNotBotOwnedLegacyInDb}, baseline_suppressed=${result.skippedNotBotOwnedBeforeBaseline}. Samples: ${sampleSummary || 'n/a'}`;
            healthTracker.recordTradingControl({
                mode: 'PAUSED',
                lastChangedBy: 'system:recon_unattributed_fill_watchdog',
                lastChangedAt: Date.now(),
                reason
            });
            logger.error(`[ReconcileCoverage] ${reason}`);
            observabilityService.emitEvent({
                type: 'RECONCILIATION_DEGRADED',
                severity: 'ERROR',
                message: reason,
                profileId: ctx.profileId,
                userId: ctx.userId
            });
        } else if (healthTracker.isPaused() && result.skippedNotBotOwnedActionable === 0) {
            const currentControl = healthTracker.getSnapshot().tradingControl;
            const changedBy = String(currentControl.lastChangedBy || '').trim();
            if (changedBy === 'system:recon_unattributed_fill_watchdog') {
                const reason = `Auto-resumed unattributed watchdog pause for profile ${ctx.profileId}: actionable_unattributed=0 (total_unattributed=${result.skippedNotBotOwned}, legacy_db_suppressed=${result.skippedNotBotOwnedLegacyInDb}, baseline_suppressed=${result.skippedNotBotOwnedBeforeBaseline}).`;
                healthTracker.recordTradingControl({
                    mode: 'RUNNING',
                    lastChangedBy: 'system:recon_unattributed_fill_watchdog:auto_resume',
                    lastChangedAt: Date.now(),
                    reason
                });
                logger.info(`[ReconcileCoverage] ${reason}`);
                observabilityService.emitEvent({
                    type: 'RECONCILIATION_DEGRADED',
                    severity: 'INFO',
                    message: reason,
                    profileId: ctx.profileId,
                    userId: ctx.userId
                });
            }
        }

        const candidates = Array.from(candidateByOrderId.values())
            .sort((a, b) => a.timestampMs - b.timestampMs);
        result.eligibleOrders = candidates.length;
        if (candidates.length === 0) return result;

        const existing = await supabaseService.getExistingOrderIds(
            candidates.map((row) => row.orderId)
        );
        const missing = candidates.filter((row) => !existing.has(row.orderId));
        result.skippedExisting = candidates.length - missing.length;
        result.missingInDb = missing.length;
        if (missing.length === 0 || dryRun) return result;

        const toInsert = missing.slice(0, maxInserts);
        result.skippedMaxInsertLimit = Math.max(0, missing.length - toInsert.length);

        for (const row of toInsert) {
            await supabaseService.logOrder(row.payload);
        }

        const insertedSet = await supabaseService.getExistingOrderIds(
            toInsert.map((row) => row.orderId)
        );
        result.insertedRows = toInsert.filter((row) => insertedSet.has(row.orderId)).length;

        if (result.insertedRows > 0 || result.skippedMaxInsertLimit > 0) {
            logger.warn('[Reconcile] Order coverage sync evaluated', {
                event: 'reconciliation_order_coverage_profile',
                profileId: ctx.profileId,
                userId: ctx.userId,
                lookbackHours,
                scannedOrders: result.scannedOrders,
                filledLikeOrders: result.filledLikeOrders,
                botOwnedOrders: result.botOwnedOrders,
                eligibleOrders: result.eligibleOrders,
                missingInDb: result.missingInDb,
                insertedRows: result.insertedRows,
                skippedNotBotOwned: result.skippedNotBotOwned,
                skippedNotBotOwnedActionable: result.skippedNotBotOwnedActionable,
                skippedNotBotOwnedLegacyInDb: result.skippedNotBotOwnedLegacyInDb,
                skippedNotBotOwnedBeforeBaseline: result.skippedNotBotOwnedBeforeBaseline,
                skippedUnmappedTrade: result.skippedUnmappedTrade,
                skippedUnmappedAction: result.skippedUnmappedAction,
                skippedMissingFillData: result.skippedMissingFillData,
                skippedMissingOrderId: result.skippedMissingOrderId,
                skippedExisting: result.skippedExisting,
                skippedMaxInsertLimit: result.skippedMaxInsertLimit,
                dryRun: result.dryRun
            });
        }

        return result;
    }
}

export const reconciliationOrderCoverageService = new ReconciliationOrderCoverageService();
