import React from 'react';
import { screen, fireEvent, act, waitFor } from '@testing-library/react';
import { renderInAppContext } from '@theorchard/suite-testing';
import SpotifyPopulatorSidecar from '..';

const mockToast = jest.fn();
const mockPopulateTemplate = jest.fn().mockResolvedValue(undefined);
const mockSetTaskToken = jest.fn();
let capturedOnSuccess: ((downloadUrl: string) => void) | undefined;
let capturedOnFailure: (() => void) | undefined;

jest.mock('@theorchard/suite-components', () => ({
    ...jest.requireActual('@theorchard/suite-components'),
    useToast: () => mockToast,
}));

jest.mock('src/data/queries/templateArtistLookup', () => ({
    useTemplateArtistLookup: () => jest.fn(),
}));

jest.mock('src/data/queries/templateAlbumLookup', () => ({
    useTemplateAlbumLookup: () => jest.fn(),
}));

jest.mock('src/data/mutations/populateTemplate', () => ({
    usePopulateTemplateMutation: (cb: (token: string | null) => void) => {
        return (...args: unknown[]) => {
            cb('mock-task-token');
            return mockPopulateTemplate(...args);
        };
    },
}));

jest.mock('src/data/queries/getTemplateStatus', () => ({
    __esModule: true,
    default: ({
        onSuccess,
        onFailure,
    }: {
        onSuccess: (downloadUrl: string) => void;
        onFailure: () => void;
    }) => {
        capturedOnSuccess = onSuccess;
        capturedOnFailure = onFailure;
        return { setTaskToken: mockSetTaskToken };
    },
}));

const mockSpotifySelectOnChanges: ((
    value: { name: string; url: string }[]
) => void)[] = [];
const mockSpotifySelectExpectedTypes: string[] = [];
const mockSpotifySelectInvalidTypeMessages: ((type: string) => string)[] = [];

jest.mock('../../spotifySelect', () => {
    return {
        __esModule: true,
        default: ({
            onChange,
            expectedType,
            invalidTypeMessage,
        }: {
            onChange: (value: { name: string; url: string }[]) => void;
            expectedType: string;
            invalidTypeMessage: (type: string) => string;
        }) => {
            mockSpotifySelectOnChanges.push(onChange);
            mockSpotifySelectExpectedTypes.push(expectedType);
            mockSpotifySelectInvalidTypeMessages.push(invalidTypeMessage);
            return (
                <div
                    data-testid="SpotifySelect"
                    data-expected-type={expectedType}
                />
            );
        },
    };
});

