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

const { getPlatformAccessTokenMock, writeTextMock } = vi.hoisted(() => ({
    getPlatformAccessTokenMock: vi.fn(),
    writeTextMock: vi.fn()
}));

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

const profilesFixture = [
    { id: 'p1', name: 'High Risk Scalper ⚡', allocated_capital: 1000, risk_per_trade_percent: 1.5, symbols: 'BTC/USDT' },
    { id: 'p2', name: 'Conservative Bag', allocated_capital: 2000, risk_per_trade_percent: 0.8, symbols: 'ETH/USDT' }
];

const botStateFixture: BotState = {
    ...DEFAULT_BOT_STATE,
    symbols: {
        'BTC/USDT': {
            price: 61500,
            change24h: 2.1,
            changeToday: 1.2,
            session: 'London',
            volatility: 'High',
            signal: 'BUY',
            priceHistory: [],
            rules: {},
            profileSignals: {
                p1: {
                    profileName: 'High Risk Scalper ⚡',
                    signal: 'BUY',
                    passed: false,
                    reason: 'Waiting for stronger rule alignment.',
                    execution: {
                        status: 'SKIPPED',
                        code: 'rule_ratio_not_met',
                        reason: 'Only 2 of 4 voting rules passed.',
                    }
                }
            },
            indicators: {},
        }
    },
    positions: [
        {
            id: 'pos-1',
            symbol: 'BTC/USDT',
            side: 'BUY' as const,
            size: 0.25,
            entryPrice: 60000,
            currentPrice: 61500,
            stopLoss: 58500,
            takeProfit: 63000,
            unrealizedPnl: 375,
            unrealizedPnlPercent: 2.5,
            marketValue: 15375,
            profileId: 'p1',
            profileName: 'High Risk Scalper ⚡',
            tradeId: 'trade-1',
        }
    ],
    health: {
        ...DEFAULT_BOT_STATE.health!,
        reconciliationMismatchCount: 2,
        reconciliationNoGoTrades: 1,
    },
};

