import expect from 'expect';
import { shallow, ShallowWrapper } from 'enzyme';
import { spy, SinonSpy } from 'sinon';
import { EventHandler, MouseEvent } from 'react';
import { Link } from 'react-router-dom';

import { UITrackingPayload } from 'mweb/common/actions/tracking';
import { TrackClickHandler } from 'mweb/common/tracking/withTracking';
import { TrackedLink } from 'mweb/common/tracking/trackedLink';

interface ClickTrackingTestCase {
  title: string;
  expectedPayload: ObjectOmit<UITrackingPayload, 'interactionMedium'>;
  clickTargetBuilder: (trackClick?: TrackClickHandler) => ShallowWrapper;
  // the relative position of this element to other trackables in the render flow
  clickTargetTrackingIndex?: number;
  simulatedEvent?: string;
}

interface ClickTrackingFormattedMessageTestCase {
  title: string;
  key: string;
  interactionContent: string;
  formattedMessage: ShallowWrapper;
}

export function testClickTracking(
  cases: ClickTrackingTestCase | ClickTrackingTestCase[],
): void {
  const interactionMedium = 'test';
  const testCases = Array.isArray(cases) ? cases : [cases];
  describe('click tracking', () => {
    testCases.forEach(
      ({
        title,
        expectedPayload,
        clickTargetBuilder,
        clickTargetTrackingIndex = 0,
        simulatedEvent = 'click',
      }) => {
        it(title, () => {
          const clickHandlers: SinonSpy[] = [];
          const payloads: UITrackingPayload[] = [];

          const trackClick: TrackClickHandler = (
            payload: UITrackingPayload,
          ) => {
            payloads.push(payload);
            const handler = spy();
            clickHandlers.push(handler);
            return handler;
          };

          const target = clickTargetBuilder(trackClick);

          if (target.is(TrackedLink)) {
            // going through the HOC-wrapped TrackedLink
            target
              .shallow({
                context: {
                  trackingTrackClick: trackClick,
                  interactionMedium,
                },
              })
              .shallow()
              .find(Link)
              .simulate(simulatedEvent);

            // have to shift the index because this shallow render happens last
            const effectiveIndex = payloads.length - 1;
            const trackedLinkPayload = payloads[effectiveIndex];

            // the presence of this payload indicates that the link was clicked
            // medium is appended to the payload in the HOC
            expect(trackedLinkPayload).toEqual({
              ...expectedPayload,
              interactionMedium,
            });
          } else {
            // going through a manually built click tracker
            expect(payloads[clickTargetTrackingIndex]).toEqual(expectedPayload);
            expect(clickHandlers[clickTargetTrackingIndex].called).toEqual(
              false,
              'clickHandler activated before click',
            );
            target.simulate(simulatedEvent);
            expect(clickHandlers[clickTargetTrackingIndex].called).toEqual(
              true,
              'click did not activate clickHandler',
            );
          }
        });
      },
    );
  });
}

export function trackClickMock(): TrackClickHandler {
  return (_p: UITrackingPayload, callback?: EventHandler<MouseEvent<any>>) =>
    callback || (() => undefined);
}

/**
Specifically added to handle the case when you have a TrackedLink inside of a FormattedMessage:
  <FormattedMessage
    values={{
      key: (<TrackedLink />)
    }}
  </FormattedMessage>

  We can't use testClickTracking in this case because of FormattedMessage's
  reliance on there being an IntlProvider in the hierarchy. You also can't rely
  on putting intl into context because of how Enzyme handles context (shallow
  renders below the root do not retain context).
*/
export function testClickTrackingInsideFormattedMessage(
  cases:
    | ClickTrackingFormattedMessageTestCase
    | ClickTrackingFormattedMessageTestCase[],
): void {
  describe('click tracking inside a formatted message', () => {
    const testCases = Array.isArray(cases) ? cases : [cases];
    testCases.forEach(
      ({ title, key, interactionContent, formattedMessage }) => {
        it(title, () => {
          const link = shallow((formattedMessage.prop('values') as any)[key]);
          expect(link.prop('interactionContent')).toEqual(interactionContent);
        });
      },
    );
  });
}
