import configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import fetchMock from 'fetch-mock';

import * as actions from '../../../pages/homepage/actions/categories.js';
import * as constants from '../../../pages/homepage/constants.js';
import * as ruleActions from '../../../pages/homepage/actions/rules.js';
import * as errorActions from '../../../pages/homepage/actions/error.js';

const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);

describe('category actions', () => {
  afterEach(() => {
    fetchMock.restore();
  });

  it('should create an action to select a category', () => {
    const category = { id: 1, name: 'Test category', unread: 100 };

    const expectedAction = {
      section: { ...category, type: constants.CATEGORY_TYPE },
      type: actions.SELECT_CATEGORY,
    };

    expect(actions.selectCategory(category)).toEqual(expectedAction);
  });

  it('should create an action to receive a category', () => {
    const category = { id: 1, name: 'Test category', unread: 100 };

    const expectedAction = {
      type: actions.RECEIVE_CATEGORY,
      category,
    };

    expect(actions.receiveCategory(category)).toEqual(expectedAction);
  });

  it('should create an action to receive multiple categories', () => {
    const categories = [
      { id: 1, name: 'Test category 1', unread: 200 },
      { id: 2, name: 'Test category 2', unread: 500 },
      { id: 3, name: 'Test category 3', unread: 600 },
    ];

    const expectedAction = {
      type: actions.RECEIVE_CATEGORIES,
      categories,
    };

    expect(actions.receiveCategories(categories)).toEqual(expectedAction);
  });

  it('should create an action to request a category', () => {
    const expectedAction = { type: actions.REQUEST_CATEGORY };

    expect(actions.requestCategory()).toEqual(expectedAction);
  });

  it('should create an action to request multiple categories', () => {
    const expectedAction = { type: actions.REQUEST_CATEGORIES };

    expect(actions.requestCategories()).toEqual(expectedAction);
  });

  it('should create multiple actions when fetching a category', () => {
    const category = {
      id: 1,
      name: 'Tech',
      unread: 1138,
    };

    fetchMock.getOnce('/api/categories/1', {
      body: { ...category, unread: 500 },
      headers: { 'content-type': 'application/json' },
    });

    const expectedActions = [
      { type: actions.REQUEST_CATEGORY },
      {
        type: actions.RECEIVE_CATEGORY,
        category: { ...category, unread: 500 },
      },
    ];

    const store = mockStore({
      categories: { items: {}, isFetching: false },
      rules: { items: {}, isFetching: false },
      posts: { items: {}, isFetching: false },
      selected: { item: {}, next: false, lastReached: false, post: {} },
    });

    return store.dispatch(actions.fetchCategory(category)).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
    });
  });

  it('should create multiple actions when fetching categories', () => {
    const categories = [
      { id: 1, name: 'Tech', unread: 29 },
      { id: 2, name: 'World news', unread: 956 },
    ];

    const rules = [
      {
        id: 5,
        name: 'Ars Technica',
        url: 'http://feeds.arstechnica.com/arstechnica/index?fmt=xml',
        favicon: 'https://cdn.arstechnica.net/favicon.ico',
        category: 1,
        unread: 7,
      },
      {
        id: 6,
        name: 'BBC',
        url: 'http://feeds.bbci.co.uk/news/world/rss.xml',
        favicon:
          'https://m.files.bbci.co.uk/modules/bbc-morph-news-waf-page-meta/2.5.2/apple-touch-icon-57x57-precomposed.png',
        category: 2,
        unread: 345,
      },
    ];

    fetchMock
      .get('/api/categories/', {
        body: categories,
        headers: { 'content-type': 'application/json' },
      })
      .get('/api/categories/1/rules/', {
        body: [{ ...rules[0] }],
        headers: { 'content-type': 'application/json' },
      })
      .get('/api/categories/2/rules/', {
        body: [{ ...rules[1] }],
        headers: { 'content-type': 'application/json' },
      });

    const expectedActions = [
      { type: actions.REQUEST_CATEGORIES },
      { type: actions.RECEIVE_CATEGORIES, categories },
      { type: ruleActions.REQUEST_RULES },
      { type: ruleActions.RECEIVE_RULES, rules },
    ];

    const store = mockStore({
      categories: { items: {}, isFetching: false },
      rules: { items: {}, isFetching: false },
      posts: { items: {}, isFetching: false },
      selected: { item: {}, next: false, lastReached: false, post: {} },
    });

    return store.dispatch(actions.fetchCategories()).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
    });
  });

  it('should create multiple actions when fetching a category which is read', () => {
    const category = {
      id: 1,
      name: 'Tech',
      unread: 0,
    };

    const rules = [
      {
        id: 1,
        name: 'Ars Technica',
        url: 'http://feeds.arstechnica.com/arstechnica/index?fmt=xml',
        favicon: 'https://cdn.arstechnica.net/favicon.ico',
        category: 1,
        unread: 200,
      },
      {
        id: 2,
        name: 'Hacker News',
        url: 'https://news.ycombinator.com/rss',
        favicon: 'https://news.ycombinator.com/favicon.ico',
        category: 1,
        unread: 350,
      },
    ];

    fetchMock
      .get('/api/categories/1', {
        body: { ...category, unread: 500 },
        headers: { 'content-type': 'application/json' },
      })
      .get('/api/categories/1/rules/', {
        body: rules,
        headers: { 'content-type': 'application/json' },
      });

    const expectedActions = [
      { type: actions.REQUEST_CATEGORY },
      {
        type: actions.RECEIVE_CATEGORY,
        category: { ...category, unread: 500 },
      },
      { type: ruleActions.REQUEST_RULES },
      { type: ruleActions.RECEIVE_RULES, rules },
    ];

    const store = mockStore({
      categories: { items: {}, isFetching: false },
      rules: { items: {}, isFetching: false },
      posts: { items: {}, isFetching: false },
      selected: {
        item: { ...category, type: constants.CATEGORY_TYPE, clicks: 2 },
        next: false,
        lastReached: false,
        post: {},
      },
    });

    return store.dispatch(actions.fetchCategory(category)).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
    });
  });

  it('should create no actions for a category which is selected less than x', () => {
    const category = {
      id: 1,
      name: 'Tech',
      unread: 200,
    };

    fetchMock.getOnce('/api/categories/1', {
      body: { ...category, unread: 100 },
      headers: { 'content-type': 'application/json' },
    });

    const store = mockStore({
      categories: { items: {}, isFetching: false },
      rules: { items: {}, isFetching: false },
      posts: { items: {}, isFetching: false },
      selected: {
        item: { ...category, type: constants.CATEGORY_TYPE, clicks: 1 },
        next: false,
        lastReached: false,
        post: {},
      },
    });

    const expectedActions = [];

    store.dispatch(actions.fetchCategory(category));

    expect(store.getActions()).toEqual(expectedActions);
  });

  it('should handle an unexpected response when fetching a category', () => {
    const category = {
      id: 1,
      name: 'Tech',
      unread: 1138,
    };

    const errorMessage = 'Key id not found';

    fetchMock.getOnce('/api/categories/1', () => {
      throw new TypeError(errorMessage);
    });

    const expectedActions = [
      { type: actions.REQUEST_CATEGORY },
      { type: actions.RECEIVE_CATEGORY, category: {} },
      { type: errorActions.RECEIVE_API_ERROR, error: TypeError(errorMessage) },
    ];

    const store = mockStore({
      categories: { items: {}, isFetching: false },
      rules: { items: {}, isFetching: false },
      posts: { items: {}, isFetching: false },
      selected: { item: {}, next: false, lastReached: false, post: {} },
      error: { error: {} },
    });

    return store.dispatch(actions.fetchCategory(category)).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
    });
  });

  it('should handle an unexpected response when multiple categories', () => {
    const category = {
      id: 1,
      name: 'Tech',
      unread: 1138,
    };

    const errorMessage = 'URL not found';

    fetchMock.getOnce('/api/categories/', () => {
      throw new Error(errorMessage);
    });

    const expectedActions = [
      { type: actions.REQUEST_CATEGORIES },
      { type: actions.RECEIVE_CATEGORIES, categories: [] },
      { type: ruleActions.RECEIVE_RULES, rules: [] },
      { type: errorActions.RECEIVE_API_ERROR, error: Error(errorMessage) },
    ];

    const store = mockStore({
      categories: { items: {}, isFetching: false },
      rules: { items: {}, isFetching: false },
      posts: { items: {}, isFetching: false },
      selected: { item: {}, next: false, lastReached: false, post: {} },
      error: { error: {} },
    });

    return store.dispatch(actions.fetchCategories()).then(() => {
      expect(store.getActions()).toEqual(expectedActions);
    });
  });
});
