import React from 'react';
import { act, fireEvent, screen } from '@testing-library/react';
import { renderInAppContext } from '@theorchard/suite-testing';
import { getMockTrack } from 'lib/mockProductData';
import { PAUSE_REQUESTED, PLAY_REQUESTED } from 'src/constants';
import * as trackAudioQuery from 'src/data/queries/trackAudio/trackAudio';
import { AudioPlayerSection, CLASS_NAME, Props } from '../audioPlayerSection';

jest.mock('hls.js', () => {
    let destroyCalled = false;

    const hlsMock = function () {
        const mockLoadingEvents = ['hlsManifestLoaded', 'hlsMediaAttached'];

        return {
            loadSource: jest.fn(),
            attachMedia: jest.fn(),
            destroy: jest.fn(() => {
                destroyCalled = true;
            }),
            on: jest.fn((event: string, callback: () => void) => {
                if (mockLoadingEvents.includes(event)) callback();
            }),
        };
    };

    hlsMock.Events = {
        MEDIA_ATTACHED: 'hlsMediaAttached',
        MANIFEST_PARSED: 'hlsManifestParsed',
        ERROR: 'hlsError',
    };

    hlsMock.isSupported = jest.fn(() => true);
    hlsMock.__wasDestroyed = () => destroyCalled;

    return hlsMock;
});

