import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import { TEST_HOST } from 'helpers/test_constants';
import DiffTableCell from '~/diffs/components/diff_table_cell.vue';
import DiffGutterAvatars from '~/diffs/components/diff_gutter_avatars.vue';
import { LINE_POSITION_RIGHT } from '~/diffs/constants';
import { createStore } from '~/mr_notes/stores';
import discussionsMockData from '../mock_data/diff_discussions';
import diffFileMockData from '../mock_data/diff_file';

const localVue = createLocalVue();
localVue.use(Vuex);

const TEST_USER_ID = 'abc123';
const TEST_USER = { id: TEST_USER_ID };
const TEST_LINE_NUMBER = 1;
const TEST_LINE_CODE = 'LC_42';
const TEST_FILE_HASH = diffFileMockData.file_hash;

describe('DiffTableCell', () => {
  const symlinkishFileTooltip =
    'Commenting on symbolic links that replace or are replaced by files is currently not supported.';
  const realishFileTooltip =
    'Commenting on files that replace or are replaced by symbolic links is currently not supported.';
  const otherFileTooltip = 'Add a comment to this line';

  let wrapper;
  let line;
  let store;

  beforeEach(() => {
    store = createStore();
    store.state.notes.userData = TEST_USER;

    line = {
      line_code: TEST_LINE_CODE,
      type: 'new',
      old_line: null,
      new_line: 1,
      discussions: [{ ...discussionsMockData }],
      discussionsExpanded: true,
      text: '+<span id="LC1" class="line" lang="plaintext">  - Bad dates</span>\n',
      rich_text: '+<span id="LC1" class="line" lang="plaintext">  - Bad dates</span>\n',
      meta_data: null,
    };
  });

  afterEach(() => {
    wrapper.destroy();
  });

  const setWindowLocation = value => {
    Object.defineProperty(window, 'location', {
      writable: true,
      value,
    });
  };

  const createComponent = (props = {}) => {
    wrapper = shallowMount(DiffTableCell, {
      localVue,
      store,
      propsData: {
        line,
        fileHash: TEST_FILE_HASH,
        contextLinesPath: '/context/lines/path',
        isHighlighted: false,
        ...props,
      },
    });
  };

  const findTd = () => wrapper.find({ ref: 'td' });
  const findNoteButton = () => wrapper.find({ ref: 'addDiffNoteButton' });
  const findLineNumber = () => wrapper.find({ ref: 'lineNumberRef' });
  const findTooltip = () => wrapper.find({ ref: 'addNoteTooltip' });
  const findAvatars = () => wrapper.find(DiffGutterAvatars);

  describe('td', () => {
    it('highlights when isHighlighted true', () => {
      createComponent({ isHighlighted: true });

      expect(findTd().classes()).toContain('hll');
    });

    it('does not highlight when isHighlighted false', () => {
      createComponent({ isHighlighted: false });

      expect(findTd().classes()).not.toContain('hll');
    });
  });

  describe('comment button', () => {
    it.each`
      showCommentButton | userData     | query                | mergeRefHeadComments | expectation
      ${true}           | ${TEST_USER} | ${'diff_head=false'} | ${false}             | ${true}
      ${true}           | ${TEST_USER} | ${'diff_head=true'}  | ${true}              | ${true}
      ${true}           | ${TEST_USER} | ${'diff_head=true'}  | ${false}             | ${false}
      ${false}          | ${TEST_USER} | ${'diff_head=true'}  | ${true}              | ${false}
      ${false}          | ${TEST_USER} | ${'bogus'}           | ${true}              | ${false}
      ${true}           | ${null}      | ${''}                | ${true}              | ${false}
    `(
      'exists is $expectation - with showCommentButton ($showCommentButton) userData ($userData) query ($query)',
      ({ showCommentButton, userData, query, mergeRefHeadComments, expectation }) => {
        store.state.notes.userData = userData;
        gon.features = { mergeRefHeadComments };
        setWindowLocation({ href: `${TEST_HOST}?${query}` });
        createComponent({ showCommentButton });

        wrapper.setData({ isCommentButtonRendered: showCommentButton });

        return wrapper.vm.$nextTick().then(() => {
          expect(findNoteButton().exists()).toBe(expectation);
        });
      },
    );

    it.each`
      isHover  | otherProps                                      | discussions | expectation
      ${true}  | ${{}}                                           | ${[]}       | ${true}
      ${false} | ${{}}                                           | ${[]}       | ${false}
      ${true}  | ${{ line: { ...line, type: 'context' } }}       | ${[]}       | ${false}
      ${true}  | ${{ line: { ...line, type: 'old-nonewline' } }} | ${[]}       | ${false}
      ${true}  | ${{}}                                           | ${[{}]}     | ${false}
    `(
      'visible is $expectation - with isHover ($isHover), discussions ($discussions), otherProps ($otherProps)',
      ({ isHover, otherProps, discussions, expectation }) => {
        line.discussions = discussions;
        createComponent({
          showCommentButton: true,
          isHover,
          ...otherProps,
        });

        wrapper.setData({
          isCommentButtonRendered: true,
        });

        return wrapper.vm.$nextTick().then(() => {
          expect(findNoteButton().isVisible()).toBe(expectation);
        });
      },
    );

    it.each`
      disabled      | commentsDisabled
      ${'disabled'} | ${true}
      ${undefined}  | ${false}
    `(
      'has attribute disabled=$disabled when the outer component has prop commentsDisabled=$commentsDisabled',
      ({ disabled, commentsDisabled }) => {
        line.commentsDisabled = commentsDisabled;

        createComponent({
          showCommentButton: true,
          isHover: true,
        });

        wrapper.setData({ isCommentButtonRendered: true });

        return wrapper.vm.$nextTick().then(() => {
          expect(findNoteButton().attributes('disabled')).toBe(disabled);
        });
      },
    );

    it.each`
      tooltip                  | commentsDisabled
      ${symlinkishFileTooltip} | ${{ wasSymbolic: true }}
      ${symlinkishFileTooltip} | ${{ isSymbolic: true }}
      ${realishFileTooltip}    | ${{ wasReal: true }}
      ${realishFileTooltip}    | ${{ isReal: true }}
      ${otherFileTooltip}      | ${false}
    `(
      'has the correct tooltip when commentsDisabled=$commentsDisabled',
      ({ tooltip, commentsDisabled }) => {
        line.commentsDisabled = commentsDisabled;

        createComponent({
          showCommentButton: true,
          isHover: true,
        });

        wrapper.setData({ isCommentButtonRendered: true });

        return wrapper.vm.$nextTick().then(() => {
          expect(findTooltip().attributes('title')).toBe(tooltip);
        });
      },
    );
  });

  describe('line number', () => {
    describe('without lineNumber prop', () => {
      it('does not render', () => {
        createComponent({ lineType: 'old' });

        expect(findLineNumber().exists()).toBe(false);
      });
    });

    describe('with lineNumber prop', () => {
      describe.each`
        lineProps                                                         | expectedHref            | expectedClickArg
        ${{ line_code: TEST_LINE_CODE }}                                  | ${`#${TEST_LINE_CODE}`} | ${TEST_LINE_CODE}
        ${{ line_code: undefined }}                                       | ${'#'}                  | ${undefined}
        ${{ line_code: undefined, left: { line_code: TEST_LINE_CODE } }}  | ${'#'}                  | ${TEST_LINE_CODE}
        ${{ line_code: undefined, right: { line_code: TEST_LINE_CODE } }} | ${'#'}                  | ${TEST_LINE_CODE}
      `('with line ($lineProps)', ({ lineProps, expectedHref, expectedClickArg }) => {
        beforeEach(() => {
          jest.spyOn(store, 'dispatch').mockImplementation();
          Object.assign(line, lineProps);
          createComponent({ lineNumber: TEST_LINE_NUMBER });
        });

        it('renders', () => {
          expect(findLineNumber().exists()).toBe(true);
          expect(findLineNumber().attributes()).toEqual({
            href: expectedHref,
            'data-linenumber': TEST_LINE_NUMBER.toString(),
          });
        });

        it('on click, dispatches setHighlightedRow', () => {
          expect(store.dispatch).not.toHaveBeenCalled();

          findLineNumber().trigger('click');

          expect(store.dispatch).toHaveBeenCalledWith('diffs/setHighlightedRow', expectedClickArg);
        });
      });
    });
  });

  describe('diff-gutter-avatars', () => {
    describe('with showCommentButton', () => {
      beforeEach(() => {
        jest.spyOn(store, 'dispatch').mockImplementation();

        createComponent({ showCommentButton: true });
      });

      it('renders', () => {
        expect(findAvatars().props()).toEqual({
          discussions: line.discussions,
          discussionsExpanded: line.discussionsExpanded,
        });
      });

      it('toggles line discussion', () => {
        expect(store.dispatch).not.toHaveBeenCalled();

        findAvatars().vm.$emit('toggleLineDiscussions');

        expect(store.dispatch).toHaveBeenCalledWith('diffs/toggleLineDiscussions', {
          lineCode: TEST_LINE_CODE,
          fileHash: TEST_FILE_HASH,
          expanded: !line.discussionsExpanded,
        });
      });
    });

    it.each`
      props                                                             | lineProps              | expectation
      ${{ showCommentButton: true }}                                    | ${{}}                  | ${true}
      ${{ showCommentButton: false }}                                   | ${{}}                  | ${false}
      ${{ showCommentButton: true, linePosition: LINE_POSITION_RIGHT }} | ${{ type: null }}      | ${false}
      ${{ showCommentButton: true }}                                    | ${{ discussions: [] }} | ${false}
    `(
      'exists is $expectation - with props ($props), line ($lineProps)',
      ({ props, lineProps, expectation }) => {
        Object.assign(line, lineProps);
        createComponent(props);

        expect(findAvatars().exists()).toBe(expectation);
      },
    );
  });
});