// @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';
import { tableNameOrders, tableNameStocks } from '../lib/const';

const {
    authState,
    fromMock,
    getSessionMock,
    entriesEqMock,
    ordersEqMock,
    historyEqMock,
    fetchTradeProfilesMock
} = vi.hoisted(() => ({
    authState: {
        user: { id: 'user-1' } as any,
        profile: { role: 'admin' } as any
    },
    fromMock: vi.fn(),
    getSessionMock: vi.fn(),
    entriesEqMock: vi.fn(),
    ordersEqMock: vi.fn(),
    historyEqMock: vi.fn(),
    fetchTradeProfilesMock: vi.fn()
}));

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

vi.mock('../lib/supabaseClient', () => ({
    supabase: {
        from: fromMock,
        auth: {
            getSession: getSessionMock
        }
    }
}));

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

interface QueryResult {
    data: any;
    error: any;
}

type TableKey = 'entries' | 'orders' | 'trade_history';

const makeBuilder = (result: QueryResult, eqSpy: (field: string, value: any) => void) => {
    const builder: any = {
        select: vi.fn(() => builder),
        order: vi.fn(() => builder),
        limit: vi.fn(() => builder),
        eq: vi.fn((field: string, value: any) => {
            eqSpy(field, value);
            return builder;
        }),
        then: (resolve: any, reject: any) => Promise.resolve(result).then(resolve, reject)
    };
    return builder;
};