describe('<AudioPlayerSection />', () => {
    const tuid = 123456;
    const defaultProps = {
        tuid,
        title: 'Track 1: House Of The Rising Sun',
        audioStatus: 0,
        isActive: true,
        section: 'primary',
        setActivePlayer: jest.fn(),
        onChangeAudioStatus: jest.fn(),
        onChangeCurrentTrack: jest.fn(),
    };
    let playSpy: jest.SpyInstance;
    let pauseSpy: jest.SpyInstance;

    const mockTrack = getMockTrack();

    const renderComponent = (props: Partial<Props> = {}) => {
        return renderInAppContext(
            <AudioPlayerSection {...defaultProps} {...props} />
        );
    };

    beforeEach(() => {
        jest.spyOn(trackAudioQuery, 'useTrackAudioQuery').mockImplementation(
            () => ({
                loading: false,
                error: undefined,
                data: { track: mockTrack },
            })
        );
        playSpy = jest
            .spyOn(window.HTMLMediaElement.prototype, 'play')
            .mockImplementation(async () => await Promise.resolve());
        pauseSpy = jest
            .spyOn(HTMLAudioElement.prototype, 'pause')
            // eslint-disable-next-line @typescript-eslint/no-empty-function
            .mockImplementation(() => {});
    });

    afterEach(() => {
        jest.restoreAllMocks();
    });

    test('renders scrubber component after successfully loaded', () => {
        const { getByTestId } = renderComponent();
        const scrubber = getByTestId(`AudioPlayerScrubber`);
        const audioElement = getByTestId(`${CLASS_NAME}-audio-primary-${tuid}`);

        act(() => {
            audioElement.dispatchEvent(new Event('loadeddata'));
        });

        expect(scrubber).toBeInTheDocument();
        expect(audioElement).toBeInTheDocument();
    });

    test('renders as primary section with audio', () => {
        const { getByTestId } = renderComponent();
        const primaryContainer = getByTestId(`${CLASS_NAME}-primary`);
        const audioElement = getByTestId(`${CLASS_NAME}-audio-primary-${tuid}`);

        act(() => {
            audioElement.dispatchEvent(new Event('loadeddata'));
        });

        expect(primaryContainer).toBeInTheDocument();
        expect(
            screen.getByText('Track 1: House Of The Rising Sun')
        ).toBeVisible();
        expect(getByTestId('PlayGlyphIcon')).toBeVisible();
        expect(getByTestId('SuiteCard')).not.toHaveClass('disabled');
    });

    test('renders as secondary section with audio', () => {
        const { getByTestId } = renderComponent({
            section: 'secondary',
            audioCorrectionIds: [tuid],
        });
        const secondaryContainer = getByTestId(`${CLASS_NAME}-secondary`);
        const audioElement = getByTestId(
            `${CLASS_NAME}-audio-secondary-${tuid}`
        );

        act(() => {
            audioElement.dispatchEvent(new Event('loadeddata'));
        });

        expect(secondaryContainer).toBeInTheDocument();
        expect(screen.getByText('Previous Audio')).toBeVisible();
        expect(getByTestId('SuiteCard')).not.toHaveClass('disabled');
    });

    test('renders section as disabled', () => {
        const { getByTestId } = renderComponent();
        const card = getByTestId('SuiteCard');

        expect(card).toHaveClass('disabled');
    });

    test('plays audio, after successfully loaded, upon clicking play button', () => {
        const { getByTestId } = renderComponent({
            audioStatus: PLAY_REQUESTED,
        });
        const playButton = getByTestId('audio-button');
        const audioElement = getByTestId(`${CLASS_NAME}-audio-primary-${tuid}`);

        act(() => {
            audioElement.dispatchEvent(new Event('loadeddata'));
        });

        fireEvent.click(playButton);

        expect(playSpy).toHaveBeenCalled();
        expect(defaultProps.onChangeAudioStatus).toHaveBeenCalledWith(
            PAUSE_REQUESTED
        );
    });

    test('pauses audio, after successfully loaded, upon clicking play button twice', () => {
        const { getByTestId } = renderComponent({
            audioStatus: PLAY_REQUESTED,
        });
        const playButton = getByTestId('audio-button');
        const audioElement = getByTestId(`${CLASS_NAME}-audio-primary-${tuid}`);

        act(() => {
            audioElement.dispatchEvent(new Event('loadeddata'));
        });

        fireEvent.click(playButton);
        fireEvent.click(playButton);

        expect(pauseSpy).toHaveBeenCalled();
        expect(defaultProps.onChangeAudioStatus).toHaveBeenCalledWith(
            PLAY_REQUESTED
        );
    });

    test('plays audio immediately after loading if `PLAY_REQUESTED` status passed', () => {
        const { getByTestId } = renderComponent({
            audioStatus: PLAY_REQUESTED,
        });
        const audioElement = getByTestId(`${CLASS_NAME}-audio-primary-${tuid}`);

        act(() => {
            audioElement.dispatchEvent(new Event('loadeddata'));
        });

        expect(playSpy).toHaveBeenCalled();
    });

    test("audio doesn't play immediately after loading on secondary section", () => {
        const { getByTestId } = renderComponent({
            section: 'secondary',
            audioStatus: PLAY_REQUESTED,
            audioCorrectionIds: [tuid],
        });
        const audioElement = getByTestId(
            `${CLASS_NAME}-audio-secondary-${tuid}`
        );

        act(() => {
            audioElement.dispatchEvent(new Event('loadeddata'));
        });

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

    test('pauses audio when clicking play on another player', () => {
        const { getByTestId, rerender } = renderComponent({
            section: 'secondary',
            audioStatus: PLAY_REQUESTED,
            audioCorrectionIds: [tuid],
            isActive: true,
        });
        const audioElement = getByTestId(
            `${CLASS_NAME}-audio-secondary-${tuid}`
        );

        act(() => {
            audioElement.dispatchEvent(new Event('loadeddata'));
        });

        rerender(<AudioPlayerSection {...defaultProps} isActive={false} />);

        expect(pauseSpy).toHaveBeenCalled();
    });

    test('calls onChangeCurrentTrack when clicking play on initial loaded track', () => {
        const { getByTestId } = renderComponent({
            section: 'secondary',
            audioStatus: PLAY_REQUESTED,
            audioCorrectionIds: [tuid],
            isActive: true,
        });
        const playButton = getByTestId('audio-button');
        const audioElement = getByTestId(
            `${CLASS_NAME}-audio-secondary-${tuid}`
        );

        act(() => {
            audioElement.dispatchEvent(new Event('loadeddata'));
        });

        fireEvent.click(playButton);

        expect(playSpy).toHaveBeenCalled();
        expect(defaultProps.onChangeCurrentTrack).toHaveBeenCalled();
    });
});
