export type HistorySide = 'BUY' | 'SELL' | 'UNKNOWN';
export type HistorySource = 'BOT' | 'MANUAL';
export type LedgerOrigin = 'db' | 'realtime';

export interface LedgerProfileAggregate {
    tradeCount: number;
    wins: number;
    winRate: number;
    realizedPnl: number;
    lastClosedTradeAt: number;
}

export interface HistoryLedgerAggregate {
    totalPnl: number;
    tradeCount: number;
    wins: number;
    winRate: number;
    byProfile: Record<string, LedgerProfileAggregate>;
}

export interface NormalizedLedgerTrade {
    id: string;
    symbol: string;
    side: HistorySide;
    size: number;
    entryPrice: number;
    exitPrice: number;
    pnl: number;
    pnlPercent: number;
    reason: string;
    profileId?: string;
    tradeId?: string;
    source: HistorySource;
    timestampMs: number;
    timestampRaw?: number | string;
    createdAt?: string;
    origin: LedgerOrigin;
}

interface BuildLedgerOptions {
    dbRows?: any[];
    realtimeRows?: any[];
    includeRealtime?: boolean;
    realtimeWindowMs?: number;
    nowMs?: number;
}

const DEFAULT_REALTIME_WINDOW_MS = 6 * 60 * 60 * 1000;

export const normalizeEpochMs = (value: number): number => {
    if (!Number.isFinite(value) || value <= 0) return 0;
    return value < 1_000_000_000_000 ? value * 1000 : value;
};

export const toEpochMs = (timestamp?: number | string | null, createdAt?: string | null): number => {
    if (typeof timestamp === 'number') return normalizeEpochMs(timestamp);
    if (typeof timestamp === 'string') {
        const trimmed = timestamp.trim();
        if (/^\d+(\.\d+)?$/.test(trimmed)) {
            return normalizeEpochMs(Number(trimmed));
        }
        const parsed = Date.parse(trimmed);
        if (Number.isFinite(parsed) && parsed > 0) return parsed;
    }
    if (typeof createdAt === 'string') {
        const parsed = Date.parse(createdAt);
        if (Number.isFinite(parsed) && parsed > 0) return parsed;
    }
    return 0;
};

export const normalizeSide = (side?: string | null): HistorySide => {
    const value = String(side || '').toUpperCase();
    if (value === 'SELL' || value === 'SHORT') return 'SELL';
    if (value === 'BUY' || value === 'LONG') return 'BUY';
    return 'UNKNOWN';
};

export const normalizeSource = (source?: string | null, profileId?: string | null): HistorySource => {
    const value = String(source || '').toUpperCase();
    if (value === 'BOT' || value === 'MANUAL') return value;
    return String(profileId || '').trim() ? 'BOT' : 'MANUAL';
};

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

const toFixedNumber = (value: unknown, decimals: number): string => toNumber(value).toFixed(decimals);
const normalizeReasonToken = (value: string): string =>
    value
        .toLowerCase()
        .replace(/\s+/g, ' ')
        .trim();
const isPartialExitReason = (reason: string): boolean => normalizeReasonToken(reason).includes('partial exit');

const normalizeRow = (
    raw: any,
    index: number,
    origin: LedgerOrigin
): NormalizedLedgerTrade => {
    const profileId = String(raw?.profile_id ?? raw?.profileId ?? '').trim() || undefined;
    const tradeId = String(raw?.trade_id ?? raw?.tradeId ?? '').trim() || undefined;
    const timestampRaw = raw?.timestamp ?? raw?.created_at ?? raw?.createdAt;
    const timestampMs = toEpochMs(raw?.timestamp ?? raw?.created_at ?? raw?.createdAt, raw?.created_at ?? raw?.createdAt);
    const id = String(raw?.id ?? `${origin}-${tradeId || 'NO_TRADE_ID'}-${timestampMs || Date.now()}-${index}`);

    return {
        id,
        symbol: String(raw?.symbol || ''),
        side: normalizeSide(raw?.side),
        size: toNumber(raw?.size ?? raw?.qty),
        entryPrice: toNumber(raw?.entry_price ?? raw?.entryPrice),
        exitPrice: toNumber(raw?.exit_price ?? raw?.exitPrice),
        pnl: toNumber(raw?.pnl),
        pnlPercent: toNumber(raw?.pnl_percent ?? raw?.pnlPercent),
        reason: String(raw?.reason || 'N/A'),
        profileId,
        tradeId,
        source: normalizeSource(raw?.source, profileId),
        timestampMs,
        timestampRaw,
        createdAt: raw?.created_at ?? raw?.createdAt,
        origin
    };
};

