// @vitest-environment jsdom
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import { OverviewTab, dedupeLivePositions } from './OverviewTab';
import { DEFAULT_BOT_STATE } from '../hooks/useWebSocket';

const { authMock, fetchTradeProfilesMock, canonicalLifecycleHookState } = vi.hoisted(() => {
    return {
        authMock: { user: { id: 'u1' }, profile: { role: 'user' } },
        fetchTradeProfilesMock: vi.fn(),
        canonicalLifecycleHookState: {
            snapshot: null as any,
            loading: false,
            error: null as string | null
        }
    };
});

vi.mock('../components/AuthContext', () => ({
    useAuth: () => ({
        ...authMock,
        refreshProfile: vi.fn()
    })
}));

vi.mock('../lib/profileApi', () => ({
    fetchTradeProfiles: fetchTradeProfilesMock
}));

vi.mock('../hooks/useCanonicalLifecycle', () => ({
    useCanonicalLifecycle: () => ({
        snapshot: canonicalLifecycleHookState.snapshot,
        loading: canonicalLifecycleHookState.loading,
        error: canonicalLifecycleHookState.error
    })
}));

describe('OverviewTab coverage maximization', () => {
    let mockBotState: any;

    beforeEach(() => {
        vi.clearAllMocks();
        authMock.profile.role = 'user';
        canonicalLifecycleHookState.snapshot = null;
        canonicalLifecycleHookState.loading = false;
        canonicalLifecycleHookState.error = null;
        mockBotState = JSON.parse(JSON.stringify(DEFAULT_BOT_STATE));
        mockBotState.symbols = {
            'BTC/USDT': {
                signal: 'BUY',
                volatility: 'LOW',
                session: 'US',
                indicators: { rsi_1h: 60, ema50_4h: 50000, ema200_4h: 40000 },
                profileSignals: { 'p1': { signal: 'BUY', passed: true } }
            }
        };
        fetchTradeProfilesMock.mockResolvedValue([
            { id: 'p1', name: 'Alpha', is_active: true, allocated_capital: 5000, strategy_config: { execution: { cooldownMinutes: 5 } } }
        ]);
    });

    it('covers all uptime and duration formatting paths', async () => {
        mockBotState.uptime = 45000;
        const { unmount } = render(<OverviewTab botState={mockBotState} />);
        await waitFor(() => expect(screen.getByText('45s')).toBeInTheDocument());
        unmount();

        mockBotState.uptime = 3600000 * 25 + 60000;
        render(<OverviewTab botState={mockBotState} />);
        await waitFor(() => expect(screen.getByText('25h 1m')).toBeInTheDocument());
    });

    it('covers Capital Sync Warning branch', async () => {
        mockBotState.positions = [
            { symbol: 'BTC/USDT', side: 'BUY', entryPrice: 60000, size: 0.1, profileId: 'p1' }
        ];
        render(<OverviewTab botState={mockBotState} />);
        await waitFor(() => expect(screen.getByText(/Capital sync warning/i)).toBeInTheDocument());
    });

    it('covers fallback capital logic', async () => {
        fetchTradeProfilesMock.mockResolvedValue([]);
        mockBotState.settings.totalCapital = 25000;
        render(<OverviewTab botState={mockBotState} />);
        // The label in bot-status-bar is "Allocated:"
        await waitFor(() => expect(screen.getByText(/^Allocated:$/i).parentElement).toHaveTextContent(/\$25,?000\.00/));
    });

    it('covers Signal Active branches', async () => {
        fetchTradeProfilesMock.mockResolvedValueOnce([
            { id: 'p1', name: 'Alpha', is_active: true, allocated_capital: 0, strategy_config: { execution: { cooldownMinutes: 5 } } }
        ]);
        const { unmount } = render(<OverviewTab botState={mockBotState} />);
        await waitFor(() => expect(screen.getByText(/Signal active, no allocation/i)).toBeInTheDocument());
        unmount();

        fetchTradeProfilesMock.mockResolvedValueOnce([
            { id: 'p1', name: 'Alpha', is_active: true, allocated_capital: 5000, strategy_config: { execution: { cooldownMinutes: 5 } } }
        ]);
        render(<OverviewTab botState={mockBotState} />);
        await waitFor(() => expect(screen.getByText(/Signal active, waiting entry/i)).toBeInTheDocument());
    });

    it('shows blocked state when directional signal is blocked by execution guard', async () => {
        mockBotState.symbols['BTC/USDT'].profileSignals = {
            p1: {
                signal: 'BUY',
                passed: true,
                execution: {
                    status: 'BLOCKED',
                    code: 'trading_paused',
                    reason: 'Trading is paused by control plane.'
                }
            }
        };
        render(<OverviewTab botState={mockBotState} />);
        await waitFor(() => expect(screen.getByText(/Signal blocked by guard/i)).toBeInTheDocument());
    });

    it('falls back to live profile positions when canonical lifecycle is unavailable', async () => {
        mockBotState.positions = [
            {
                id: 'pos-live-1',
                symbol: 'BTC/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 1000,
                currentPrice: 1010,
                stopLoss: 950,
                takeProfit: 1100,
                unrealizedPnl: 10,
                unrealizedPnlPercent: 1,
                marketValue: 1010,
                profileId: 'p1',
                profileName: 'Alpha',
                tradeId: 'TRD-LIVE-1'
            }
        ];
        render(<OverviewTab botState={mockBotState} />);
        await waitFor(() => expect(screen.getByText(/In Position \(1 open\)/i)).toBeInTheDocument());
    });

    it('prefers live-flat utilization when canonical lifecycle has stale open notional', async () => {
        canonicalLifecycleHookState.snapshot = {
            generatedAt: Date.now(),
            diagnostics: {
                orderRows: 1,
                lifecycleRows: 1,
                openPositions: 1,
                realizedTrades: 0,
                truncated: false
            },
            profiles: [{
                id: 'p1',
                name: 'Alpha',
                allocatedCapital: 5000,
                isActive: true
            }],
            lifecycleRows: [],
            openPositions: [],
            realizedTrades: [],
            aggregates: {
                total: {
                    openTrades: 1,
                    openNotional: 20205.03,
                    realizedPnl: 0,
                    unrealizedPnl: 0,
                    netPnl: 0,
                    tradeCount: 0,
                    wins: 0,
                    winRate: 0
                },
                byProfile: {
                    p1: {
                        profileId: 'p1',
                        profileName: 'Alpha',
                        allocatedCapital: 5000,
                        isActive: true,
                        openTrades: 1,
                        openNotional: 20205.03,
                        realizedPnl: 0,
                        unrealizedPnl: 0,
                        netPnl: 0,
                        tradeCount: 0,
                        wins: 0,
                        winRate: 0,
                        lastClosedTradeAt: 0
                    }
                }
            }
        };
        mockBotState.positions = [];

        render(<OverviewTab botState={mockBotState} />);
        await waitFor(() => expect(screen.getByText(/Lifecycle sync pending/i)).toBeInTheDocument());
        expect(screen.queryByText(/Capital sync warning/i)).not.toBeInTheDocument();
    });

    it('covers Admin role data fetching', async () => {
        authMock.profile.role = 'admin';
        render(<OverviewTab botState={mockBotState} />);
        await waitFor(() => expect(fetchTradeProfilesMock).toHaveBeenCalledWith({ scope: 'all' }));
    });

    it('covers error handling in fetchData', async () => {
        const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
        fetchTradeProfilesMock.mockRejectedValueOnce(new Error('Query Fail'));
        render(<OverviewTab botState={mockBotState} />);
        await waitFor(() => expect(consoleSpy).toHaveBeenCalled());
        consoleSpy.mockRestore();
    });

    it('covers Mixed signal label rendering for customer', async () => {
        mockBotState.symbols['BTC/USDT'].signal = 'MIXED';
        render(<OverviewTab botState={mockBotState} />);
        await waitFor(() => expect(screen.getByText(/Conditions mixed — holding off/i)).toBeInTheDocument());
    });

    it('covers Mixed signal label rendering for admin', async () => {
        authMock.profile.role = 'admin';
        mockBotState.symbols['BTC/USDT'].signal = 'MIXED';
        render(<OverviewTab botState={mockBotState} />);
        await waitFor(() => expect(screen.getByText(/PROFILE SIGNALS MIXED/i)).toBeInTheDocument());
    });
});

describe('dedupeLivePositions logic', () => {
    it('merges positions with identical tradeId', () => {
        const p1 = { symbol: 'BTC', side: 'BUY', tradeId: 'T1', profileId: 'P1', entryPrice: 1, size: 1 } as any;
        const p2 = { symbol: 'BTC', side: 'BUY', tradeId: 'T1', profileId: 'P1', entryPrice: 1, size: 2 } as any;
        const result = dedupeLivePositions([p1, p2]);
        expect(result).toHaveLength(1);
        expect(result[0].size).toBe(2);
    });

    it('merges positions using fallback key', () => {
        const p1 = { symbol: 'BTC', side: 'BUY', tradeId: '', profileId: 'P1', entryPrice: 1, size: 1 } as any;
        const p2 = { symbol: 'BTC', side: 'BUY', tradeId: '', profileId: 'P1', entryPrice: 1, size: 2 } as any;
        const result = dedupeLivePositions([p1, p2]);
        expect(result).toHaveLength(1);
        expect(result[0].size).toBe(2);
    });

    it('handles null/undefined gracefully', () => {
        expect(dedupeLivePositions(undefined)).toEqual([]);
        expect(dedupeLivePositions(null as any)).toEqual([]);
    });
});
