import React from 'react';
import { render, act, screen } from '@testing-library/react';
import { testComponent } from 'lib/test-utils/common';
import { ToastContainer, ToastContainerProps } from '../toastContainer';
import { Message } from '../types';

type PopoverElement = HTMLElement & {
    showPopover?: (() => void) | undefined;
    hidePopover?: (() => void) | undefined;
};

describe('<ToastContainer>', () => {
    const defaultProps: ToastContainerProps = {
        messages: [],
        setMessages: vi.fn(),
        toastTime: 1,
    };

    const renderComponent = (props: Partial<ToastContainerProps> = {}) =>
        render(<ToastContainer {...defaultProps} {...props} />);

    testComponent(ToastContainer, {
        ...defaultProps,
        messages: [{ id: '1', content: 'test' } as Message],
    });

    beforeEach(() => {
        vi.useFakeTimers();
    });

    describe('without messages', () => {
        test('does not render "ToastContainer"', () => {
            const { queryByTestId } = renderComponent();

            expect(queryByTestId('ToastContainer')).toBeNull();
        });
    });

    describe('with messages', () => {
        const props = {
            messages: [
                { id: '1', content: 'one' },
                { id: '2', content: 'two' },
            ],
            setMessages: vi.fn(),
        };

        beforeEach(() => {
            props.setMessages.mockClear();
        });

        const expectMessagesBeingCleared = async () => {
            act(() => {
                vi.runAllTimers();
            });

            expect(props.setMessages).toHaveBeenCalledWith([]);
        };

        test('renders "ToastContainer"', async () => {
            const { getByTestId } = renderComponent(props);

            expect(getByTestId('ToastContainer')).toBeVisible();

            await expectMessagesBeingCleared();
        });

        test('renders toasts', async () => {
            const { getByText } = renderComponent(props);

            props.messages.forEach((message) => expect(getByText(message.content)).toBeVisible());

            await expectMessagesBeingCleared();
        });

        // eslint-disable-next-line jest/expect-expect
        test('calls "setMessages" with empty array on timeout', async () => {
            renderComponent(props);

            await expectMessagesBeingCleared();
        });
    });

    describe('Popover API integration', () => {
        const props = {
            messages: [{ id: '1', content: 'one' }] as Message[],
            setMessages: vi.fn(),
        };

        afterEach(() => {
            (HTMLElement.prototype as unknown as Record<string, unknown>).showPopover = undefined;
            (HTMLElement.prototype as unknown as Record<string, unknown>).hidePopover = undefined;
        });

        test('renders container with popover="manual" attribute', () => {
            renderComponent(props);

            expect(screen.getByTestId('ToastContainer')).toHaveAttribute('popover', 'manual');
        });

        test('calls showPopover() when supported and container mounts with messages', () => {
            const showPopover = vi.fn();
            (HTMLElement.prototype as PopoverElement).showPopover = showPopover;
            const matchesSpy = vi
                .spyOn(HTMLElement.prototype, 'matches')
                .mockImplementation((selector: string) =>
                    selector === ':popover-open' ? false : false
                );

            renderComponent(props);

            expect(showPopover).toHaveBeenCalledTimes(1);
            matchesSpy.mockRestore();
        });

        test('does not call showPopover() when already open', () => {
            const showPopover = vi.fn();
            (HTMLElement.prototype as PopoverElement).showPopover = showPopover;
            const matchesSpy = vi
                .spyOn(HTMLElement.prototype, 'matches')
                .mockImplementation((selector: string) => selector === ':popover-open');

            renderComponent(props);

            expect(showPopover).not.toHaveBeenCalled();
            matchesSpy.mockRestore();
        });

        test('does not throw when Popover API is unsupported', () => {
            expect((HTMLElement.prototype as PopoverElement).showPopover).toBeUndefined();
            expect(() => renderComponent(props)).not.toThrow();
            expect(screen.getByTestId('ToastContainer')).toBeVisible();
        });

        test('does not call showPopover() when there are no messages', () => {
            const showPopover = vi.fn();
            (HTMLElement.prototype as PopoverElement).showPopover = showPopover;

            renderComponent({ messages: [], setMessages: vi.fn() });

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