import React from 'react';
import { shallow } from 'enzyme';
import { assoc, assocPath, clone, has, last } from 'ramda';

import { JwPlayer } from './JwPlayer';
import { printError } from '../../lib';

jest.mock('../../../public/lib/jwplayer/jwplayer', () => null);
jest.mock('../../lib');

describe('JwPlayer', () => {
  let wrapper;

  const PLAYER_FIXTURE = { on: jest.fn() };

  const site = {
    getSchema: jest.fn().mockReturnValue({
      keys: { jwplayer_8: 'test key' },
      name: 'Test',
      url: 'test.com',
    }),
  };

  const JWPLAYER_MEDIA_FIXTURE = {
    'image': 'sample/path',
    'mediaid': 23,
    'sources': [{ 'file': 'http://examplehls.com', 'type': 'hls' }],
    'tracks': [
      { 'file': 'http://examplepreview.com', 'kind': 'thumbnails' },
    ],
  };
  const JWPLAYER_CONFIG_FIXTURE = {
    aspectratio: '16:9',
    playlist: [JWPLAYER_MEDIA_FIXTURE],
  };

  let jwplayer;

  beforeEach(() => {
    jwplayer = {
      remove: jest.fn(),
      setup: jest.fn().mockReturnValue(PLAYER_FIXTURE),
      getState: jest.fn().mockReturnValue('idle'),
    };
    window.jwplayer = jest.fn().mockReturnValue(jwplayer);
    window.__webpack_public_path__ = '/test/path/';
    wrapper = shallow(
      <JwPlayer
        jwPlayerConfig={ JWPLAYER_CONFIG_FIXTURE }
        site={ site }
      />
    );
    printError.mockClear();
  });

  const getSetup = () => last(jwplayer.setup.mock.calls)[0];

  it('renders', () => {
    expect(wrapper).toExist();
  });

  it('passes the ref to the player', () => {
    const component = wrapper.instance();

    expect(window.jwplayer).toHaveBeenCalled();
    expect(window.jwplayer)
      .toHaveBeenCalledWith(component.playerDivRef.current);
  });

  it('sets up the video when mounted', () => {
    expect(jwplayer.setup).toHaveBeenCalled();
  });

  it('replaces any existing player when updated with a new data', () => {
    const clonedConfig = clone(JWPLAYER_CONFIG_FIXTURE);
    clonedConfig.playlist.push(JWPLAYER_MEDIA_FIXTURE);
    const newJwplayerConfig = assocPath(
      ['playlist', 0, 'image'],
      'new-image',
      clonedConfig
    );

    jwplayer.setup.mockClear();
    wrapper.setProps({ jwPlayerConfig: newJwplayerConfig });

    expect(jwplayer.remove).toHaveBeenCalledTimes(1);
    expect(jwplayer.setup).toHaveBeenCalledTimes(1);
  });

  it('does not update if you pass the same jwplayerconfig prop', () => {
    const newJwplayerConfig = clone(JWPLAYER_CONFIG_FIXTURE);
    jwplayer.setup.mockClear();

    wrapper.setProps({ jwPlayerConfig: newJwplayerConfig });

    expect(jwplayer.remove).not.toHaveBeenCalledTimes(1);
    expect(jwplayer.setup).not.toHaveBeenCalledTimes(1);
  });

  it('sets JWPlayer key', () => {
    expect(window.jwplayer.key).toBe('test key');
  });

  it('sets the base path from __webpack_public_path__', () => {
    expect(getSetup().base).toBe('/test/path/lib/jwplayer');
  });

  it('sets about information correctly', () => {
    expect(getSetup().abouttext).toBe('Test');
    expect(getSetup().aboutlink).toBe('test.com');
  });

  it('creates a playlist with prop passed in', () => {
    expect(getSetup().playlist).toEqual(JWPLAYER_CONFIG_FIXTURE.playlist);
  });

  it('removes jwplayer when component unmounts', () => {
    wrapper.unmount();

    expect(jwplayer.remove).toHaveBeenCalled();
  });

  it('creates setup with aspectratio', () => {
    expect(getSetup().aspectratio).toBe('16:9');
  });

  it('removes aspectratio if height is provided', () => {
    const newconfig = assoc('height', '100%', JWPLAYER_CONFIG_FIXTURE);

    wrapper.setProps({ jwPlayerConfig: newconfig });
    expect(getSetup().height).toBe('100%');
    expect(has('aspectratio')(getSetup())).toBe(false);
  });

  it('calls onPlayerMounted if it is provided', () => {
    const onPlayerMounted = jest.fn();
    wrapper.setProps({ onPlayerMounted });

    const component = wrapper.instance();
    component.setupVideo();

    expect(onPlayerMounted).toHaveBeenCalled();
  });

  it('ensures the configuration prop is not modified by JWPlayer', () => {
    const configBackup = clone(JWPLAYER_CONFIG_FIXTURE);
    jwplayer.setup.mockImplementation((setup) => {
      // Simulate JWPlayer mutating the configuration, because it does
      setup.playlist[0].newProperty = 'new value';
      return PLAYER_FIXTURE;
    });

    shallow(
      <JwPlayer jwPlayerConfig={ JWPLAYER_CONFIG_FIXTURE } site={ site } />
    );

    expect(JWPLAYER_CONFIG_FIXTURE).toEqual(configBackup);
  });

  it('throws an error if jwplayer setup fails', () => {
    jwplayer.setup.mockImplementation(() => {
      throw new Error();
    });

    shallow(
      <JwPlayer jwPlayerConfig={ JWPLAYER_CONFIG_FIXTURE } site={ site } />
    );

    expect(printError).toHaveBeenCalled();
    expect(printError).toHaveBeenCalledWith(
      'There was an error with the setup configuration of the video player.'
    );
  });

  it('throws error when component unmounts jwplayer remove fails', () => {
    jwplayer.remove.mockImplementation(() => {
      throw new Error('Error');
    });

    wrapper.unmount();

    expect(printError).toHaveBeenCalled();
    expect(printError).toHaveBeenCalledWith(
      'There was an error unmounting the video player.'
    );
  });

  it('does not replace existing player if video is playing', () => {
    const newJwplayerConfig = assocPath(
      ['playlist', 0, 'image'],
      'new-image',
      JWPLAYER_CONFIG_FIXTURE
    );

    jwplayer.setup.mockClear();
    jwplayer.getState.mockClear();
    jwplayer.getState.mockReturnValue('playing');
    wrapper.setProps({ jwPlayerConfig: newJwplayerConfig });

    expect(jwplayer.remove).toHaveBeenCalledTimes(0);
    expect(jwplayer.setup).toHaveBeenCalledTimes(0);
  });

  it('survives next render even if the player does not exist', () => {
    jwplayer.setup.mockClear();
    wrapper.instance().jwplayer = window.jwplayer = undefined;

    wrapper.setProps({ });

    // test passes if there's no build-errors
  });
});
