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

import { objectsFromArray } from '../../../utils.js';

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

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

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

  it('should create an action to select a rule', () => {
    const rule = {
      id: 1,
      name: 'Test rule',
      unread: 100,
      category: 1,
      url: 'http://feeds.arstechnica.com/arstechnica/index?fmt=xml',
      favicon: 'https://cdn.arstechnica.net/favicon.ico',
    };

    const expectedAction = {
      section: { ...rule, type: constants.RULE_TYPE },
      type: actions.SELECT_RULE,
    };

    expect(actions.selectRule(rule)).toEqual(expectedAction);
  });

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

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

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

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

  it('should create an action to receive a rule', () => {
    const rule = {
      id: 1,
      name: 'Test rule',
      unread: 100,
      category: 1,
      url: 'http://feeds.arstechnica.com/arstechnica/index?fmt=xml',
      favicon: 'https://cdn.arstechnica.net/favicon.ico',
    };

    const expectedAction = {
      type: actions.RECEIVE_RULE,
      rule,
    };

    expect(actions.receiveRule(rule)).toEqual(expectedAction);
  });

  it('should create an action to receive multiple rules', () => {
    const rules = [
      {
        id: 1,
        name: 'Test rule',
        unread: 100,
        category: 1,
        url: 'http://feeds.arstechnica.com/arstechnica/index?fmt=xml',
        favicon: 'https://cdn.arstechnica.net/favicon.ico',
      },
      {
        id: 2,
        name: 'Test rule 2',
        unread: 50,
        category: 1,
        url: 'https://xkcd.com/atom.xml',
        favicon: null,
      },
    ];

    const expectedAction = {
      type: actions.RECEIVE_RULES,
      rules,
    };

    expect(actions.receiveRules(rules)).toEqual(expectedAction);
  });

  it('should create multiple actions to fetch a rule', () => {
    const rule = {
      id: 1,
      name: 'Test rule',
      unread: 100,
      category: 1,
      url: 'http://feeds.arstechnica.com/arstechnica/index?fmt=xml',
      favicon: 'https://cdn.arstechnica.net/favicon.ico',
    };

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

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

    const expectedActions = [
      { type: actions.REQUEST_RULE },
      { type: actions.RECEIVE_RULE, rule: { ...rule, unread: 500 } },
    ];

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

  it('should not create not create actions when rule is clicked less then twice', () => {
    const rule = {
      id: 1,
      name: 'Test rule',
      unread: 100,
      category: 1,
      url: 'http://feeds.arstechnica.com/arstechnica/index?fmt=xml',
      favicon: 'https://cdn.arstechnica.net/favicon.ico',
    };

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

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

    const expectedActions = [];

    store.dispatch(actions.fetchRule(rule));

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

  it('should create multiple actions to fetch a rule wich is read', () => {
    const rule = {
      id: 1,
      name: 'Test rule',
      unread: 0,
      category: 1,
      url: 'http://feeds.arstechnica.com/arstechnica/index?fmt=xml',
      favicon: 'https://cdn.arstechnica.net/favicon.ico',
    };

    const category = {
      id: 1,
      name: 'Test category',
      unread: 500,
    };

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

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

    const expectedActions = [
      { type: actions.REQUEST_RULE },
      { type: actions.RECEIVE_RULE, rule: { ...rule, unread: 500 } },
      { type: categoryActions.REQUEST_CATEGORY },
      {
        type: categoryActions.RECEIVE_CATEGORY,
        category: { ...category, unread: 2000 },
      },
    ];

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

  it('should create multiple actions when fetching rules by category', () => {
    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.getOnce('/api/categories/1/rules/', {
      body: rules,
      headers: { 'content-type': 'application/json' },
    });

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

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

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

  it('should handle an unexpected response when fetching a rule', () => {
    const rule = {
      id: 1,
      name: 'Test rule',
      unread: 100,
      category: 1,
      url: 'http://feeds.arstechnica.com/arstechnica/index?fmt=xml',
      favicon: 'https://cdn.arstechnica.net/favicon.ico',
    };

    const errorMessage = 'Too many requests';

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

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

    const expectedActions = [
      { type: actions.REQUEST_RULE },
      { type: actions.RECEIVE_RULE, rule: {} },
      { type: errorActions.RECEIVE_API_ERROR, error: TypeError(errorMessage) },
    ];

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

  it('should handle an unexpected response when fetching rules by category', () => {
    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,
      },
    ];

    const errorMessage = 'Too many request';

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

    const expectedActions = [
      { type: actions.REQUEST_RULES },
      { type: actions.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: {},
    });

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