import { describe, expect, it } from 'vitest';
import {
    aggregateHistoryLedger,
    buildHistoryLedger,
    dedupeHistoryLedger,
    normalizeEpochMs,
    normalizeSide,
    normalizeSource,
    toEpochMs,
    type NormalizedLedgerTrade
} from './tradeHistoryLedger';

describe('tradeHistoryLedger', () => {
    it('normalizes epoch values in seconds and milliseconds', () => {
        expect(normalizeEpochMs(1_700_000_000)).toBe(1_700_000_000_000);
        expect(normalizeEpochMs(1_700_000_000_000)).toBe(1_700_000_000_000);
        expect(normalizeEpochMs(-1)).toBe(0);
    });

    it('parses timestamps from numeric and ISO inputs', () => {
        expect(toEpochMs('1700000000')).toBe(1_700_000_000_000);
        expect(toEpochMs('2026-02-15T00:00:00.000Z')).toBe(1_771_113_600_000);
        expect(toEpochMs(undefined, '2026-02-15T00:00:00.000Z')).toBe(1_771_113_600_000);
        expect(toEpochMs('not-a-date', 'still-not-a-date')).toBe(0);
    });

    it('normalizes side and source values', () => {
        expect(normalizeSide('long')).toBe('BUY');
        expect(normalizeSide('short')).toBe('SELL');
        expect(normalizeSide('')).toBe('UNKNOWN');

        expect(normalizeSource('BOT', '')).toBe('BOT');
        expect(normalizeSource(undefined, 'profile-1')).toBe('BOT');
        expect(normalizeSource(undefined, '')).toBe('MANUAL');
        expect(normalizeSource('manual', 'profile-1')).toBe('MANUAL');
    });

    it('dedupes realtime and db rows with db precedence', () => {
        const nowMs = 1_700_000_000_500;
        const dbRows = [{
            id: 'db-1',
            trade_id: 'TRD-1',
            profile_id: 'profile-1',
            symbol: 'BTC/USDT',
            side: 'BUY',
            size: 0.1,
            entry_price: 100,
            exit_price: 110,
            pnl: 1,
            pnl_percent: 1,
            reason: 'target hit',
            timestamp: 1_700_000_000
        }];

        const realtimeRows = [{
            id: 'rt-1',
            trade_id: 'TRD-1',
            profile_id: 'profile-1',
            symbol: 'BTC/USDT',
            side: 'BUY',
            size: 0.1,
            entry_price: 100,
            exit_price: 110,
            pnl: 1,
            pnl_percent: 1,
            reason: 'target hit',
            timestamp: 1_700_000_000
        }];

        const ledger = buildHistoryLedger({
            dbRows,
            realtimeRows,
            includeRealtime: true,
            nowMs,
            realtimeWindowMs: 60_000
        });

        expect(ledger).toHaveLength(1);
        expect(ledger[0].id).toBe('db-1');
        expect(ledger[0].origin).toBe('db');
    });

    it('keeps latest entry when two db events share the same lifecycle key', () => {
        const rows = [
            {
                id: 'db-old',
                trade_id: 'TRD-TIE',
                profile_id: 'profile-1',
                symbol: 'ETH/USDT',
                side: 'SELL',
                size: 1,
                entry_price: 10,
                exit_price: 9,
                pnl: -1,
                pnl_percent: -10,
                reason: 'stop',
                timestamp: 1_700_000_000
            },
            {
                id: 'db-new',
                trade_id: 'TRD-TIE',
                profile_id: 'profile-1',
                symbol: 'ETH/USDT',
                side: 'SELL',
                size: 1,
                entry_price: 10,
                exit_price: 9,
                pnl: -1,
                pnl_percent: -10,
                reason: 'stop',
                timestamp: 1_700_000_001
            }
        ];

        const ledger = buildHistoryLedger({
            dbRows: rows,
            includeRealtime: false
        });

        expect(ledger).toHaveLength(1);
        expect(ledger[0].id).toBe('db-new');
    });

    it('filters realtime rows outside the configured window', () => {
        const nowMs = 1_700_000_000_000;
        const ledger = buildHistoryLedger({
            dbRows: [],
            realtimeRows: [
                {
                    id: 'rt-old',
                    symbol: 'BTC/USDT',
                    side: 'BUY',
                    qty: 1,
                    entry_price: 10,
                    exit_price: 11,
                    pnl: 1,
                    pnl_percent: 10,
                    reason: 'target',
                    timestamp: 1_699_000_000_000
                },
                {
                    id: 'rt-new',
                    symbol: 'BTC/USDT',
                    side: 'BUY',
                    qty: 1,
                    entry_price: 10,
                    exit_price: 11,
                    pnl: 1,
                    pnl_percent: 10,
                    reason: 'target',
                    timestamp: 1_700_000_000_000
                }
            ],
            includeRealtime: true,
            realtimeWindowMs: 60_000,
            nowMs
        });

        expect(ledger).toHaveLength(1);
        expect(ledger[0].id).toBe('rt-new');
    });

    it('creates fallback id and default values for sparse rows', () => {
        const ledger = buildHistoryLedger({
            dbRows: [
                {
                    symbol: '',
                    side: '',
                    reason: '',
                    qty: 'bad-number',
                    entryPrice: 'bad',
                    exitPrice: 'bad',
                    pnl: 'bad',
                    pnlPercent: 'bad'
                }
            ],
            includeRealtime: false
        });

        expect(ledger).toHaveLength(1);
        expect(ledger[0].id.startsWith('db-NO_TRADE_ID-')).toBe(true);
        expect(ledger[0].source).toBe('MANUAL');
        expect(ledger[0].size).toBe(0);
        expect(ledger[0].entryPrice).toBe(0);
        expect(ledger[0].exitPrice).toBe(0);
        expect(ledger[0].pnl).toBe(0);
        expect(ledger[0].pnlPercent).toBe(0);
    });

    it('dedupe handles rank precedence and NaN timestamp edge path', () => {
        const rows: NormalizedLedgerTrade[] = [
            {
                id: 'rt',
                symbol: 'BTC/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 1,
                exitPrice: 2,
                pnl: 1,
                pnlPercent: 100,
                reason: 'same',
                profileId: 'profile-1',
                tradeId: 'TRD-EDGE',
                source: 'BOT',
                timestampMs: 1000,
                origin: 'realtime'
            },
            {
                id: 'db',
                symbol: 'BTC/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 1,
                exitPrice: 2,
                pnl: 1,
                pnlPercent: 100,
                reason: 'same',
                profileId: 'profile-1',
                tradeId: 'TRD-EDGE',
                source: 'BOT',
                timestampMs: 1000,
                origin: 'db'
            },
            {
                id: 'nan-1',
                symbol: 'SOL/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 1,
                exitPrice: 1.1,
                pnl: 0.1,
                pnlPercent: 10,
                reason: 'nan',
                profileId: 'profile-2',
                tradeId: 'TRD-NAN',
                source: 'BOT',
                timestampMs: Number.NaN,
                origin: 'db'
            },
            {
                id: 'nan-2',
                symbol: 'SOL/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 1,
                exitPrice: 1.1,
                pnl: 0.1,
                pnlPercent: 10,
                reason: 'nan',
                profileId: 'profile-2',
                tradeId: 'TRD-NAN',
                source: 'BOT',
                timestampMs: Number.NaN,
                origin: 'db'
            }
        ];

        const deduped = dedupeHistoryLedger(rows);
        expect(deduped).toHaveLength(2);
        expect(deduped.find((row) => row.tradeId === 'TRD-EDGE')?.id).toBe('db');
        expect(deduped.find((row) => row.tradeId === 'TRD-NAN')?.id).toBe('nan-1');
    });

    it('aggregates pnl, wins, and profile-level last-closed timestamps by lifecycle', () => {
        const rows: NormalizedLedgerTrade[] = [
            {
                id: '1',
                symbol: 'BTC/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 100,
                exitPrice: 110,
                pnl: 10,
                pnlPercent: 10,
                reason: 'target hit',
                profileId: 'profile-1',
                tradeId: 'TRD-1',
                source: 'BOT',
                timestampMs: 1000,
                origin: 'db'
            },
            {
                id: '2',
                symbol: 'BTC/USDT',
                side: 'SELL',
                size: 0.5,
                entryPrice: 110,
                exitPrice: 108,
                pnl: -5,
                pnlPercent: -4.5,
                reason: 'partial exit',
                profileId: 'profile-1',
                tradeId: 'TRD-1',
                source: 'BOT',
                timestampMs: 2000,
                origin: 'db'
            },
            {
                id: '3',
                symbol: 'SOL/USDT',
                side: 'BUY',
                size: 0.2,
                entryPrice: 100,
                exitPrice: 99,
                pnl: -1,
                pnlPercent: -1,
                reason: 'stop',
                source: 'MANUAL',
                timestampMs: 3000,
                origin: 'realtime'
            }
        ];

        const agg = aggregateHistoryLedger(rows);
        expect(agg.totalPnl).toBe(4);
        expect(agg.tradeCount).toBe(2);
        expect(agg.wins).toBe(1);
        expect(agg.winRate).toBe(50);
        expect(agg.byProfile['profile-1'].realizedPnl).toBe(5);
        expect(agg.byProfile['profile-1'].tradeCount).toBe(1);
        expect(agg.byProfile['profile-1'].lastClosedTradeAt).toBe(2000);
    });

    it('collapses duplicate trade rows with different timestamps under the same trade lifecycle', () => {
        const rows: NormalizedLedgerTrade[] = [
            {
                id: 'dup-1',
                symbol: 'BTC/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 100,
                exitPrice: 105,
                pnl: 5,
                pnlPercent: 5,
                reason: 'Order Sync Exit Fill',
                profileId: 'profile-dup',
                tradeId: 'TRD-DUP-1',
                source: 'BOT',
                timestampMs: 1000,
                origin: 'db'
            },
            {
                id: 'dup-2',
                symbol: 'BTC/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 100,
                exitPrice: 105,
                pnl: 5,
                pnlPercent: 5,
                reason: 'order   sync exit fill',
                profileId: 'profile-dup',
                tradeId: 'TRD-DUP-1',
                source: 'BOT',
                timestampMs: 1200,
                origin: 'db'
            }
        ];

        const deduped = dedupeHistoryLedger(rows);
        expect(deduped).toHaveLength(1);
        expect(deduped[0].id).toBe('dup-2');

        const agg = aggregateHistoryLedger(deduped);
        expect(agg.tradeCount).toBe(1);
        expect(agg.totalPnl).toBe(5);
        expect(agg.byProfile['profile-dup'].tradeCount).toBe(1);
        expect(agg.byProfile['profile-dup'].realizedPnl).toBe(5);
    });
});