describe('SpotifyPopulatorSidecar', () => {
    beforeEach(() => {
        jest.clearAllMocks();
        mockSpotifySelectOnChanges.length = 0;
        mockSpotifySelectExpectedTypes.length = 0;
        mockSpotifySelectInvalidTypeMessages.length = 0;
    });

    const populateArtist = () => {
        // First SpotifySelect rendered is the artist one
        act(() => {
            mockSpotifySelectOnChanges[0]([
                { name: 'Artist', url: 'https://spotify.com/artist/1' },
            ]);
        });
    };

    test('renders closed sidecar', async () => {
        const component = renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={false} onClose={() => null} />
        );
        expect(component).toMatchSnapshot();
    });

    test('renders open sidecar', async () => {
        const component = renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        expect(component).toMatchSnapshot();
    });

    test('renders SpotifySelect when open', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        expect(screen.getAllByTestId('SpotifySelect')).toHaveLength(2);
    });

    test('passes expectedType=artist to the first select and album to the second', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        expect(mockSpotifySelectExpectedTypes).toEqual(['artist', 'album']);
    });

    test('invalidTypeMessage produces a translated message for the artist field', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        const message = mockSpotifySelectInvalidTypeMessages[0]('playlist');
        expect(message).toContain('playlist');
        expect(message).toContain('artist');
    });

    test('invalidTypeMessage produces a translated message for the album field', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        const message = mockSpotifySelectInvalidTypeMessages[1]('track');
        expect(message).toContain('track');
        expect(message).toContain('album');
    });

    test('renders confirm button with correct title', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        const confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );
        expect(confirmButton).toHaveTextContent('Download Populated Template');
    });

    test('confirm button is disabled when no artists or albums are selected', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        const confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );
        expect(confirmButton).toBeDisabled();
    });

    test('confirm button is enabled when artists are selected', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        populateArtist();
        const confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );
        expect(confirmButton).not.toBeDisabled();
    });

    test('confirm button shows loading state when clicked', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        populateArtist();
        let confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );

        fireEvent.click(confirmButton);

        confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );

        expect(confirmButton).toHaveClass('loading');
    });

    test('calls populateTemplate mutation when confirm button is clicked', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        populateArtist();
        const confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );

        fireEvent.click(confirmButton);

        expect(mockPopulateTemplate).toHaveBeenCalledWith({
            artists: [{ name: 'Artist', url: 'https://spotify.com/artist/1' }],
            albums: [],
        });
    });

    test('sets task token after mutation completes', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        populateArtist();
        const confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );

        fireEvent.click(confirmButton);

        expect(mockSetTaskToken).toHaveBeenCalledWith('mock-task-token');
    });

    test('triggers file download and closes on success', () => {
        const onClose = jest.fn();
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={onClose} />
        );

        const clickSpy = jest.fn();
        jest.spyOn(document, 'createElement').mockImplementationOnce(() => {
            const el = {
                href: '',
                download: '',
                click: clickSpy,
            } as unknown as HTMLAnchorElement;
            return el;
        });
        const appendSpy = jest
            .spyOn(document.body, 'appendChild')
            .mockImplementationOnce(jest.fn());
        const removeSpy = jest
            .spyOn(document.body, 'removeChild')
            .mockImplementationOnce(jest.fn());

        act(() => {
            capturedOnSuccess!('https://example.com/template.xlsx');
        });

        expect(clickSpy).toHaveBeenCalled();
        expect(appendSpy).toHaveBeenCalled();
        expect(removeSpy).toHaveBeenCalled();
        expect(onClose).toHaveBeenCalled();

        appendSpy.mockRestore();
        removeSpy.mockRestore();
    });

    test('shows error alert on failure', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );

        expect(
            screen.queryByText(
                $t('bulkDigitalAudio.spotifyPopulatorSidecar.errorTitle')
            )
        ).not.toBeInTheDocument();

        act(() => {
            capturedOnFailure!();
        });

        expect(
            screen.getByText(
                $t('bulkDigitalAudio.spotifyPopulatorSidecar.errorTitle')
            )
        ).toBeInTheDocument();
    });

    test('hides error alert when confirm is clicked again', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        populateArtist();

        act(() => {
            capturedOnFailure!();
        });

        expect(
            screen.getByText(
                $t('bulkDigitalAudio.spotifyPopulatorSidecar.errorTitle')
            )
        ).toBeInTheDocument();

        const confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );
        fireEvent.click(confirmButton);

        expect(
            screen.queryByText(
                $t('bulkDigitalAudio.spotifyPopulatorSidecar.errorTitle')
            )
        ).not.toBeInTheDocument();
    });

    test('stops loading state on failure', () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        populateArtist();

        let confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );

        fireEvent.click(confirmButton);

        confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );

        act(() => {
            capturedOnFailure!();
        });

        confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );
        expect(confirmButton).not.toHaveClass('loading');
    });

    test('shows success toast when template status completes', () => {
        const onClose = jest.fn();
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={onClose} />
        );

        capturedOnSuccess!('test.com');

        expect(mockToast).toHaveBeenCalledWith('Template Download Complete', {
            variant: 'success',
        });
    });

    test('shows tooltip on hover when populating is true', async () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        populateArtist();

        let confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );

        fireEvent.click(confirmButton);

        confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );

        // Hover over the button to trigger tooltip
        fireEvent.mouseEnter(confirmButton);

        // Wait for the tooltip to appear
        const tooltip = await screen.findByRole('tooltip');
        expect(tooltip).toBeVisible();
        expect(tooltip).toHaveTextContent(
            $t('bulkDigitalAudio.spotifyPopulatorSidecar.loadingTooltip')
        );
    });

    test('does not show tooltip on hover when populating is false', async () => {
        renderInAppContext(
            <SpotifyPopulatorSidecar isOpen={true} onClose={() => null} />
        );
        populateArtist();

        const confirmButton = screen.getByTestId(
            'spotify-populator-sidecar--confirm-button'
        );

        // Button should NOT be in loading state
        expect(confirmButton).not.toHaveClass('loading');

        // Hover over the button
        fireEvent.mouseEnter(confirmButton);

        // Wait a bit and ensure tooltip does not appear
        await waitFor(() => {
            expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
        });
    });
});
