import Immutable from 'immutable';
import forEach from 'lodash/forEach';
import isEqual from 'lodash/isEqual';
import _filter from 'lodash/filter'; // has to be `_filter` to avoid collision
import React from 'react';
import WebRPC from 'webrpc';
import moment from 'moment';
import generateEntityId from 'utils/entities';
import { channelService } from './index';
import { updateConnectionStatus } from 'actions/status';
import { userService } from 'services/';
import { playAlertAudio } from '../components/utils/AlertAudio';
import store from '../store';

export default class FeedService {
  constructor() {
    this.feeds = new Immutable.Map({});
    this.handlers = new Immutable.Map({});
    this.historyRequests = new Immutable.Map({});
  }

  connect() {
    if (this.socket) {
      return true;
    }
    this.socket = new WebRPC(process.env.CONTENT_ADDR);
    this.socket.onconnect = function(e) {
      updateConnectionStatus('feed', true);
    };
    this.socket.ondisconnect = function(e) {
      updateConnectionStatus('feed', false);
    };
    this.socket.on('connected', (data) => {
      this.updateContentSubscription();
    });
    this.socket.on('liveContent', this.receiveLive.bind(this));
    this.socket.on('archiveContent', this.receiveHistory.bind(this));
  }

  getNumberOfStoriesToRequest(feedId) {
    const currentNumberOfStories = this.feeds.getIn([feedId, 'stories']).size;
    return currentNumberOfStories < 15 ? 3 : 15;
  }

  requestHistory(range, feedId) {
    const filters = this.feeds.getIn([feedId, 'filters']).toJS();
    const query = {
      RequestID: generateEntityId(),
      Filter: this.formatFilter(filters),
      Range: {
        EarliestDate: range.earliestDate || null,
        LatestDate: range.latestDate || null,
        Limit: this.getNumberOfStoriesToRequest(feedId),
        Skip: 0
      }
    };
    this.historyRequests = this.historyRequests.set(feedId, query.RequestID);
    this.socket.emit('query', query);
  }

  receiveHistory(data) {
    // Match up request with Feed
    const feedId = this.historyRequests.findKey((v) => v === data.RequestID);
    if (!data.RequestID || feedId === undefined) {
      return;
    }
    const feed = this.feeds.get(feedId);

    // Populate feed with history
    if (data.Stories) {
      data.Stories.forEach((s) => {
        // Don't allow future stories in the feed. You'd be surprised.
        // eyeroll.docx
        if (moment().diff(s.CreatedAt, 'minutes') < -15) {
          return;
        }

        // Updates not supported by this
        if (!this.feeds.getIn([feedId, 'stories', s.ID])) {
          this.feeds = this.feeds.setIn([feedId, 'stories', s.ID], s);
        }
      });
    }
    if (!data.Stories || data.Stories.length === 0) {
      this.feeds = this.feeds.setIn([feedId, 'moreStoriesExist'], false);
    }
    this.historyRequests = this.historyRequests.delete(feedId);
    this.handlers.get(feedId)(this.getUpdateObject(feedId));
  }

  receiveLive(data) {
    data.Filters.forEach((f) => {
      this.findFilterMatch(f).forEach((feed, id) => {
        const s = data.Story;

        // Don't allow future stories in the feed. You'd be surprised.
        // eyeroll(1).docx
        if (moment().diff(s.CreatedAt, 'minutes') < -15) {
          return;
        }

        s.Live = true; // Used by CSS animation to flash row

        // We receive history for all filters, if we didn't need to update history don't touch this filter
        // The following code does not allow for updates
        if (!this.feeds.getIn([id, 'stories', s.ID])) {
          this.feeds = this.feeds.setIn([id, 'stories', s.ID], s);
        }
        this.handlers.get(id)(this.getUpdateObject(id));

        // test whether we should alert the user with a audible alert
        this.shouldPlayAlertAudio(userService.alerts, s);
      });
    });
  }

  updateContentSubscription() {
    const subscriptions = this.feeds.valueSeq().toJS().map((v) => v.filters);
    this.socket.emit('subscribe', subscriptions);
  }

  formatFilter(filter = {}) {
    let contentTypes = filter.contentType.concat();
    const symbols = filter.symbols.concat();
    // If it's pr_story we will show ALL press releases from all agencies
    if (contentTypes.indexOf('pr_story') > -1) {
      contentTypes.splice(contentTypes.indexOf('pr_story'), 1);
      contentTypes = contentTypes.concat(channelService.getPressReleaseTypes());
    }
    // Filter by watchlists
    if (filter.watchlists && filter.watchlists.length) {
      const watchlists = store.getState().watchlists.get('watchlists');
      forEach(filter.watchlists, w => {
        // When you delete a watchlist or any data that is saved for a workspace
        // on a different browser it breaks on any other browsers with saved sessions
        // using this deleted data.
        if (watchlists.getIn([w, 'tickers'])) {
          watchlists.getIn([w, 'tickers']).forEach(ticker => {
            if (symbols.indexOf(ticker.get('symbol')) === -1) {
              symbols.push(ticker.get('symbol'));
            }
          });
        }
      });
    }
    return {
      'keywords': filter.keywords,
      'symbols': symbols,
      'channels': filter.channels,
      'contentType': contentTypes
    };
  }

