import { mount } from '@vue/test-utils';
import { GlAlert, GlLoadingIcon, GlTable, GlAvatar, GlEmptyState } from '@gitlab/ui';
import Tracking from '~/tracking';
import { visitUrl, joinPaths, mergeUrlParams } from '~/lib/utils/url_utility';
import IncidentsList from '~/incidents/components/incidents_list.vue';
import SeverityToken from '~/sidebar/components/severity/severity.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import {
  I18N,
  TH_CREATED_AT_TEST_ID,
  TH_SEVERITY_TEST_ID,
  TH_PUBLISHED_TEST_ID,
  TH_INCIDENT_SLA_TEST_ID,
  trackIncidentCreateNewOptions,
  trackIncidentListViewsOptions,
} from '~/incidents/constants';
import mockIncidents from '../mocks/incidents.json';

jest.mock('~/lib/utils/url_utility', () => ({
  visitUrl: jest.fn().mockName('visitUrlMock'),
  joinPaths: jest.fn(),
  mergeUrlParams: jest.fn(),
  setUrlParams: jest.fn(),
  updateHistory: jest.fn(),
}));
jest.mock('~/tracking');

describe('Incidents List', () => {
  let wrapper;
  const newIssuePath = 'namespace/project/-/issues/new';
  const emptyListSvgPath = '/assets/empty.svg';
  const incidentTemplateName = 'incident';
  const incidentType = 'incident';
  const incidentsCount = {
    opened: 24,
    closed: 10,
    all: 26,
  };

  const findTable = () => wrapper.find(GlTable);
  const findTableRows = () => wrapper.findAll('table tbody tr');
  const findAlert = () => wrapper.find(GlAlert);
  const findLoader = () => wrapper.find(GlLoadingIcon);
  const findTimeAgo = () => wrapper.findAll(TimeAgoTooltip);
  const findAssignees = () => wrapper.findAll('[data-testid="incident-assignees"]');
  const findIncidentSlaHeader = () => wrapper.find('[data-testid="incident-management-sla"]');
  const findCreateIncidentBtn = () => wrapper.find('[data-testid="createIncidentBtn"]');
  const findClosedIcon = () => wrapper.findAll("[data-testid='incident-closed']");
  const findEmptyState = () => wrapper.find(GlEmptyState);
  const findSeverity = () => wrapper.findAll(SeverityToken);
  const findIncidentSla = () => wrapper.findAll("[data-testid='incident-sla']");

  function mountComponent({ data = {}, loading = false, provide = {} } = {}) {
    wrapper = mount(IncidentsList, {
      data() {
        return {
          incidents: [],
          incidentsCount: {},
          ...data,
        };
      },
      mocks: {
        $apollo: {
          queries: {
            incidents: {
              loading,
            },
          },
        },
      },
      provide: {
        projectPath: '/project/path',
        newIssuePath,
        incidentTemplateName,
        incidentType,
        issuePath: '/project/issues',
        publishedAvailable: true,
        emptyListSvgPath,
        textQuery: '',
        authorUsernameQuery: '',
        assigneeUsernameQuery: '',
        slaFeatureAvailable: true,
        ...provide,
      },
      stubs: {
        GlButton: true,
        GlAvatar: true,
        GlEmptyState: true,
        ServiceLevelAgreementCell: true,
      },
    });
  }

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

  it('shows the loading state', () => {
    mountComponent({
      loading: true,
    });
    expect(findLoader().exists()).toBe(true);
  });

  describe('empty state', () => {
    const {
      emptyState: { title, emptyClosedTabTitle, description },
    } = I18N;

    it.each`
      statusFilter | all  | closed | expectedTitle          | expectedDescription
      ${'all'}     | ${2} | ${1}   | ${title}               | ${description}
      ${'open'}    | ${2} | ${0}   | ${title}               | ${description}
      ${'closed'}  | ${0} | ${0}   | ${title}               | ${description}
      ${'closed'}  | ${2} | ${0}   | ${emptyClosedTabTitle} | ${undefined}
    `(
      `when active tab is $statusFilter and there are $all incidents in total and $closed closed incidents, the empty state
      has title: $expectedTitle and description: $expectedDescription`,
      ({ statusFilter, all, closed, expectedTitle, expectedDescription }) => {
        mountComponent({
          data: { incidents: { list: [] }, incidentsCount: { all, closed }, statusFilter },
          loading: false,
        });
        expect(findEmptyState().exists()).toBe(true);
        expect(findEmptyState().attributes('title')).toBe(expectedTitle);
        expect(findEmptyState().attributes('description')).toBe(expectedDescription);
      },
    );
  });

  it('shows error state', () => {
    mountComponent({
      data: { incidents: { list: [] }, incidentsCount: { all: 0 }, errored: true },
      loading: false,
    });
    expect(findTable().text()).toContain(I18N.noIncidents);
    expect(findAlert().exists()).toBe(true);
  });

  describe('Incident Management list', () => {
    beforeEach(() => {
      mountComponent({
        data: { incidents: { list: mockIncidents }, incidentsCount },
        loading: false,
      });
    });

    it('renders rows based on provided data', () => {
      expect(findTableRows().length).toBe(mockIncidents.length);
    });

    it('renders a createdAt with timeAgo component per row', () => {
      expect(findTimeAgo().length).toBe(mockIncidents.length);
    });

    describe('Assignees', () => {
      it('shows Unassigned when there are no assignees', () => {
        expect(
          findAssignees()
            .at(0)
            .text(),
        ).toBe(I18N.unassigned);
      });

      it('renders an avatar component when there is an assignee', () => {
        const avatar = findAssignees()
          .at(1)
          .find(GlAvatar);
        const { src, label } = avatar.attributes();
        const { name, avatarUrl } = mockIncidents[1].assignees.nodes[0];

        expect(avatar.exists()).toBe(true);
        expect(label).toBe(name);
        expect(src).toBe(avatarUrl);
      });

      it('renders a closed icon for closed incidents', () => {
        expect(findClosedIcon().length).toBe(
          mockIncidents.filter(({ state }) => state === 'closed').length,
        );
      });
    });

    it('renders severity per row', () => {
      expect(findSeverity().length).toBe(mockIncidents.length);
    });

    it('contains a link to the incident details page', async () => {
      findTableRows()
        .at(0)
        .trigger('click');
      expect(visitUrl).toHaveBeenCalledWith(
        joinPaths(`/project/issues/incident`, mockIncidents[0].iid),
      );
    });

    describe('Incident SLA field', () => {
      it('displays the column when the feature is available', () => {
        mountComponent({
          data: { incidents: { list: mockIncidents } },
          provide: { slaFeatureAvailable: true },
        });

        expect(findIncidentSlaHeader().text()).toContain('Time to SLA');
      });

      it('does not display the column when the feature is not available', () => {
        mountComponent({
          data: { incidents: { list: mockIncidents } },
          provide: { slaFeatureAvailable: false },
        });

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

      it('renders an SLA for each incident', () => {
        mountComponent({
          data: { incidents: { list: mockIncidents } },
          provide: { slaFeatureAvailable: true },
        });

        expect(findIncidentSla().length).toBe(mockIncidents.length);
      });
    });
  });

  describe('Create Incident', () => {
    beforeEach(() => {
      mountComponent({
        data: { incidents: { list: mockIncidents }, incidentsCount: {} },
        loading: false,
      });
    });

    it('shows the button linking to new incidents page with pre-filled incident template when clicked', () => {
      expect(findCreateIncidentBtn().exists()).toBe(true);
      findCreateIncidentBtn().trigger('click');
      expect(mergeUrlParams).toHaveBeenCalledWith(
        { issuable_template: incidentTemplateName, 'issue[issue_type]': incidentType },
        newIssuePath,
      );
    });

    it('sets button loading on click', async () => {
      findCreateIncidentBtn().vm.$emit('click');
      await wrapper.vm.$nextTick();
      expect(findCreateIncidentBtn().attributes('loading')).toBe('true');
    });

    it("doesn't show the button when list is empty", () => {
      mountComponent({
        data: { incidents: { list: [] }, incidentsCount: {} },
        loading: false,
      });
      expect(findCreateIncidentBtn().exists()).toBe(false);
    });

    it('should track create new incident button', async () => {
      findCreateIncidentBtn().vm.$emit('click');
      await wrapper.vm.$nextTick();
      expect(Tracking.event).toHaveBeenCalled();
    });
  });

  describe('sorting the incident list by column', () => {
    beforeEach(() => {
      mountComponent({
        data: { incidents: { list: mockIncidents }, incidentsCount },
        loading: false,
      });
    });

    const descSort = 'descending';
    const ascSort = 'ascending';
    const noneSort = 'none';

    it.each`
      selector                   | initialSort | firstSort   | nextSort
      ${TH_CREATED_AT_TEST_ID}   | ${descSort} | ${ascSort}  | ${descSort}
      ${TH_SEVERITY_TEST_ID}     | ${noneSort} | ${descSort} | ${ascSort}
      ${TH_PUBLISHED_TEST_ID}    | ${noneSort} | ${descSort} | ${ascSort}
      ${TH_INCIDENT_SLA_TEST_ID} | ${noneSort} | ${ascSort}  | ${descSort}
    `('updates sort with new direction', async ({ selector, initialSort, firstSort, nextSort }) => {
      const [[attr, value]] = Object.entries(selector);
      const columnHeader = () => wrapper.find(`[${attr}="${value}"]`);
      expect(columnHeader().attributes('aria-sort')).toBe(initialSort);
      columnHeader().trigger('click');
      await wrapper.vm.$nextTick();
      expect(columnHeader().attributes('aria-sort')).toBe(firstSort);
      columnHeader().trigger('click');
      await wrapper.vm.$nextTick();
      expect(columnHeader().attributes('aria-sort')).toBe(nextSort);
    });
  });

  describe('Snowplow tracking', () => {
    beforeEach(() => {
      mountComponent({
        data: { incidents: { list: mockIncidents }, incidentsCount: {} },
        loading: false,
      });
    });

    it('should track incident list views', () => {
      const { category, action } = trackIncidentListViewsOptions;
      expect(Tracking.event).toHaveBeenCalledWith(category, action);
    });

    it('should track incident creation events', async () => {
      findCreateIncidentBtn().vm.$emit('click');
      await wrapper.vm.$nextTick();
      const { category, action } = trackIncidentCreateNewOptions;
      expect(Tracking.event).toHaveBeenCalledWith(category, action);
    });
  });
});