describe('ChatControl DOM flow', () => {
    const LocationProbe = () => {
        const location = useLocation();
        return <div data-testid="chat-location">{`${location.pathname}${location.search}`}</div>;
    };

    const renderControl = (onApplyProfile = vi.fn(async () => ({ success: true }))) =>
        render(
            <MemoryRouter initialEntries={['/']}>
                <LocationProbe />
                <ChatControl profiles={profilesFixture} botState={botStateFixture} onApplyProfile={onApplyProfile} />
            </MemoryRouter>
        );

    beforeEach(() => {
        getPlatformAccessTokenMock.mockReset();
        writeTextMock.mockReset();
        vi.stubGlobal('fetch', vi.fn());

        Object.defineProperty(globalThis.navigator, 'clipboard', {
            value: { writeText: writeTextMock },
            configurable: true
        });
    });

    it('opens chat, shows quick actions, and handles unauthenticated send errors', async () => {
        getPlatformAccessTokenMock.mockRejectedValue(new Error('Not authenticated'));
        const onApplyProfile = vi.fn(async () => ({ success: true }));
        const user = userEvent.setup();

        renderControl(onApplyProfile);

        await user.click(screen.getAllByRole('button')[0]);

        expect(screen.getByText('AI Trading Copilot')).toBeInTheDocument();
        expect(screen.getByText('Tune High Risk Scalper ⚡')).toBeInTheDocument();
        expect(screen.getByText('Tune Conservative Bag')).toBeInTheDocument();

        await user.type(screen.getByPlaceholderText(/Ask for a profile, holding explanation, or reconciliation help/i), 'Create a conservative profile{enter}');

        await waitFor(() => {
            expect(screen.getByText(/session is not authorized for chat right now/i)).toBeInTheDocument();
        }, { timeout: 3000 });
    }, 15000);

    it('sends prompt, edits profile draft, copies JSON, and applies profile', async () => {
        getPlatformAccessTokenMock.mockResolvedValue('token-1');
        const fetchMock = vi.mocked(fetch);
        fetchMock.mockResolvedValue({
            ok: true,
            json: async () => ({
                summary: 'Generated profile summary',
                reasoning: 'Reasoning details',
                action: 'create_profile',
                profile: {
                    name: 'AI Generated',
                    allocated_capital: 1500,
                    risk_per_trade_percent: 1.2,
                    symbols: 'BTC/USDT',
                    is_active: true,
                    strategy_config: {
                        rules: [{ ruleId: 'TrendBiasRule', enabled: true }]
                    }
                }
            })
        } as any);

        const onApplyProfile = vi.fn(async () => ({ success: true }));
        const user = userEvent.setup();

        renderControl(onApplyProfile);

        await user.click(screen.getAllByRole('button')[0]);

        const textarea = screen.getByPlaceholderText(/Ask for a profile, holding explanation, or reconciliation help/i);
        await user.type(textarea, 'Create a profile for BTC/USDT{enter}');

        await waitFor(() => {
            expect(screen.getByText(/Generated profile summary/i)).toBeInTheDocument();
            expect(screen.getByText(/Reasoning details/i)).toBeInTheDocument();
        });

        await user.click(screen.getByTitle('Copy JSON'));

        await user.click(screen.getByRole('button', { name: /Edit Params/i }));
        const capitalInput = screen.getByPlaceholderText('Capital');
        fireEvent.change(capitalInput, { target: { value: '2500' } });

        await user.click(screen.getByRole('button', { name: /Apply to Dashboard/i }));

        await waitFor(() => {
            expect(onApplyProfile).toHaveBeenCalledWith(
                'create_profile',
                expect.objectContaining({
                    name: 'AI Generated',
                    allocated_capital: 2500
                })
            );
            expect(screen.getByText('Applied')).toBeInTheDocument();
        });
    }, 20000);

    it('marks generated profile action as cancelled', async () => {
        getPlatformAccessTokenMock.mockResolvedValue('token-2');
        vi.mocked(fetch).mockResolvedValue({
            ok: true,
            json: async () => ({
                summary: 'Generated profile summary',
                action: 'create_profile',
                profile: {
                    name: 'Cancelable Profile',
                    allocated_capital: 1200,
                    risk_per_trade_percent: 1,
                    symbols: 'ETH/USDT',
                    is_active: true,
                    strategy_config: { rules: [] }
                }
            })
        } as any);

        const user = userEvent.setup();

        renderControl(vi.fn(async () => ({ success: true })));
        await user.click(screen.getAllByRole('button')[0]);
        await user.type(screen.getByPlaceholderText(/Ask for a profile, holding explanation, or reconciliation help/i), 'Create profile{enter}');

        await waitFor(() => {
            expect(screen.getByRole('button', { name: /Cancel/i })).toBeInTheDocument();
        });

        await user.click(screen.getByRole('button', { name: /Cancel/i }));

        await waitFor(() => {
            expect(screen.getByText('Cancelled')).toBeInTheDocument();
            expect(screen.getByText(/Profile creation cancelled/i)).toBeInTheDocument();
        });
    });

    it('sends runtime context and renders explanation-only assistant responses without apply controls', async () => {
        getPlatformAccessTokenMock.mockResolvedValue('token-3');
        const fetchMock = vi.mocked(fetch);
        fetchMock.mockResolvedValue({
            ok: true,
            json: async () => ({
                summary: 'BTC/USDT is currently up 375.00 (2.50%) under High Risk Scalper ⚡.',
                reasoning: 'The bot is primarily waiting for either your profit target, stop-loss, or a management change from you.',
                action: 'explain_position',
                insights: ['Position side: BUY', 'Entry price: 60000.00'],
            })
        } as any);

        const user = userEvent.setup();

        renderControl(vi.fn(async () => ({ success: true })));
        await user.click(screen.getAllByRole('button')[0]);
        await user.type(screen.getByPlaceholderText(/Ask for a profile, holding explanation, or reconciliation help/i), 'Explain my current BTC holding{enter}');

        await waitFor(() => {
            expect(screen.getByText(/BTC\/USDT is currently up 375.00/i)).toBeInTheDocument();
            expect(screen.getByText(/waiting for either your profit target/i)).toBeInTheDocument();
            expect(screen.getByText(/Key facts/i)).toBeInTheDocument();
            expect(screen.getByText(/Position side: BUY/i)).toBeInTheDocument();
        });

        expect(screen.queryByRole('button', { name: /Apply to Dashboard/i })).not.toBeInTheDocument();

        expect(fetchMock).toHaveBeenCalledWith(
            expect.stringContaining('/api/chat'),
            expect.objectContaining({
                method: 'POST',
                body: expect.any(String),
            })
        );
        const body = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body));
        expect(body.context.runtime.positions).toEqual(
            expect.arrayContaining([
                expect.objectContaining({
                    symbol: 'BTC/USDT',
                    tradeId: 'trade-1',
                })
            ])
        );
        expect(body.context.runtime.health).toEqual(
            expect.objectContaining({
                reconciliationMismatchCount: 2,
                reconciliationNoGoTrades: 1,
            })
        );
        expect(body.context.runtime.signalContexts).toEqual(
            expect.arrayContaining([
                expect.objectContaining({
                    symbol: 'BTC/USDT',
                    executionCode: 'rule_ratio_not_met',
                    executionStatus: 'SKIPPED',
                })
            ])
        );
    });

    it('renders suggested next actions and applies a recommended profile change as an update', async () => {
        getPlatformAccessTokenMock.mockResolvedValue('token-4');
        const fetchMock = vi.mocked(fetch);
        fetchMock.mockResolvedValue({
            ok: true,
            json: async () => ({
                summary: 'I recommend tuning "High Risk Scalper ⚡" to improve execution quality without changing its overall strategy intent.',
                reasoning: 'Recent signal context shows BTC/USDT is waiting on rule alignment.',
                nextActions: [
                    'Review the suggested profile draft below before applying it.',
                    'After applying, monitor the next signal cycle to see whether the waiting state improves.'
                ],
                action: 'recommend_profile_change',
                profile: {
                    id: 'p1',
                    name: 'High Risk Scalper ⚡',
                    allocated_capital: 1000,
                    risk_per_trade_percent: 1.1,
                    symbols: 'BTC/USDT',
                    is_active: true,
                    strategy_config: {
                        rules: [{ ruleId: 'TrendBiasRule', enabled: true }],
                        riskLimits: { maxDailyLossUsd: 50, maxOpenTrades: 2, maxConsecutiveLosses: 2 },
                        execution: { orderType: 'market', cooldownMinutes: 20, entryMode: 'both' }
                    }
                }
            })
        } as any);

        const onApplyProfile = vi.fn(async () => ({ success: true }));
        const user = userEvent.setup();

        renderControl(onApplyProfile);
        await user.click(screen.getAllByRole('button')[0]);
        await user.type(screen.getByPlaceholderText(/Ask for a profile, holding explanation, or reconciliation help/i), 'Recommend a profile change for High Risk Scalper ⚡{enter}');

        await waitFor(() => {
            expect(screen.getByText(/Suggested next actions/i)).toBeInTheDocument();
            expect(screen.getByText(/Review the suggested profile draft below before applying it/i)).toBeInTheDocument();
            expect(screen.getByText(/Suggested Profile Change/i)).toBeInTheDocument();
        });

        await user.click(screen.getByRole('button', { name: /Apply to Dashboard/i }));

        await waitFor(() => {
            expect(onApplyProfile).toHaveBeenCalledWith(
                'update_profile',
                expect.objectContaining({
                    id: 'p1',
                    name: 'High Risk Scalper ⚡',
                    risk_per_trade_percent: 1.1,
                })
            );
        });
    });

    it('renders safe quick links for copilot follow-up actions', async () => {
        getPlatformAccessTokenMock.mockResolvedValue('token-5');
        vi.mocked(fetch).mockResolvedValue({
            ok: true,
            json: async () => ({
                summary: 'A Trade Plan would help manage BTC/USDT more explicitly from here.',
                reasoning: 'You already have a live holding on BTC/USDT.',
                action: 'recommend_trade_plan',
                nextActions: ['Open Plans to attach or adjust a profit exit for this holding.'],
                quickLinks: [
                    { kind: 'plans', label: 'Manage in Plans', mode: 'sell', symbol: 'BTC/USDT', tradeId: 'trade-1' },
                    { kind: 'portfolio', label: 'Open Portfolio', tradeId: 'trade-1', symbol: 'BTC/USDT' }
                ]
            })
        } as any);

        const user = userEvent.setup();
        renderControl();
        await user.click(screen.getAllByRole('button')[0]);
        await user.type(screen.getByPlaceholderText(/Ask for a profile, holding explanation, or reconciliation help/i), 'Recommend a trade plan for BTC/USDT{enter}');

        await waitFor(() => {
            expect(screen.getByRole('button', { name: /Manage in Plans/i })).toBeInTheDocument();
            expect(screen.getByRole('button', { name: /Open Portfolio/i })).toBeInTheDocument();
        });
    });

    it('navigates through safe quick links into plans and settings routes', async () => {
        getPlatformAccessTokenMock.mockResolvedValue('token-6');
        vi.mocked(fetch).mockResolvedValue({
            ok: true,
            json: async () => ({
                summary: 'Reconciliation needs follow-up before more execution.',
                reasoning: 'Current mismatch counts show the admin panel is the safest next stop.',
                action: 'recommend_reconciliation_followup',
                quickLinks: [
                    { kind: 'settings', label: 'Open Admin Panel', section: 'Admin Panel' },
                    { kind: 'plans', label: 'Manage in Plans', mode: 'sell', symbol: 'BTC/USDT', tradeId: 'trade-1' }
                ]
            })
        } as any);

        const user = userEvent.setup();
        renderControl();
        await user.click(screen.getAllByRole('button')[0]);
        await user.type(screen.getByPlaceholderText(/Ask for a profile, holding explanation, or reconciliation help/i), 'What should I do about this reconciliation mismatch?{enter}');

        await waitFor(() => {
            expect(screen.getByRole('button', { name: /Open Admin Panel/i })).toBeInTheDocument();
        });

        await user.click(screen.getByRole('button', { name: /Open Admin Panel/i }));
        await waitFor(() => {
            expect(screen.getByTestId('chat-location')).toHaveTextContent('/settings?section=Admin+Panel');
        });

        await user.click(screen.getAllByRole('button')[0]);
        await user.click(screen.getByRole('button', { name: /Manage in Plans/i }));
        await waitFor(() => {
            expect(screen.getByTestId('chat-location')).toHaveTextContent('/plans?mode=sell&symbol=BTC%2FUSDT&tradeId=trade-1');
        });
    });
});
