// @vitest-environment jsdom
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { PositionsTab } from './PositionsTab';
import type { BotState } from '../hooks/useWebSocket';

const {
    authState,
    fetchPositionsBootstrapMock,
    getPlatformAccessTokenMock,
    canonicalLifecycleState
} = vi.hoisted(() => ({
    authState: {
        user: { id: 'user-1' } as any,
        profile: { role: 'admin' } as any,
        session: { access_token: 'session-token' } as any
    },
    fetchPositionsBootstrapMock: vi.fn(),
    getPlatformAccessTokenMock: vi.fn(),
    canonicalLifecycleState: {
        snapshot: {
            lifecycleRows: [{ tradeId: 'TRD-CANONICAL-1' }],
            realizedTrades: [],
            openPositions: [{
                id: 'canonical-open-1',
                profileId: 'p1',
                profileName: 'High Risk Scalper',
                symbol: 'BTC/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 100,
                currentPrice: 125,
                pnl: 25,
                pnlPercent: 25,
                stopLoss: 90,
                takeProfit: 130,
                tradeId: 'TRD-POS-1',
                lastEventAt: 1
            }],
            diagnostics: { truncated: false }
        } as any
    }
}));

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

vi.mock('../lib/positionsApi', () => ({
    fetchPositionsBootstrap: fetchPositionsBootstrapMock
}));

vi.mock('../lib/authSession', () => ({
    getPlatformAccessToken: getPlatformAccessTokenMock
}));

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

const buildBotState = (now: number): BotState => {
    const sharedSymbolMeta = {
        change24h: 0,
        changeToday: 0,
        session: 'NY',
        volatility: 'High',
        signal: 'HOLD',
        activePosition: null,
        priceHistory: [],
        rules: {},
        indicators: {}
    } as any;

    const botOrders = Array.from({ length: 12 }).map((_, idx) => ({
        id: `bot-order-${idx}`,
        order_id: `bot-order-${idx}`,
        profileId: idx % 2 === 0 ? 'p1' : 'p2',
        symbol: idx % 2 === 0 ? 'BTC/USDT' : 'ETH/USDT',
        type: 'Market',
        side: idx % 2 === 0 ? 'BUY' : 'SELL',
        qty: 0.5 + idx * 0.01,
        price: 100 + idx,
        status: idx === 1 ? 'expired' : idx === 2 ? 'unknown' : idx === 3 ? 'pending_new' : 'filled',
        timestamp: now - idx * 30_000,
        trade_id: `TRD-BOT-${idx}`,
        action: idx % 2 === 0 ? 'ENTRY' : 'EXIT',
        source: 'BOT'
    }));

    botOrders.push({
        id: 'merge-1',
        order_id: 'merge-1',
        profileId: 'p1',
        symbol: 'BTC/USDT',
        type: 'Market',
        side: 'BUY',
        qty: 1.2,
        price: 150,
        status: 'filled',
        timestamp: now - 5000,
        trade_id: 'TRD-MERGE',
        action: 'ENTRY',
        source: 'BOT'
    } as any);

    return {
        symbols: {
            'BTC/USDT': { ...sharedSymbolMeta, price: 125, signal: 'BUY' },
            'ETH/USDT': { ...sharedSymbolMeta, price: 2200, signal: 'SELL' },
            'SOL/USDT': { ...sharedSymbolMeta, price: 100, signal: 'HOLD' },
            'XRP/USDT': { ...sharedSymbolMeta, price: 1.1, signal: 'HOLD' },
            'DOGE/USDT': { ...sharedSymbolMeta, price: 0.1, signal: 'HOLD' },
            'UNI/USDT': { ...sharedSymbolMeta, price: 10, signal: 'HOLD' },
            'LINK/USDT': { ...sharedSymbolMeta, price: 20, signal: 'HOLD' }
        } as any,
        alerts: [],
        positions: [
            {
                id: 'bot-1',
                symbol: 'BTC/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 100,
                currentPrice: 125,
                stopLoss: 90,
                takeProfit: 130,
                unrealizedPnl: 25,
                unrealizedPnlPercent: 25,
                marketValue: 125,
                profileId: 'p1',
                profileName: 'High Risk Scalper',
                tradeId: 'TRD-POS-1'
            },
            {
                id: 'bot-1-dupe',
                symbol: 'BTC/USDT',
                side: 'BUY',
                size: 2,
                entryPrice: 100,
                currentPrice: 125,
                stopLoss: 90,
                takeProfit: 130,
                unrealizedPnl: 50,
                unrealizedPnlPercent: 25,
                marketValue: 250,
                profileId: 'p1',
                profileName: 'High Risk Scalper',
                tradeId: 'TRD-POS-1'
            },
            {
                id: 'manual-gap',
                symbol: 'ETH/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 2000,
                currentPrice: 2200,
                stopLoss: 1900,
                takeProfit: 2300,
                unrealizedPnl: 200,
                unrealizedPnlPercent: 10,
                marketValue: 2200,
                profileId: 'p2',
                profileName: 'Conservative Bag'
            }
        ] as any,
        orders: botOrders as any,
        history: [],
        settings: {
            executionMode: 'Paper',
            riskPerTrade: 0.01,
            totalCapital: 15000,
            maxOpenTrades: 6,
            isAlgoEnabled: true,
            enabledRules: ['TrendBiasRule']
        },
        uptime: 1_000_000
    };
};

