export type LifecycleSide = 'BUY' | 'SELL';
export type LifecycleAction = 'ENTRY' | 'EXIT';

export interface LifecycleOrderRow {
    id?: string;
    order_id?: string;
    profile_id?: string;
    trade_id?: string;
    symbol?: string;
    side?: string;
    action?: string;
    qty?: number | string | null;
    quantity?: number | string | null;
    price?: number | string | null;
    status?: string;
    source?: string;
    timestamp?: number | string;
    filled_at?: string;
    created_at?: string;
}

export interface CanonicalLifecycleTrade {
    id: string;
    tradeId: string;
    profileId?: string;
    symbol: string;
    side: LifecycleSide;
    size: number;
    entryPrice: number;
    exitPrice: number;
    pnl: number;
    pnlPercent: number;
    closedAtMs: number;
}

export interface CanonicalLifecycleAggregate {
    totalPnl: number;
    tradeCount: number;
    wins: number;
    winRate: number;
    byProfile: Record<string, {
        tradeCount: number;
        wins: number;
        winRate: number;
        realizedPnl: number;
        lastClosedTradeAt: number;
    }>;
}

const EPS = 1e-8;
const SYNTHETIC_ORDER_PREFIXES = ['BFILL-', 'MANOVR-', 'RECON-BF', 'RECON-', 'SYNC-'];

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

const normalizeTimestamp = (row: LifecycleOrderRow): number => {
    const raw = row.filled_at || row.timestamp || row.created_at;
    if (typeof raw === 'number') {
        return raw > 1_000_000_000_000 ? raw : raw * 1000;
    }
    if (typeof raw === 'string') {
        const trimmed = raw.trim();
        if (/^\d+(\.\d+)?$/.test(trimmed)) {
            return normalizeTimestamp({ ...row, timestamp: Number(trimmed), filled_at: undefined });
        }
        const parsed = Date.parse(trimmed);
        if (Number.isFinite(parsed) && parsed > 0) return parsed;
    }
    return 0;
};

const normalizeStatus = (status?: string | null): string => {
    const normalized = String(status || '').trim().toLowerCase();
    if (normalized === 'partially-filled') return 'partially_filled';
    return normalized;
};

const normalizeSide = (side?: string | null): LifecycleSide => {
    const normalized = String(side || '').trim().toUpperCase();
    return normalized === 'SELL' || normalized === 'SHORT' ? 'SELL' : 'BUY';
};

const normalizeAction = (action?: string | null): LifecycleAction | undefined => {
    const normalized = String(action || '').trim().toUpperCase();
    if (normalized === 'ENTRY' || normalized === 'EXIT') return normalized;
    return undefined;
};

const inferAction = (row: LifecycleOrderRow, entrySide: LifecycleSide | null): LifecycleAction | undefined => {
    const explicit = normalizeAction(row.action);
    if (explicit) return explicit;

    const tradeId = String(row.trade_id || '').trim();
    if (!tradeId) return undefined;

    if (!entrySide) {
        // Without explicit action and no known entry side yet, classification is ambiguous.
        // Skipping this row is safer than forcing an ENTRY on potentially truncated datasets.
        return undefined;
    }

    const side = normalizeSide(row.side);
    return side === entrySide ? 'ENTRY' : 'EXIT';
};

const isSyntheticOrderId = (orderId: string): boolean => {
    const normalized = String(orderId || '').trim().toUpperCase();
    if (!normalized) return false;
    return SYNTHETIC_ORDER_PREFIXES.some((prefix) => normalized.startsWith(prefix));
};

const getOrderQty = (row: LifecycleOrderRow): number => {
    const qty = toNumber(row.qty);
    if (qty > 0) return qty;
    return toNumber(row.quantity);
};

const isFilledLifecycleOrder = (row: LifecycleOrderRow): boolean => {
    const status = normalizeStatus(row.status);
    if (status !== 'filled' && status !== 'partially_filled') return false;

    const qty = getOrderQty(row);
    const price = toNumber(row.price);
    if (!(qty > 0) || !(price > 0)) return false;

    const tradeId = String(row.trade_id || '').trim();
    if (!tradeId) return false;

    const source = String(row.source || '').trim().toUpperCase();
    if (source === 'MANUAL') return false;

    const orderId = String(row.order_id || '').trim();
    if (isSyntheticOrderId(orderId)) return false;

    return true;
};

const sortByTimestampAsc = (left: LifecycleOrderRow, right: LifecycleOrderRow): number => {
    const leftTs = normalizeTimestamp(left);
    const rightTs = normalizeTimestamp(right);
    if (leftTs !== rightTs) return leftTs - rightTs;
    return String(left.order_id || left.id || '').localeCompare(String(right.order_id || right.id || ''));
};