  findFilterMatch(filter) {
    return this.feeds.filter((feed) => {
      return this.filterMatch(filter, feed.get('filters').toJS());
    });
  }

  filterMatch(filter1, filter2) {
    if (filter1 === null || filter2 === null) {
      return filter1 === filter2;
    }
    let upperFilter = null;
    let lowerFilter = null;
    if ('Keywords' in filter1) {
      upperFilter = filter1;
      lowerFilter = filter2;
    } else if ('Keywords' in filter2) {
      upperFilter = filter2;
      lowerFilter = filter1;
    // If neither are uppercase, check lowercase
    } else {
      if (isEqual(filter1.keywords,    filter2.keywords) &&
          isEqual(filter1.symbols,     filter2.symbols) &&
          isEqual(filter1.channels,    filter2.channels) &&
          isEqual(filter1.contentType, filter2.contentType)) {
        return true;
      }
      return false;
    }

    if (isEqual(upperFilter.Keywords,    lowerFilter.keywords) &&
        isEqual(upperFilter.Symbols,     lowerFilter.symbols) &&
        isEqual(upperFilter.Channels,    lowerFilter.channels) &&
        isEqual(upperFilter.ContentType, lowerFilter.contentType)) {
      return true;
    }
    return false;
  }

  getStories(id) {
    return this.feeds.getIn([id, 'stories']);
  }

  getMoreStoriesExist(id) {
    return this.feeds.getIn([id, 'moreStoriesExist']);
  }

  requestMoreStories(range, feedId) {
    this.requestHistory(range, feedId);
    this.handlers.get(feedId)(this.getUpdateObject(feedId));
  }

  getUpdateObject(id) {
    return {
      stories: this.getStories(id),
      moreStoriesExist: this.getMoreStoriesExist(id),
      isLoading: this.historyRequests.has(id),
    };
  }

  // filters must be a list of tids
  initializeFeed(id, filters) {
    const newFeed = new Immutable.Map({
      id: id,
      filters: Immutable.fromJS(this.formatFilter(filters)),
      stories: new Immutable.Map(),
      moreStoriesExist: true,
      isLoading: true
    });

    this.feeds = this.feeds.set(id, newFeed);
    this.requestHistory({}, id);
    this.updateContentSubscription();
  }

  convertChannelsToTids(oldFilters) {
    const filters = Object.assign({}, oldFilters);
    const allChannels = store.getState().channels.items.toJS();
    if (allChannels !== undefined && filters !== undefined && filters.channels !== undefined) {
      filters.channels = channelService.convertIdsToTids(filters.channels, allChannels);
    }
    return filters;
  }

  // Component Subscribers
  addSubscriber(entityId, props, handler) {
    const filters = this.convertChannelsToTids(props.filters);
    this.initializeFeed(entityId, filters);
    this.handlers = this.handlers.set(entityId, handler);
    return this.getUpdateObject(entityId);
  }

  updateSubscriber(entityId, props) {
    const filters = this.convertChannelsToTids(props.filters);
    if (this.filterMatch(this.formatFilter(filters), this.feeds.getIn([entityId, 'filters']).toJS())) {
      return;
    }
    this.initializeFeed(entityId, filters);
    this.handlers.get(entityId)(this.getUpdateObject(entityId));
  }

  removeSubscriber(entityId, props) {
    this.feeds = this.feeds.delete(entityId);
    this.handlers = this.handlers.delete(entityId);
    this.historyRequests = this.historyRequests.delete(entityId);
    this.updateContentSubscription();
  }

  shouldPlayAlertAudio(alerts, story) {
    if (!alerts) {
      return;
    }

    let alert = null;
    let matchedAlert = null;
    for (alert of alerts) {
      if (alert.type === 'channel' && story.Channels) {
        if (_filter(story.Channels, 'name', alert.alert_name).length !== 0) {
          matchedAlert = alert;
          break;
        }
      } else if (alert.type === 'ticker' && story.Channels) {
        if (_filter(story.Tickers, 'name', alert.alert_name).length !== 0) {
          matchedAlert = alert;
          break;
        }
      }
    }

    if (matchedAlert !== null) {
      playAlertAudio(matchedAlert.sound);
    }
  }
}