describe('PositionsTab DOM behavior', () => {
    beforeEach(() => {
        authState.user = { id: 'user-1' };
        authState.profile = { role: 'admin' };
        authState.session = { access_token: 'session-token' };
        fetchPositionsBootstrapMock.mockReset();
        getPlatformAccessTokenMock.mockReset();
        canonicalLifecycleState.snapshot = {
            lifecycleRows: [{ tradeId: 'TRD-CANONICAL-1' }],
            realizedTrades: [],
            openPositions: [{
                id: 'canonical-open-1',
                profileId: 'p1',
                profileName: 'High Risk Scalper',
                symbol: 'BTC/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 100,
                currentPrice: 125,
                pnl: 25,
                pnlPercent: 25,
                stopLoss: 90,
                takeProfit: 130,
                tradeId: 'TRD-POS-1',
                lastEventAt: 1
            }],
            diagnostics: { truncated: false }
        };
        vi.stubGlobal('confirm', vi.fn(() => true));
        vi.stubGlobal('alert', vi.fn());
        vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
            json: async () => ({ success: true })
        }));
    });

    it('renders positions, lifecycle, and controls from the bootstrap api', async () => {
        const now = Date.now();
        fetchPositionsBootstrapMock.mockResolvedValue({
            entries: [
                {
                    stock_instance_id: 'manual-live',
                    symbol: 'BTC/USDT',
                    quantity: 1,
                    buy_price: 100,
                    drop_threshold_for_buy: 95,
                    gain_threshold_for_sell: 115,
                    active: true,
                    status: 'active'
                }
            ],
            orders: [
                {
                    id: 'db-profile',
                    order_id: 'db-profile',
                    profile_id: 'p2',
                    symbol: 'BTC/USDT',
                    type: 'Market',
                    side: 'BUY',
                    qty: 1,
                    price: 2000,
                    status: 'filled',
                    timestamp: now - 120_000,
                    created_at: new Date(now - 120_000).toISOString(),
                    trade_id: 'TRD-PROFILE-MISMATCH',
                    action: 'ENTRY',
                    source: 'BOT'
                }
            ],
            historyTradeKeys: [{ trade_id: 'TRD-BOT-3', profile_id: 'p2' }],
            profiles: [
                { id: 'p1', name: 'High Risk Scalper' },
                { id: 'p2', name: 'Conservative Bag' }
            ]
        });

        const user = userEvent.setup();
        const { container } = render(<PositionsTab botState={buildBotState(now)} />);

        await waitFor(() => {
            expect(screen.getByText('Order Activity')).toBeInTheDocument();
            expect(screen.getByText('Lifecycle Trace')).toBeInTheDocument();
            expect(screen.getByText('Trade cycle tracing active')).toBeInTheDocument();
        });

        expect(fetchPositionsBootstrapMock).toHaveBeenCalledWith({ scope: 'all', limit: 5000 });
        expect(screen.getAllByText('Conservative Bag').length).toBeGreaterThan(0);
        expect(screen.getByText(/Lifecycle Mismatch Diagnostics/)).toBeInTheDocument();
        expect(screen.queryByText('No active positions for this selection.')).not.toBeInTheDocument();

        const selects = container.querySelectorAll('select');
        expect(selects.length).toBeGreaterThanOrEqual(2);
        await user.selectOptions(selects[0] as HTMLSelectElement, 'p1');
        await user.selectOptions(selects[1] as HTMLSelectElement, 'p1');

        const nextButtons = screen.getAllByRole('button', { name: 'Next' });
        const prevButtons = screen.getAllByRole('button', { name: 'Prev' });
        await user.click(nextButtons[0]);
        await user.click(prevButtons[0]);
    });

    it('handles square-off workflow for cancel, auth failure, api failure, and success', async () => {
        const now = Date.now();
        fetchPositionsBootstrapMock.mockResolvedValue({
            entries: [],
            orders: [{
                id: 'entry-order',
                order_id: 'entry-order',
                profile_id: 'p1',
                symbol: 'BTC/USDT',
                type: 'Market',
                side: 'BUY',
                qty: 1,
                price: 100,
                status: 'filled',
                timestamp: now - 1_000,
                created_at: new Date(now - 1_000).toISOString(),
                trade_id: 'TRD-POS-1',
                action: 'ENTRY',
                source: 'BOT'
            }],
            historyTradeKeys: [],
            profiles: [{ id: 'p1', name: 'High Risk Scalper' }]
        });

        const user = userEvent.setup();
        render(<PositionsTab botState={buildBotState(now)} />);

        await user.click(await screen.findByRole('button', { name: 'High Risk Scalper' }));

        await waitFor(() => {
            expect(screen.getAllByRole('button', { name: 'Square Off' }).length).toBeGreaterThan(0);
        });

        vi.mocked(confirm)
            .mockReturnValueOnce(false)
            .mockReturnValueOnce(true)
            .mockReturnValueOnce(true)
            .mockReturnValueOnce(true);

        getPlatformAccessTokenMock
            .mockRejectedValueOnce(new Error('Not authenticated'))
            .mockResolvedValueOnce('token-1')
            .mockResolvedValueOnce('token-2');

        vi.mocked(fetch)
            .mockResolvedValueOnce({ json: async () => ({ success: false, error: 'broker rejected' }) } as any)
            .mockResolvedValueOnce({ json: async () => ({ success: true }) } as any);

        const button = screen.getAllByRole('button', { name: 'Square Off' })[0];
        await user.click(button);
        expect(fetch).not.toHaveBeenCalled();

        await user.click(button);
        await waitFor(() => {
            expect(alert).toHaveBeenCalledWith(expect.stringContaining('Error: Not authenticated'));
        });

        await user.click(button);
        await waitFor(() => {
            expect(alert).toHaveBeenCalledWith(expect.stringContaining('Failed to close: broker rejected'));
        });

        await user.click(button);
        await waitFor(() => {
            expect(alert).toHaveBeenCalledWith(expect.stringContaining('Successfully closed'));
        });
    });

    it('handles non-admin bootstrap failures with empty-state fallback', async () => {
        authState.user = { id: 'user-2' };
        authState.profile = { role: 'trader' };
        canonicalLifecycleState.snapshot = null;
        const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
        fetchPositionsBootstrapMock.mockRejectedValueOnce(new Error('bootstrap failed'));

        render(<PositionsTab botState={{
            symbols: {},
            alerts: [],
            positions: [],
            orders: [],
            history: [],
            settings: {
                executionMode: 'Paper',
                riskPerTrade: 0.01,
                totalCapital: 1000,
                maxOpenTrades: 3,
                isAlgoEnabled: true,
                enabledRules: []
            },
            uptime: 1000
        }} />);

        await waitFor(() => {
            expect(screen.getByText('No recent orders for this cluster.')).toBeInTheDocument();
            expect(screen.getByText('No active positions for this selection.')).toBeInTheDocument();
        });

        expect(fetchPositionsBootstrapMock).toHaveBeenCalledWith({ scope: 'user', limit: 5000 });
        expect(errorSpy).toHaveBeenCalledWith('[PositionsTab] Failed loading positions bootstrap:', 'bootstrap failed');
    });

    it('does not render fake market price or breach badges for manual entries without live pricing', async () => {
        canonicalLifecycleState.snapshot = null;
        fetchPositionsBootstrapMock.mockResolvedValue({
            entries: [
                {
                    stock_instance_id: 'manual-live',
                    symbol: 'BTC/USDT',
                    quantity: 1,
                    buy_price: 100,
                    drop_threshold_for_buy: 95,
                    gain_threshold_for_sell: 115,
                    active: true,
                    status: 'active'
                }
            ],
            orders: [],
            historyTradeKeys: [],
            profiles: []
        });

        render(<PositionsTab botState={{
            symbols: {},
            alerts: [],
            positions: [],
            orders: [],
            history: [],
            settings: {
                executionMode: 'Paper',
                riskPerTrade: 0.01,
                totalCapital: 1000,
                maxOpenTrades: 3,
                isAlgoEnabled: true,
                enabledRules: []
            },
            uptime: 1000
        }} />);

        await waitFor(() => {
            expect(screen.getByText('BTC/USDT')).toBeInTheDocument();
        });

        expect(screen.queryByText('SL breached')).not.toBeInTheDocument();
        expect(screen.queryByText('$0')).not.toBeInTheDocument();
    });

    it('does not refetch bootstrap just because websocket order/history counts change', async () => {
        const now = Date.now();
        fetchPositionsBootstrapMock.mockResolvedValue({
            entries: [],
            orders: [],
            historyTradeKeys: [],
            profiles: []
        });

        const { rerender } = render(<PositionsTab botState={buildBotState(now)} />);

        await waitFor(() => {
            expect(fetchPositionsBootstrapMock).toHaveBeenCalledTimes(1);
        });

        const nextBotState = buildBotState(now);
        nextBotState.orders = [...nextBotState.orders, {
            id: 'new-order',
            order_id: 'new-order',
            profileId: 'p1',
            symbol: 'BTC/USDT',
            type: 'Market',
            side: 'BUY',
            qty: 1,
            price: 101,
            status: 'filled',
            timestamp: now + 1_000,
            trade_id: 'TRD-NEW',
            action: 'ENTRY',
            source: 'BOT'
        } as any];
        nextBotState.history = [{ id: 'history-1' } as any];

        rerender(<PositionsTab botState={nextBotState} />);

        await waitFor(() => {
            expect(fetchPositionsBootstrapMock).toHaveBeenCalledTimes(1);
        });
    });
});