export const buildCanonicalLifecycleTrades = (rows: LifecycleOrderRow[]): CanonicalLifecycleTrade[] => {
    const byLifecycle = new Map<string, LifecycleOrderRow[]>();

    for (const row of rows || []) {
        if (!isFilledLifecycleOrder(row)) continue;
        const profileId = String(row.profile_id || '').trim();
        const tradeId = String(row.trade_id || '').trim();
        const key = `${profileId || 'global'}|${tradeId}`;
        const list = byLifecycle.get(key) || [];
        list.push(row);
        byLifecycle.set(key, list);
    }

    const out: CanonicalLifecycleTrade[] = [];
    for (const [key, lifecycleRows] of byLifecycle.entries()) {
        const sorted = [...lifecycleRows].sort(sortByTimestampAsc);
        const [profileIdRaw, tradeId] = key.split('|');
        const profileId = profileIdRaw === 'global' ? undefined : profileIdRaw;

        const lots: Array<{ qty: number; price: number }> = [];
        let entrySide: LifecycleSide | null = null;
        let closedQty = 0;
        let closedEntryNotional = 0;
        let closedExitNotional = 0;
        let realizedPnl = 0;
        let symbol = '';
        let lastExitTs = 0;

        for (const row of sorted) {
            const qty = getOrderQty(row);
            const price = toNumber(row.price);
            if (!(qty > 0) || !(price > 0)) continue;

            if (!symbol) symbol = String(row.symbol || '').trim();
            const side = normalizeSide(row.side);
            const action = inferAction(row, entrySide);
            if (!action) continue;

            if (action === 'ENTRY') {
                if (!entrySide) entrySide = side;
                if (entrySide !== side) continue;
                lots.push({ qty, price });
                continue;
            }

            if (!entrySide) continue;
            const expectedExitSide: LifecycleSide = entrySide === 'BUY' ? 'SELL' : 'BUY';
            if (side !== expectedExitSide) continue;

            let remaining = qty;
            while (remaining > EPS && lots.length > 0) {
                const lot = lots[0];
                const matched = Math.min(remaining, lot.qty);
                if (matched <= EPS) break;

                lot.qty -= matched;
                remaining -= matched;
                closedQty += matched;
                closedEntryNotional += matched * lot.price;
                closedExitNotional += matched * price;
                realizedPnl += entrySide === 'BUY'
                    ? (price - lot.price) * matched
                    : (lot.price - price) * matched;
                if (lot.qty <= EPS) lots.shift();
            }

            lastExitTs = Math.max(lastExitTs, normalizeTimestamp(row));
        }

        if (!(closedQty > EPS) || !entrySide) continue;

        const avgEntry = closedEntryNotional / closedQty;
        const avgExit = closedExitNotional / closedQty;
        const pnlPercent = avgEntry > 0
            ? ((avgExit - avgEntry) / avgEntry) * 100 * (entrySide === 'BUY' ? 1 : -1)
            : 0;

        out.push({
            id: `canonical:${profileId || 'global'}:${tradeId}`,
            tradeId,
            profileId,
            symbol,
            side: entrySide,
            size: closedQty,
            entryPrice: avgEntry,
            exitPrice: avgExit,
            pnl: realizedPnl,
            pnlPercent,
            closedAtMs: lastExitTs || normalizeTimestamp(sorted[sorted.length - 1])
        });
    }

    return out.sort((a, b) => (b.closedAtMs - a.closedAtMs) || b.id.localeCompare(a.id));
};

export const aggregateCanonicalLifecycleTrades = (
    trades: CanonicalLifecycleTrade[]
): CanonicalLifecycleAggregate => {
    const byProfile: CanonicalLifecycleAggregate['byProfile'] = {};
    let totalPnl = 0;
    let wins = 0;

    for (const trade of trades || []) {
        const pnl = toNumber(trade.pnl);
        totalPnl += pnl;
        if (pnl > 0) wins += 1;

        const profileId = String(trade.profileId || '').trim();
        if (!profileId) continue;
        if (!byProfile[profileId]) {
            byProfile[profileId] = {
                tradeCount: 0,
                wins: 0,
                winRate: 0,
                realizedPnl: 0,
                lastClosedTradeAt: 0
            };
        }

        const aggregate = byProfile[profileId];
        aggregate.tradeCount += 1;
        if (pnl > 0) aggregate.wins += 1;
        aggregate.realizedPnl += pnl;
        aggregate.lastClosedTradeAt = Math.max(aggregate.lastClosedTradeAt, Number(trade.closedAtMs || 0));
    }

    const tradeCount = trades.length;
    for (const aggregate of Object.values(byProfile)) {
        aggregate.winRate = aggregate.tradeCount > 0
            ? (aggregate.wins / aggregate.tradeCount) * 100
            : 0;
    }

    return {
        totalPnl,
        tradeCount,
        wins,
        winRate: tradeCount > 0 ? (wins / tradeCount) * 100 : 0,
        byProfile
    };
};
