import axios, { AxiosStatic } from 'axios';
import {
  createArticle,
  createComment,
  deleteArticle,
  deleteComment,
  favoriteArticle,
  followUser,
  getArticle,
  getArticleComments,
  getArticles,
  getFeed,
  getProfile,
  getTags,
  getUser,
  login,
  signUp,
  unfavoriteArticle,
  unfollowUser,
  updateArticle,
  updateSettings,
} from './conduit';

jest.mock('axios');

const mockedAxios = axios as jest.Mocked<AxiosStatic>;

const defaultArticle = {
  slug: 'how-to-train-your-dragon',
  title: 'How to train your dragon',
  description: 'Ever wonder how?',
  body: 'It takes a Jacobian',
  tagList: ['dragons', 'training'],
  createdAt: '2016-02-18T03:22:56.637Z',
  updatedAt: '2016-02-18T03:48:35.824Z',
  favorited: false,
  favoritesCount: 0,
  author: {
    username: 'jake',
    bio: 'I work at statefarm',
    image: 'https://i.stack.imgur.com/xHWG8.jpg',
    following: false,
  },
};

const defaultComment = {
  id: 1,
  createdAt: '2016-02-18T03:22:56.637Z',
  updatedAt: '2016-02-18T03:22:56.637Z',
  body: 'It takes a Jacobian',
  author: {
    username: 'jake',
    bio: 'I work at statefarm',
    image: 'https://i.stack.imgur.com/xHWG8.jpg',
    following: false,
  },
};

it('Should get articles', async () => {
  mockedAxios.get.mockResolvedValueOnce({
    data: {
      articles: [
        defaultArticle,
        {
          slug: 'how-to-train-your-dragon-2',
          title: 'How to train your dragon 2',
          description: 'So toothless',
          body: 'It a dragon',
          tagList: ['dragons', 'training'],
          createdAt: '2016-02-18T03:22:56.637Z',
          updatedAt: '2016-02-18T03:48:35.824Z',
          favorited: false,
          favoritesCount: 0,
          author: {
            username: 'jake',
            bio: 'I work at statefarm',
            image: 'https://i.stack.imgur.com/xHWG8.jpg',
            following: false,
          },
        },
      ],
      articlesCount: 2,
    },
  });

  const result = await getArticles();
  expect(result.articles.length).toBe(2);
  expect(result.articlesCount).toBe(2);
});

it('Should get tags', async () => {
  mockedAxios.get.mockResolvedValueOnce({
    data: {
      tags: ['reactjs', 'angularjs'],
    },
  });

  const result = await getTags();
  expect(result.tags.length).toBe(2);
  expect(result.tags).toContain('angularjs');
});

it('Should send correct login object', async () => {
  mockedAxios.post.mockRejectedValueOnce({ response: { data: { errors: { x: ['y', 'z'] } } } });

  await login('thisIsUser', 'thisIsPassword');

  const call = mockedAxios.post.mock.calls[0];

  expect(call[1]).toHaveProperty('user');
  expect(call[1].user).toHaveProperty('email', 'thisIsUser');
  expect(call[1].user).toHaveProperty('password', 'thisIsPassword');
});

it('Should get login errors', async () => {
  mockedAxios.post.mockRejectedValueOnce({ response: { data: { errors: { x: ['y', 'z'] } } } });

  const result = await login('', '');
  result.match({
    ok: () => fail(),
    err: (e) => {
      expect(e).toHaveProperty('x');
      expect(e['x']).toHaveLength(2);
    },
  });
});

it('Should get user on successful login', async () => {
  mockedAxios.post.mockResolvedValueOnce({
    data: {
      user: {
        email: '[email protected]',
        token: 'jwt.token.here',
        username: 'jake',
        bio: 'I work at statefarm',
        image: null,
      },
    },
  });

  const result = await login('', '');
  result.match({
    ok: (user) => {
      expect(user).toHaveProperty('email', '[email protected]');
      expect(user).toHaveProperty('token', 'jwt.token.here');
    },
    err: () => fail(),
  });
});

it('Should return article on favorite', async () => {
  mockedAxios.post.mockResolvedValueOnce({
    data: {
      article: { ...defaultArticle, favorited: true },
    },
  });

  const result = await favoriteArticle(defaultArticle.slug);

  expect(mockedAxios.post.mock.calls.length).toBe(1);
  expect(result.slug).toMatch(defaultArticle.slug);
});

it('Should return article on unfavorite', async () => {
  mockedAxios.delete.mockResolvedValueOnce({
    data: {
      article: { ...defaultArticle, favorited: true },
    },
  });

  const result = await unfavoriteArticle(defaultArticle.slug);

  expect(mockedAxios.delete.mock.calls.length).toBe(1);
  expect(result.slug).toMatch(defaultArticle.slug);
});

it('Should get user', async () => {
  mockedAxios.get.mockResolvedValueOnce({
    data: {
      user: {
        email: '[email protected]',
        token: 'jwt.token.here',
        username: 'jake',
        bio: 'I work at statefarm',
        image: null,
      },
    },
  });

  const user = await getUser();
  expect(user).toHaveProperty('email', '[email protected]');
  expect(user).toHaveProperty('token', 'jwt.token.here');
});

it('Should get update settings errors', async () => {
  mockedAxios.put.mockRejectedValueOnce({ data: { errors: { x: ['y', 'z'] } } });

  const result = await updateSettings({ email: '', password: '', bio: '', image: null, username: '' });
  result.match({
    ok: () => fail(),
    err: (e) => {
      expect(e).toHaveProperty('x');
      expect(e['x']).toHaveLength(2);
    },
  });
});