const configureQueries = (plan: Record<TableKey, QueryResult[]>) => {
    const queues = {
        entries: [...plan.entries],
        orders: [...plan.orders],
        trade_history: [...plan.trade_history]
    };

    fromMock.mockImplementation((table: string) => {
        if (table === tableNameStocks) {
            return makeBuilder(queues.entries.shift() || { data: [], error: null }, entriesEqMock);
        }
        if (table === tableNameOrders) {
            return makeBuilder(queues.orders.shift() || { data: [], error: null }, ordersEqMock);
        }
        if (table === 'trade_history') {
            return makeBuilder(queues.trade_history.shift() || { data: [], error: null }, historyEqMock);
        }
        return makeBuilder({ data: [], error: null }, vi.fn());
    });
};

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: 24 }).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: 'missing-trade',
                symbol: 'ETH/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 2000,
                currentPrice: 2200,
                stopLoss: 1900,
                takeProfit: 2300,
                unrealizedPnl: 200,
                unrealizedPnlPercent: 10,
                marketValue: 2200,
                profileId: 'p1',
                profileName: 'High Risk Scalper'
            },
            {
                id: 'missing-entry',
                symbol: 'SOL/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 100,
                currentPrice: 101,
                stopLoss: 90,
                takeProfit: 120,
                unrealizedPnl: 1,
                unrealizedPnlPercent: 1,
                marketValue: 101,
                profileId: 'p1',
                profileName: 'High Risk Scalper',
                tradeId: 'TRD-NO-ENTRY'
            },
            {
                id: 'profile-mismatch',
                symbol: 'BTC/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 100,
                currentPrice: 110,
                stopLoss: 90,
                takeProfit: 130,
                unrealizedPnl: 10,
                unrealizedPnlPercent: 10,
                marketValue: 110,
                profileId: 'p1',
                profileName: 'High Risk Scalper',
                tradeId: 'TRD-PROFILE-MISMATCH'
            },
            {
                id: 'symbol-mismatch',
                symbol: 'XRP/USDT',
                side: 'BUY',
                size: 1,
                entryPrice: 1,
                currentPrice: 1.1,
                stopLoss: 0.8,
                takeProfit: 1.2,
                unrealizedPnl: 0.1,
                unrealizedPnlPercent: 10,
                marketValue: 1.1,
                profileId: 'p1',
                profileName: 'High Risk Scalper',
                tradeId: 'TRD-SYMBOL-MISMATCH'
            }
        ] 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' };
        fromMock.mockReset();
        getSessionMock.mockReset();
        entriesEqMock.mockReset();
        ordersEqMock.mockReset();
        historyEqMock.mockReset();
        fetchTradeProfilesMock.mockReset();
        fetchTradeProfilesMock.mockResolvedValue([
            { id: 'p1', name: 'High Risk Scalper' },
            { id: 'p2', name: 'Conservative Bag' }
        ]);

        vi.stubGlobal('confirm', vi.fn(() => true));
        vi.stubGlobal('alert', vi.fn());
        vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
            json: async () => ({ success: true })
        }));
    });

    it('renders positions/orders/lifecycle and supports profile/date/sort/pagination controls', async () => {
        const now = Date.now();
        const dbOrders = [
            {
                id: 'db-merge',
                order_id: 'merge-1',
                profile_id: 'p1',
                symbol: 'BTC/USDT',
                type: 'Market',
                side: 'BUY',
                qty: 1.2,
                price: 140,
                status: 'pending_new',
                timestamp: new Date(now - 20_000).toISOString(),
                created_at: new Date(now - 20_000).toISOString(),
                trade_id: 'TRD-MERGE',
                action: 'ENTRY',
                source: 'BOT'
            },
            {
                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'
            },
            {
                id: 'db-symbol',
                order_id: 'db-symbol',
                profile_id: 'p1',
                symbol: 'DOGE/USDT',
                type: 'Market',
                side: 'BUY',
                qty: 1,
                price: 0.2,
                status: 'filled',
                timestamp: now - 110_000,
                created_at: new Date(now - 110_000).toISOString(),
                trade_id: 'TRD-SYMBOL-MISMATCH',
                action: 'ENTRY',
                source: 'BOT'
            }
        ];

        const entriesRows = [
            {
                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'
            },
            {
                stock_instance_id: 'manual-no-live',
                symbol: 'UNI/USDT',
                quantity: 2,
                buy_price: 10,
                drop_threshold_for_buy: 9,
                gain_threshold_for_sell: 12,
                active: true,
                status: 'active'
            },
            {
                stock_instance_id: 'manual-invalid',
                symbol: 'LINK/USDT',
                quantity: 0,
                buy_price: 20,
                active: true,
                status: 'active'
            }
        ];

        configureQueries({
            entries: [{ data: entriesRows, error: null }],
            orders: [{ data: dbOrders, error: null }],
            trade_history: [{
                data: [{ trade_id: 'TRD-BOT-3', profile_id: 'p2' }, { trade_id: '', profile_id: 'p1' }],
                error: null
            }]
        });

        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(/Lifecycle Mismatch Diagnostics/)).toBeInTheDocument();
        });

        expect(screen.getByText('Position has no trade ID. Lifecycle tracing is degraded.')).toBeInTheDocument();
        expect(screen.getAllByText(/no matching ENTRY order/i).length).toBeGreaterThan(0);
        expect(screen.getByText('Trade cycle tracing active')).toBeInTheDocument();
        expect(screen.queryByText('No active positions for this selection.')).not.toBeInTheDocument();
        expect(screen.getAllByText('Conservative Bag').length).toBeGreaterThan(0);

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

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

        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 dateInputs = container.querySelectorAll('input[type="date"]');
        expect(dateInputs.length).toBeGreaterThanOrEqual(4);
        await user.type(dateInputs[0] as HTMLInputElement, '2026-02-15');
        await user.type(dateInputs[1] as HTMLInputElement, '2026-02-16');
        await user.click(screen.getAllByRole('button', { name: 'Clear' })[0]);
        expect((dateInputs[0] as HTMLInputElement).value).toBe('');
        expect((dateInputs[1] as HTMLInputElement).value).toBe('');
        const firstPassSortButtons = screen
            .getAllByRole('button')
            .filter((button) => /Newest|Oldest/.test(button.textContent || ''));
        await user.click(firstPassSortButtons[0]);
        expect(screen.getAllByRole('button', { name: 'Oldest' }).length).toBeGreaterThan(0);

        await user.type(dateInputs[2] as HTMLInputElement, '2026-02-15');
        await user.type(dateInputs[3] as HTMLInputElement, '2026-02-16');
        await user.click(screen.getAllByRole('button', { name: 'Clear' })[1]);
        expect((dateInputs[2] as HTMLInputElement).value).toBe('');
        expect((dateInputs[3] as HTMLInputElement).value).toBe('');
        const secondPassSortButtons = screen
            .getAllByRole('button')
            .filter((button) => /Newest|Oldest/.test(button.textContent || ''));
        await user.click(secondPassSortButtons[secondPassSortButtons.length - 1]);
    }, 30000);

    it('handles square-off workflow for cancel, auth failure, api failure, and success', async () => {
        const now = Date.now();
        configureQueries({
            entries: [{ data: [], error: null }],
            orders: [{
                data: [{
                    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'
                }],
                error: null
            }],
            trade_history: [{ data: [], error: null }],
        });

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

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

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

        getSessionMock
            .mockResolvedValueOnce({ data: { session: null } })
            .mockResolvedValueOnce({ data: { session: { access_token: 'token-1' } } })
            .mockResolvedValueOnce({ data: { session: { access_token: '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'));
        });
    }, 30000);

    it('applies non-admin scope and triggers legacy fallback/error paths', async () => {
        authState.user = { id: 'user-2' };
        authState.profile = { role: 'trader' };
        const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
        const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);

        configureQueries({
            entries: [{ data: null, error: { message: 'entries failed' } }],
            orders: [
                { data: null, error: { message: 'v2 columns missing' } },
                { data: null, error: { message: 'v2 fallback failed' } },
                { data: null, error: { message: 'legacy failed' } }
            ],
            trade_history: [{ data: null, error: { message: 'history failed' } }]
        });
        fetchTradeProfilesMock.mockRejectedValueOnce(new Error('profiles 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 trade lifecycle traces available for this selection.')).toBeInTheDocument();
        });

        expect(entriesEqMock).toHaveBeenCalledWith('user_id', 'user-2');
        expect(ordersEqMock).toHaveBeenCalledWith('user_id', 'user-2');
        expect(historyEqMock).toHaveBeenCalledWith('user_id', 'user-2');
        expect(fetchTradeProfilesMock).toHaveBeenCalledWith({ scope: 'user' });

        expect(warnSpy).toHaveBeenCalledWith(
            '[PositionsTab] V3 order query failed, falling back to v2 columns:',
            'v2 columns missing'
        );
        expect(warnSpy).toHaveBeenCalledWith(
            '[PositionsTab] V2 order query failed, falling back to legacy columns:',
            'v2 fallback failed'
        );
        expect(warnSpy).toHaveBeenCalledWith(
            '[PositionsTab] History trade lookup failed:',
            'history failed'
        );
        expect(errorSpy).toHaveBeenCalledWith(
            '[PositionsTab] Legacy order query failed:',
            'legacy failed'
        );
        expect(errorSpy).toHaveBeenCalledWith(
            '[PositionsTab] Failed loading manual positions:',
            'entries failed'
        );
        expect(errorSpy).toHaveBeenCalledWith(
            '[PositionsTab] Failed loading profiles:',
            'profiles failed'
        );
    }, 30000);
});
