import { shallow } from 'enzyme';
import React from 'react';
import { assoc, forEach } from 'ramda';

import { MediaVideo } from './MediaVideo';
import DefaultItem from '../Item/Item';
import { Site } from '../../../tests/storyshare';

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

describe('MediaVideo', () => {
  const VIDEO_FIXTURE = {
    id: 23,
    endpoints: {
      hls: 'http://examplehls.com',
      preview_thumbnails: 'http://examplepreview.com',
      offline: 'http://offline.com',
      subtitles: { en: 'test' },
    },
    playback: {},
  };

  const PLAYLIST_FIXTURE = [{
    mediaid: 23,
    tracks: [
      { 'file': 'http://examplepreview.com', 'kind': 'thumbnails' },
      { 'file': 'test', 'kind': 'captions', 'label': 'Default subtitles' },
    ],
    sources: [{
      'file': 'http://examplehls.com', 'type': 'hls',
    }],
  }];

  const IMAGE_FIXTURE = { media_details: { sizes: {
    '640x360': { source_url: 'path/to/640x360', width: 640, height: 360 },
  } } };

  let wrapper;

  const site = new Site();

  beforeEach(() => {
    jest.spyOn(site, 'objectToResponse');
    mockJwplayer.on.mockClear();
    mockJwplayer.getState.mockClear();
    site.objectToResponse.mockClear();
    window.__webpack_public_path__ = '/test/path/';
    wrapper = shallow(
      <MediaVideo id={ 23 } posterWidth={ 640 } site={ site } />
    );
  });

  const getVideoItem = () => {
    site.objectToResponse.mockReturnValue({
      offline: jest.fn().mockReturnValueOnce(VIDEO_FIXTURE),
    });
    const children = wrapper.find(DefaultItem).prop('children');
    return shallow(<div>{ children(VIDEO_FIXTURE) }</div>);
  };

  const updateVideoFixture = (newVideoFixture) =>
    site.objectToResponse.mockReturnValueOnce({
      offline: jest.fn().mockReturnValueOnce(newVideoFixture),
    });

  const getImageItem = () => {
    site.objectToResponse.mockReturnValue({
      offline: jest.fn().mockReturnValueOnce(IMAGE_FIXTURE),
    });
    const children = getVideoItem().find(DefaultItem).prop('children');
    return shallow(<div>{ children(IMAGE_FIXTURE) }</div>);
  };

  const mockJwplayer = {
    on: jest.fn(),
    getState: jest.fn(),
  };

  const mountPlayer = () =>
    getVideoItem().find('JwPlayer').simulate('playerMounted', mockJwplayer);

  const getJwplayerEventHandlers = () => {
    const eventHandlers = {};

    mockJwplayer.on.mockImplementation((event, handler) => {
      eventHandlers[event] = handler;
    });

    mountPlayer();

    return eventHandlers;
  };

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

  it('renders an Item to retrieve the video media', () => {
    const videoItem = wrapper.find(DefaultItem);

    expect(videoItem).toExist();
    expect(videoItem).toHaveProp('id', 23);
    expect(videoItem).toHaveProp('typeSlug', 'media');
  });

  it('calls offline when video is requested', () => {
    const videoResponse = {
      offline: jest.fn().mockReturnValue(VIDEO_FIXTURE),
    };
    site.objectToResponse.mockReturnValueOnce(videoResponse);

    getVideoItem();

    expect(videoResponse.offline).toHaveBeenCalled();
    expect(videoResponse.offline()).toEqual(VIDEO_FIXTURE);
  });

  it('renders jwplayer', () => {
    expect(getVideoItem().find('JwPlayer')).toExist();
  });

  it('passes aspectratio to jwplayerconfig if provided', () => {
    const newVideoFixture = assoc('playback',
      { aspect_ratio: '4:3' },
      VIDEO_FIXTURE
    );

    updateVideoFixture(newVideoFixture);

    const videoAspectRatio = getVideoItem()
      .find('JwPlayer')
      .prop('jwPlayerConfig')
      .aspectratio;

    expect(videoAspectRatio).toBe('4:3');
  });

  it('passes props to jwplayer with default aspectratio included', () => {
    const jwPlayerConfig = {
      aspectratio: '16:9',
      playlist: PLAYLIST_FIXTURE,
    };

    expect(getVideoItem().find('JwPlayer'))
      .toHaveProp('jwPlayerConfig', jwPlayerConfig);
  });

  it('passes extra props to jwplayer', () => {
    wrapper.setProps({ testProp: 'propValue' });

    expect(getVideoItem().find('JwPlayer')).toHaveProp('testProp', 'propValue');
  });

  it('renders image with posterId if set', () => {
    wrapper.setProps({ posterId: 15 });

    const imageItem = getVideoItem().find(DefaultItem);

    expect(imageItem).toHaveProp('id', 15);
  });

  it('renders an item to retrieve image media if featured_media exist', () => {
    const newVideoFixture = assoc('featured_media', 42, VIDEO_FIXTURE);

    updateVideoFixture(newVideoFixture);

    const imageItem = getVideoItem().find(DefaultItem);

    expect(imageItem).toExist();
    expect(imageItem).toHaveProp('id', 42);
    expect(imageItem).toHaveProp('typeSlug', 'media');
  });

  it('calls imageurl to jwplayer if featured_media exist', () => {
    const newVideoFixture = assoc('featured_media', 42, VIDEO_FIXTURE);

    updateVideoFixture(newVideoFixture);

    const spy = jest.spyOn(wrapper.instance(), 'getImageUrlByWidth');
    getImageItem();

    expect(spy).toHaveBeenCalled();
  });

  it('binds an event handler', () => {
    mountPlayer();
    expect(mockJwplayer.on).toHaveBeenCalled();
  });

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

    mountPlayer();

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

  const jwplayerEvents = [
    { event: 'play' },
    { event: 'pause' },
  ];

  forEach(({ event }) => {
    it(`calls jwplayer ${event} and sends track event info`, () => {
      jest.spyOn(site, 'trackEvent');
      site.trackEvent.mockClear();

      const eventHandlers = getJwplayerEventHandlers();

      expect(eventHandlers).toHaveProperty(event);

      eventHandlers[event]();

      expect(site.trackEvent).toHaveBeenCalled();
      expect(site.trackEvent).toHaveBeenCalledWith(
        site.ANALYTICS_TYPE.MEDIA,
        { 'action': event, 'objectRoute': 'media/23' }
      );
    });
  }, jwplayerEvents);

  it('calls jwplayer complete and sends track event info', () => {
    jest.spyOn(site, 'trackEvent');
    site.trackEvent.mockClear();

    const eventHandlers = getJwplayerEventHandlers();

    expect(eventHandlers).toHaveProperty('complete');

    eventHandlers.complete();

    expect(site.trackEvent).toHaveBeenCalled();
    expect(site.trackEvent).toHaveBeenCalledWith(
      site.ANALYTICS_TYPE.MEDIA,
      { 'action': 'ended', 'objectRoute': 'media/23' }
    );
  });

  const eventTimers = [
    { time: 10 },
    { time: 25 },
    { time: 50 },
    { time: 75 },
    { time: 90 },
  ];

  forEach(({ time }) => {
    it(`calls jwplayer time and sends track event info when it is ${time}%`,
      () => {
        jest.spyOn(site, 'trackEvent');
        site.trackEvent.mockClear();

        const eventHandlers = getJwplayerEventHandlers();

        expect(eventHandlers).toHaveProperty('time');

        eventHandlers.time({ duration: 100, currentTime: time });

        expect(site.trackEvent).toHaveBeenCalled();
        expect(site.trackEvent).toHaveBeenCalledWith(
          site.ANALYTICS_TYPE.MEDIA,
          {
            'action': `${time}%`,
            'objectRoute': 'media/23',
            interactive: false,
          }
        );
      });
  }, eventTimers);

  it('does not send track events if seeking event is taking place', () => {
    jest.spyOn(site, 'trackEvent');
    site.trackEvent.mockClear();

    const eventHandlers = getJwplayerEventHandlers();

    expect(eventHandlers).toHaveProperty('time');

    eventHandlers.seek();
    eventHandlers.time({ duration: 100, currentTime: 90 });

    expect(site.trackEvent).not.toHaveBeenCalled();
  });

  it('sends tracking event after seek completes', () => {
    jest.spyOn(site, 'trackEvent');
    site.trackEvent.mockClear();

    const eventHandlers = getJwplayerEventHandlers();

    expect(eventHandlers).toHaveProperty('time');

    eventHandlers.seek();
    eventHandlers.seeked();
    eventHandlers.time({ duration: 100, currentTime: 90 });

    expect(site.trackEvent).toHaveBeenCalledWith(
      site.ANALYTICS_TYPE.MEDIA,
      { 'action': '90%', 'objectRoute': 'media/23', interactive: false }
    );
  });
});