it('Should get user on successful settings update', async () => {
  mockedAxios.put.mockResolvedValueOnce({
    data: {
      user: {
        email: '[email protected]',
        token: 'jwt.token.here',
        username: 'jake',
        bio: 'I work at statefarm',
        image: null,
      },
    },
  });

  const result = await updateSettings({ email: '', password: '', bio: '', image: null, username: '' });
  result.match({
    ok: (user) => {
      expect(user).toHaveProperty('email', '[email protected]');
      expect(user).toHaveProperty('token', 'jwt.token.here');
    },
    err: () => fail(),
  });
});

it('Should get signUp errors', async () => {
  mockedAxios.post.mockRejectedValueOnce({ response: { data: { errors: { x: ['y', 'z'] } } } });

  const result = await signUp({ email: '', password: '', username: '' });
  result.match({
    ok: () => fail(),
    err: (e) => {
      expect(e).toHaveProperty('x');
      expect(e['x']).toHaveLength(2);
    },
  });
});

it('Should get user on successful signup', async () => {
  mockedAxios.post.mockResolvedValueOnce({
    data: {
      user: {
        email: '[email protected]',
        token: 'jwt.token.here',
        username: 'jake',
        bio: 'I work at statefarm',
        image: null,
      },
    },
  });

  const result = await signUp({ email: '', password: '', username: '' });
  result.match({
    ok: (user) => {
      expect(user).toHaveProperty('email', '[email protected]');
      expect(user).toHaveProperty('token', 'jwt.token.here');
    },
    err: () => fail(),
  });
});

it('Should get errors on unsuccessful article creation', async () => {
  mockedAxios.post.mockRejectedValueOnce({ response: { data: { errors: { x: ['y', 'z'] } } } });

  const result = await createArticle({ title: '', body: '', description: '', tagList: [] });
  result.match({
    ok: () => fail(),
    err: (e) => {
      expect(e).toHaveProperty('x');
      expect(e['x']).toHaveLength(2);
    },
  });
});

it('Should get article on successful article creation', async () => {
  mockedAxios.post.mockResolvedValueOnce({
    data: {
      article: defaultArticle,
    },
  });

  const result = await createArticle({ title: '', body: '', description: '', tagList: [] });
  expect(result.isOk()).toBeTruthy();
  expect(result.unwrap().slug).toMatch(defaultArticle.slug);
});

it('Should get article', async () => {
  mockedAxios.get.mockResolvedValueOnce({
    data: {
      article: defaultArticle,
    },
  });

  const article = await getArticle('');
  expect(article.slug).toMatch(defaultArticle.slug);
});

it('Should get errors on unsuccessful article update', async () => {
  mockedAxios.put.mockRejectedValueOnce({ response: { data: { errors: { x: ['y', 'z'] } } } });

  const result = await updateArticle('', { title: '', body: '', description: '', tagList: [] });
  result.match({
    ok: () => fail(),
    err: (e) => {
      expect(e).toHaveProperty('x');
      expect(e['x']).toHaveLength(2);
    },
  });
});

it('Should get article on successful article creation', async () => {
  mockedAxios.put.mockResolvedValueOnce({
    data: {
      article: defaultArticle,
    },
  });

  const result = await updateArticle('', { title: '', body: '', description: '', tagList: [] });
  expect(result.isOk()).toBeTruthy();
  expect(result.unwrap().slug).toMatch(defaultArticle.slug);
});

it('Should get profile', async () => {
  mockedAxios.get.mockResolvedValueOnce({
    data: {
      profile: {
        username: 'the one',
        bio: 'and only',
        image: null,
        following: false,
      },
    },
  });

  const result = await getProfile('1');
  expect(result.username === 'the one').toBeTruthy();
});

it('Should get profile on follow', async () => {
  mockedAxios.post.mockResolvedValueOnce({
    data: {
      profile: {
        username: 'the one 2',
        bio: 'and only',
        image: null,
        following: false,
      },
    },
  });

  const result = await followUser('1');
  expect(result.username === 'the one 2').toBeTruthy();
});

it('Should get profile on unfollow', async () => {
  mockedAxios.delete.mockResolvedValueOnce({
    data: {
      profile: {
        username: 'the one 3',
        bio: 'and only',
        image: null,
        following: false,
      },
    },
  });

  const result = await unfollowUser('1');
  expect(result.username === 'the one 3').toBeTruthy();
});

it('Should get feed', async () => {
  mockedAxios.get.mockResolvedValueOnce({
    data: {
      articles: [defaultArticle, defaultArticle],
      articlesCount: 2,
    },
  });

  const result = await getFeed();
  expect(result.articles.length).toBe(2);
  expect(result.articlesCount).toBe(2);
});

it('Should get comments', async () => {
  mockedAxios.get.mockResolvedValueOnce({
    data: {
      comments: [defaultComment, defaultComment, defaultComment],
    },
  });
  const result = await getArticleComments('the slug');
  expect(result).toHaveLength(3);
  expect(mockedAxios.get.mock.calls).toHaveLength(1);
});

it('Should delete comment', async () => {
  mockedAxios.delete.mockResolvedValueOnce({});
  await deleteComment('the slug', 123);
  expect(mockedAxios.delete.mock.calls).toHaveLength(1);
});

it('Should add comment', async () => {
  mockedAxios.post.mockResolvedValueOnce({ data: { comment: defaultComment } });
  await createComment('the slug', 'The body');
  expect(mockedAxios.post.mock.calls).toHaveLength(1);
  expect(mockedAxios.post.mock.calls[0][1]).toHaveProperty('comment');
  expect(mockedAxios.post.mock.calls[0][1].comment).toHaveProperty('body', 'The body');
});

it('Should delete article', async () => {
  mockedAxios.delete.mockResolvedValueOnce({});
  await deleteArticle('the slug', 123);
  expect(mockedAxios.delete.mock.calls).toHaveLength(1);
});