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

const {
    getSessionMock,
    stocksInsertMock,
    stocksUpdateEqMock,
    stocksUpdateMock,
    historyInsertMock,
    fromMock,
    authMock
} = vi.hoisted(() => ({
    getSessionMock: vi.fn(),
    stocksInsertMock: vi.fn(),
    stocksUpdateEqMock: vi.fn(),
    stocksUpdateMock: vi.fn(),
    historyInsertMock: vi.fn(),
    fromMock: vi.fn(),
    authMock: { user: { id: 'user-1' } as any }
}));

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

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

describe('EntryForm DOM flow', () => {
    const alertMock = vi.fn();

    beforeEach(() => {
        vi.clearAllMocks();
        stocksInsertMock.mockReset();
        stocksUpdateEqMock.mockReset();
        stocksUpdateMock.mockReset();
        historyInsertMock.mockReset();
        getSessionMock.mockReset();
        fromMock.mockReset();
        authMock.user = { id: 'user-1' };

        stocksInsertMock.mockResolvedValue({ error: null });
        stocksUpdateEqMock.mockResolvedValue({ error: null });
        stocksUpdateMock.mockReturnValue({ eq: stocksUpdateEqMock });
        historyInsertMock.mockResolvedValue({ error: null });
        getSessionMock.mockResolvedValue({ data: { session: null } });

        fromMock.mockImplementation((table: string) => {
            if (table === tableNameStocks) {
                return {
                    insert: stocksInsertMock,
                    update: stocksUpdateMock
                };
            }
            if (table === 'trade_history') {
                return {
                    insert: historyInsertMock
                };
            }
            return {
                insert: vi.fn().mockResolvedValue({ error: null }),
                update: vi.fn(() => ({ eq: vi.fn().mockResolvedValue({ error: null }) }))
            };
        });

        vi.stubGlobal('fetch', vi.fn());
        vi.stubGlobal('confirm', vi.fn(() => true));
        vi.stubGlobal('alert', alertMock);
        vi.spyOn(crypto, 'randomUUID').mockReturnValue('11111111-1111-1111-1111-111111111111');
    });

    it('inserts a new manual entry with numeric normalization', async () => {
        const onSuccess = vi.fn();
        const user = userEvent.setup();

        render(<EntryForm onSuccess={onSuccess} />);

        await user.type(screen.getByPlaceholderText('BTC/USD'), 'BTC/USD');
        await user.type(screen.getByPlaceholderText('0.00'), '100');
        await user.type(screen.getByPlaceholderText('0'), '1');
        await user.click(screen.getByRole('button', { name: 'Add' }));

        await waitFor(() => {
            expect(stocksInsertMock).toHaveBeenCalled();
            expect(onSuccess).toHaveBeenCalled();
        });
    });

    it('alerts error when trade execution fails', async () => {
        const onSuccess = vi.fn();
        const user = userEvent.setup();
        getSessionMock.mockResolvedValue({ data: { session: { access_token: 'valid-token' } } });
        vi.mocked(fetch).mockResolvedValue({
            ok: false,
            json: async () => ({ success: false, error: 'Insufficient funds' })
        } as any);

        render(<EntryForm onSuccess={onSuccess} />);
        await user.type(screen.getByPlaceholderText('BTC/USD'), 'BTC/USD');
        await user.click(screen.getByRole('checkbox', { name: /Execute/i }));
        await user.click(screen.getByRole('button', { name: 'Add' }));

        await waitFor(() => {
            expect(alertMock).toHaveBeenCalledWith(expect.stringContaining('Execution Failed: Insufficient funds'));
        });
    });

    it('journals a past trade (entry + exit both provided)', async () => {
        const onSuccess = vi.fn();
        const user = userEvent.setup();

        render(<EntryForm onSuccess={onSuccess} />);
        await user.type(screen.getByPlaceholderText('BTC/USD'), 'BTC/USD');
        await user.type(screen.getByPlaceholderText('0.00'), '100');
        await user.type(screen.getByPlaceholderText('Exit'), '110');
        await user.type(screen.getByPlaceholderText('0'), '1');
        await user.click(screen.getByRole('button', { name: 'Add' }));

        await waitFor(() => {
            expect(historyInsertMock).toHaveBeenCalledTimes(1);
        });
        expect(historyInsertMock.mock.calls[0][0][0].reason).toBe('Manual Log');
    });

    it('handles database error by alerting user', async () => {
        stocksInsertMock.mockResolvedValue({ error: { message: 'DB Error' } });
        const user = userEvent.setup();

        render(<EntryForm onSuccess={vi.fn()} />);
        await user.type(screen.getByPlaceholderText('BTC/USD'), 'FAIL/USD');
        await user.click(screen.getByRole('button', { name: 'Add' }));

        await waitFor(() => {
            expect(alertMock).toHaveBeenCalledWith(expect.stringContaining('DB Error'));
        });
    });

    it('does nothing when submitted without a user', async () => {
        authMock.user = null;
        const user = userEvent.setup();
        render(<EntryForm onSuccess={vi.fn()} />);
        await user.type(screen.getByPlaceholderText('BTC/USD'), 'BTC/USD');
        await user.click(screen.getByRole('button', { name: 'Add' }));

        expect(stocksInsertMock).not.toHaveBeenCalled();
    });
});
