import BoardService from '~/boards/services/board_service';
import { TEST_HOST } from 'helpers/test_constants';
import AxiosMockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import boardsStore from '~/boards/stores/boards_store';

describe('BoardService', () => {
  const dummyResponse = "without type checking this doesn't matter";
  const boardId = 'dummy-board-id';
  const endpoints = {
    boardsEndpoint: `${TEST_HOST}/boards`,
    listsEndpoint: `${TEST_HOST}/lists`,
    bulkUpdatePath: `${TEST_HOST}/bulk/update`,
    recentBoardsEndpoint: `${TEST_HOST}/recent/boards`,
  };

  let service;
  let axiosMock;

  beforeEach(() => {
    axiosMock = new AxiosMockAdapter(axios);
    boardsStore.setEndpoints({
      ...endpoints,
      boardId,
    });
    service = new BoardService();
  });

  describe('all', () => {
    it('makes a request to fetch lists', () => {
      axiosMock.onGet(endpoints.listsEndpoint).replyOnce(200, dummyResponse);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.all()).resolves.toEqual(expectedResponse);
    });

    it('fails for error response', () => {
      axiosMock.onGet(endpoints.listsEndpoint).replyOnce(500);

      return expect(service.all()).rejects.toThrow();
    });
  });

  describe('generateDefaultLists', () => {
    const listsEndpointGenerate = `${endpoints.listsEndpoint}/generate.json`;

    it('makes a request to generate default lists', () => {
      axiosMock.onPost(listsEndpointGenerate).replyOnce(200, dummyResponse);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.generateDefaultLists()).resolves.toEqual(expectedResponse);
    });

    it('fails for error response', () => {
      axiosMock.onPost(listsEndpointGenerate).replyOnce(500);

      return expect(service.generateDefaultLists()).rejects.toThrow();
    });
  });

  describe('createList', () => {
    const entityType = 'moorhen';
    const entityId = 'quack';
    const expectedRequest = expect.objectContaining({
      data: JSON.stringify({ list: { [entityType]: entityId } }),
    });

    let requestSpy;

    beforeEach(() => {
      requestSpy = jest.fn();
      axiosMock.onPost(endpoints.listsEndpoint).replyOnce(config => requestSpy(config));
    });

    it('makes a request to create a list', () => {
      requestSpy.mockReturnValue([200, dummyResponse]);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.createList(entityId, entityType))
        .resolves.toEqual(expectedResponse)
        .then(() => {
          expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
        });
    });

    it('fails for error response', () => {
      requestSpy.mockReturnValue([500]);

      return expect(service.createList(entityId, entityType))
        .rejects.toThrow()
        .then(() => {
          expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
        });
    });
  });

  describe('updateList', () => {
    const id = 'David Webb';
    const position = 'unknown';
    const expectedRequest = expect.objectContaining({
      data: JSON.stringify({ list: { position } }),
    });

    let requestSpy;

    beforeEach(() => {
      requestSpy = jest.fn();
      axiosMock.onPut(`${endpoints.listsEndpoint}/${id}`).replyOnce(config => requestSpy(config));
    });

    it('makes a request to update a list position', () => {
      requestSpy.mockReturnValue([200, dummyResponse]);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.updateList(id, position))
        .resolves.toEqual(expectedResponse)
        .then(() => {
          expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
        });
    });

    it('fails for error response', () => {
      requestSpy.mockReturnValue([500]);

      return expect(service.updateList(id, position))
        .rejects.toThrow()
        .then(() => {
          expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
        });
    });
  });

  describe('destroyList', () => {
    const id = '-42';

    let requestSpy;

    beforeEach(() => {
      requestSpy = jest.fn();
      axiosMock
        .onDelete(`${endpoints.listsEndpoint}/${id}`)
        .replyOnce(config => requestSpy(config));
    });

    it('makes a request to delete a list', () => {
      requestSpy.mockReturnValue([200, dummyResponse]);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.destroyList(id))
        .resolves.toEqual(expectedResponse)
        .then(() => {
          expect(requestSpy).toHaveBeenCalled();
        });
    });

    it('fails for error response', () => {
      requestSpy.mockReturnValue([500]);

      return expect(service.destroyList(id))
        .rejects.toThrow()
        .then(() => {
          expect(requestSpy).toHaveBeenCalled();
        });
    });
  });

  describe('getIssuesForList', () => {
    const id = 'TOO-MUCH';
    const url = `${endpoints.listsEndpoint}/${id}/issues?id=${id}`;

    it('makes a request to fetch list issues', () => {
      axiosMock.onGet(url).replyOnce(200, dummyResponse);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.getIssuesForList(id)).resolves.toEqual(expectedResponse);
    });

    it('makes a request to fetch list issues with filter', () => {
      const filter = { algal: 'scrubber' };
      axiosMock.onGet(`${url}&algal=scrubber`).replyOnce(200, dummyResponse);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.getIssuesForList(id, filter)).resolves.toEqual(expectedResponse);
    });

    it('fails for error response', () => {
      axiosMock.onGet(url).replyOnce(500);

      return expect(service.getIssuesForList(id)).rejects.toThrow();
    });
  });

  describe('moveIssue', () => {
    const urlRoot = 'potato';
    const id = 'over 9000';
    const fromListId = 'left';
    const toListId = 'right';
    const moveBeforeId = 'up';
    const moveAfterId = 'down';
    const expectedRequest = expect.objectContaining({
      data: JSON.stringify({
        from_list_id: fromListId,
        to_list_id: toListId,
        move_before_id: moveBeforeId,
        move_after_id: moveAfterId,
      }),
    });

    let requestSpy;

    beforeAll(() => {
      global.gon.relative_url_root = urlRoot;
    });

    afterAll(() => {
      delete global.gon.relative_url_root;
    });

    beforeEach(() => {
      requestSpy = jest.fn();
      axiosMock
        .onPut(`${urlRoot}/-/boards/${boardId}/issues/${id}`)
        .replyOnce(config => requestSpy(config));
    });

    it('makes a request to move an issue between lists', () => {
      requestSpy.mockReturnValue([200, dummyResponse]);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.moveIssue(id, fromListId, toListId, moveBeforeId, moveAfterId))
        .resolves.toEqual(expectedResponse)
        .then(() => {
          expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
        });
    });

    it('fails for error response', () => {
      requestSpy.mockReturnValue([500]);

      return expect(service.moveIssue(id, fromListId, toListId, moveBeforeId, moveAfterId))
        .rejects.toThrow()
        .then(() => {
          expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
        });
    });
  });

  describe('newIssue', () => {
    const id = 'not-creative';
    const issue = { some: 'issue data' };
    const url = `${endpoints.listsEndpoint}/${id}/issues`;
    const expectedRequest = expect.objectContaining({
      data: JSON.stringify({
        issue,
      }),
    });

    let requestSpy;

    beforeEach(() => {
      requestSpy = jest.fn();
      axiosMock.onPost(url).replyOnce(config => requestSpy(config));
    });

    it('makes a request to create a new issue', () => {
      requestSpy.mockReturnValue([200, dummyResponse]);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.newIssue(id, issue))
        .resolves.toEqual(expectedResponse)
        .then(() => {
          expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
        });
    });

    it('fails for error response', () => {
      requestSpy.mockReturnValue([500]);

      return expect(service.newIssue(id, issue))
        .rejects.toThrow()
        .then(() => {
          expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
        });
    });
  });

  describe('getBacklog', () => {
    const urlRoot = 'deep';
    const url = `${urlRoot}/-/boards/${boardId}/issues.json?not=relevant`;
    const requestParams = {
      not: 'relevant',
    };

    beforeAll(() => {
      global.gon.relative_url_root = urlRoot;
    });

    afterAll(() => {
      delete global.gon.relative_url_root;
    });

    it('makes a request to fetch backlog', () => {
      axiosMock.onGet(url).replyOnce(200, dummyResponse);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.getBacklog(requestParams)).resolves.toEqual(expectedResponse);
    });

    it('fails for error response', () => {
      axiosMock.onGet(url).replyOnce(500);

      return expect(service.getBacklog(requestParams)).rejects.toThrow();
    });
  });

  describe('bulkUpdate', () => {
    const issueIds = [1, 2, 3];
    const extraData = { moar: 'data' };
    const expectedRequest = expect.objectContaining({
      data: JSON.stringify({
        update: {
          ...extraData,
          issuable_ids: '1,2,3',
        },
      }),
    });

    let requestSpy;

    beforeEach(() => {
      requestSpy = jest.fn();
      axiosMock.onPost(endpoints.bulkUpdatePath).replyOnce(config => requestSpy(config));
    });

    it('makes a request to create a list', () => {
      requestSpy.mockReturnValue([200, dummyResponse]);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.bulkUpdate(issueIds, extraData))
        .resolves.toEqual(expectedResponse)
        .then(() => {
          expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
        });
    });

    it('fails for error response', () => {
      requestSpy.mockReturnValue([500]);

      return expect(service.bulkUpdate(issueIds, extraData))
        .rejects.toThrow()
        .then(() => {
          expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
        });
    });
  });

  describe('getIssueInfo', () => {
    const dummyEndpoint = `${TEST_HOST}/some/where`;

    it('makes a request to the given endpoint', () => {
      axiosMock.onGet(dummyEndpoint).replyOnce(200, dummyResponse);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(BoardService.getIssueInfo(dummyEndpoint)).resolves.toEqual(expectedResponse);
    });

    it('fails for error response', () => {
      axiosMock.onGet(dummyEndpoint).replyOnce(500);

      return expect(BoardService.getIssueInfo(dummyEndpoint)).rejects.toThrow();
    });
  });

  describe('toggleIssueSubscription', () => {
    const dummyEndpoint = `${TEST_HOST}/some/where`;

    it('makes a request to the given endpoint', () => {
      axiosMock.onPost(dummyEndpoint).replyOnce(200, dummyResponse);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(BoardService.toggleIssueSubscription(dummyEndpoint)).resolves.toEqual(
        expectedResponse,
      );
    });

    it('fails for error response', () => {
      axiosMock.onPost(dummyEndpoint).replyOnce(500);

      return expect(BoardService.toggleIssueSubscription(dummyEndpoint)).rejects.toThrow();
    });
  });

  describe('allBoards', () => {
    const url = `${endpoints.boardsEndpoint}.json`;

    it('makes a request to fetch all boards', () => {
      axiosMock.onGet(url).replyOnce(200, dummyResponse);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.allBoards()).resolves.toEqual(expectedResponse);
    });

    it('fails for error response', () => {
      axiosMock.onGet(url).replyOnce(500);

      return expect(service.allBoards()).rejects.toThrow();
    });
  });

  describe('recentBoards', () => {
    const url = `${endpoints.recentBoardsEndpoint}.json`;

    it('makes a request to fetch all boards', () => {
      axiosMock.onGet(url).replyOnce(200, dummyResponse);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.recentBoards()).resolves.toEqual(expectedResponse);
    });

    it('fails for error response', () => {
      axiosMock.onGet(url).replyOnce(500);

      return expect(service.recentBoards()).rejects.toThrow();
    });
  });

  describe('createBoard', () => {
    const labelIds = ['first label', 'second label'];
    const assigneeId = 'as sign ee';
    const milestoneId = 'vegetable soup';
    const board = {
      labels: labelIds.map(id => ({ id })),
      assignee: { id: assigneeId },
      milestone: { id: milestoneId },
    };

    describe('for existing board', () => {
      const id = 'skate-board';
      const url = `${endpoints.boardsEndpoint}/${id}.json`;
      const expectedRequest = expect.objectContaining({
        data: JSON.stringify({
          board: {
            ...board,
            id,
            label_ids: labelIds,
            assignee_id: assigneeId,
            milestone_id: milestoneId,
          },
        }),
      });

      let requestSpy;

      beforeEach(() => {
        requestSpy = jest.fn();
        axiosMock.onPut(url).replyOnce(config => requestSpy(config));
      });

      it('makes a request to update the board', () => {
        requestSpy.mockReturnValue([200, dummyResponse]);
        const expectedResponse = expect.objectContaining({ data: dummyResponse });

        return expect(
          service.createBoard({
            ...board,
            id,
          }),
        )
          .resolves.toEqual(expectedResponse)
          .then(() => {
            expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
          });
      });

      it('fails for error response', () => {
        requestSpy.mockReturnValue([500]);

        return expect(
          service.createBoard({
            ...board,
            id,
          }),
        )
          .rejects.toThrow()
          .then(() => {
            expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
          });
      });
    });

    describe('for new board', () => {
      const url = `${endpoints.boardsEndpoint}.json`;
      const expectedRequest = expect.objectContaining({
        data: JSON.stringify({
          board: {
            ...board,
            label_ids: labelIds,
            assignee_id: assigneeId,
            milestone_id: milestoneId,
          },
        }),
      });

      let requestSpy;

      beforeEach(() => {
        requestSpy = jest.fn();
        axiosMock.onPost(url).replyOnce(config => requestSpy(config));
      });

      it('makes a request to create a new board', () => {
        requestSpy.mockReturnValue([200, dummyResponse]);
        const expectedResponse = expect.objectContaining({ data: dummyResponse });

        return expect(service.createBoard(board))
          .resolves.toEqual(expectedResponse)
          .then(() => {
            expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
          });
      });

      it('fails for error response', () => {
        requestSpy.mockReturnValue([500]);

        return expect(service.createBoard(board))
          .rejects.toThrow()
          .then(() => {
            expect(requestSpy).toHaveBeenCalledWith(expectedRequest);
          });
      });
    });
  });

  describe('deleteBoard', () => {
    const id = 'capsized';
    const url = `${endpoints.boardsEndpoint}/${id}.json`;

    it('makes a request to delete a boards', () => {
      axiosMock.onDelete(url).replyOnce(200, dummyResponse);
      const expectedResponse = expect.objectContaining({ data: dummyResponse });

      return expect(service.deleteBoard({ id })).resolves.toEqual(expectedResponse);
    });

    it('fails for error response', () => {
      axiosMock.onDelete(url).replyOnce(500);

      return expect(service.deleteBoard({ id })).rejects.toThrow();
    });
  });
});