export const buildHistoryEventKey = (trade: NormalizedLedgerTrade): string => {
    const hasTradeId = String(trade.tradeId || '').trim().length > 0;
    const tradeId = String(trade.tradeId || 'NO_TRADE_ID');
    const profileId = String(trade.profileId || 'global');
    const symbol = String(trade.symbol || 'UNKNOWN_SYMBOL');
    const reason = normalizeReasonToken(String(trade.reason || ''));
    const entry = toFixedNumber(trade.entryPrice, 8);
    const exit = toFixedNumber(trade.exitPrice, 8);
    const size = toFixedNumber(trade.size, 8);
    const pnl = toFixedNumber(trade.pnl, 8);
    if (hasTradeId) {
        return `trade:${profileId}:${tradeId}:${symbol}:${trade.side}:${entry}:${exit}:${size}:${pnl}:${reason}`;
    }
    return `event:${profileId}:${tradeId}:${symbol}:${trade.side}:${trade.timestampMs}:${entry}:${exit}:${size}:${pnl}:${reason}`;
};

export const dedupeHistoryLedger = (rows: NormalizedLedgerTrade[]): NormalizedLedgerTrade[] => {
    const byKey = new Map<string, NormalizedLedgerTrade>();
    for (const row of rows) {
        const key = buildHistoryEventKey(row);
        const existing = byKey.get(key);
        if (!existing) {
            byKey.set(key, row);
            continue;
        }

        const existingRank = existing.origin === 'db' ? 2 : 1;
        const incomingRank = row.origin === 'db' ? 2 : 1;
        if (incomingRank > existingRank) {
            byKey.set(key, row);
            continue;
        }
        if (incomingRank === existingRank && row.timestampMs >= existing.timestampMs) {
            byKey.set(key, row);
        }
    }
    return Array.from(byKey.values());
};

export const buildHistoryLedger = ({
    dbRows = [],
    realtimeRows = [],
    includeRealtime = false,
    realtimeWindowMs = DEFAULT_REALTIME_WINDOW_MS,
    nowMs = Date.now()
}: BuildLedgerOptions): NormalizedLedgerTrade[] => {
    const normalizedDb = (dbRows || []).map((row, index) => normalizeRow(row, index, 'db'));
    let normalizedRealtime: NormalizedLedgerTrade[] = [];
    if (includeRealtime) {
        normalizedRealtime = (realtimeRows || [])
            .map((row, index) => normalizeRow(row, index, 'realtime'))
            .filter((row) => row.timestampMs > 0 && row.timestampMs >= (nowMs - realtimeWindowMs));
    }

    const deduped = dedupeHistoryLedger([...normalizedRealtime, ...normalizedDb]);
    return deduped.sort((a, b) => (b.timestampMs - a.timestampMs) || b.id.localeCompare(a.id));
};

export const aggregateHistoryLedger = (rows: NormalizedLedgerTrade[]): HistoryLedgerAggregate => {
    const byProfile: Record<string, LedgerProfileAggregate> = {};
    const lifecycleByKey = new Map<string, {
        profileId?: string;
        tradeId?: string;
        pnl: number;
        lastTimestampMs: number;
        hasNonPartialExit: boolean;
    }>();

    for (const row of rows) {
        const pnl = toNumber(row.pnl);
        const profileId = String(row.profileId || '').trim() || undefined;
        const tradeId = String(row.tradeId || '').trim() || undefined;
        const lifecycleKey = tradeId
            ? `trade:${profileId || 'global'}:${tradeId}`
            : `row:${row.id}`;
        const existing = lifecycleByKey.get(lifecycleKey);
        const reason = String(row.reason || '');
        const normalizedReason = normalizeReasonToken(reason);

        if (!existing) {
            lifecycleByKey.set(lifecycleKey, {
                profileId,
                tradeId,
                pnl,
                lastTimestampMs: Number(row.timestampMs || 0),
                hasNonPartialExit: !isPartialExitReason(normalizedReason)
            });
            continue;
        }

        existing.pnl += pnl;
        existing.lastTimestampMs = Math.max(existing.lastTimestampMs, Number(row.timestampMs || 0));
        existing.hasNonPartialExit = existing.hasNonPartialExit || !isPartialExitReason(normalizedReason);
        if (!existing.profileId && profileId) existing.profileId = profileId;
        if (!existing.tradeId && tradeId) existing.tradeId = tradeId;
    }

    let totalPnl = 0;
    let wins = 0;
    for (const lifecycle of lifecycleByKey.values()) {
        totalPnl += lifecycle.pnl;
        if (lifecycle.pnl > 0) wins += 1;

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

        const profileAgg = byProfile[profileId];
        profileAgg.tradeCount += 1;
        if (lifecycle.pnl > 0) profileAgg.wins += 1;
        profileAgg.realizedPnl += lifecycle.pnl;

        if (lifecycle.hasNonPartialExit && lifecycle.lastTimestampMs > profileAgg.lastClosedTradeAt) {
            profileAgg.lastClosedTradeAt = lifecycle.lastTimestampMs;
        }
    }

    const tradeCount = lifecycleByKey.size;
    Object.values(byProfile).forEach((agg) => {
        agg.winRate = agg.tradeCount > 0 ? (agg.wins / agg.tradeCount) * 100 : 0;
    });

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