import _ from 'lodash';
import debug from 'debug';
import dedent from 'dedent';
import fs from 'fs';
import goog from 'googleapis';
import { Observable } from 'rx';

import { timeCache, observeMethod } from './rx';

// one million!
const upperBound = 1000 * 1000;
const scope = 'https://www.googleapis.com/auth/analytics.readonly';
const pathToCred = process.env.GOOGLE_APPLICATION_CREDENTIALS;

const log = debug('fcc:server:utils:about');
const analytics = goog.analytics('v3');
const makeRequest = observeMethod(analytics.data.realtime, 'get');
export const toBoundInt = _.flow(
  // first convert string to integer
  _.toInteger,
  // then we bound the integer to prevent weird things like Infinity
  // and negative numbers
  // can't wait to the day we need to update this!
  _.partialRight(_.clamp, 0, upperBound)
);

export function createActiveUsers() {
  const zero = Observable.of(0);
  let credentials;
  if (!pathToCred) {
    // if no path to credentials set to zero;
    log(dedent`
      no google applications credentials environmental variable found
      'GOOGLE_APPLICATION_CREDENTIALS'
      'activeUser' api will always return 0
      this can safely be ignored during development
    `);
    return zero;
  }
  try {
    credentials = require(fs.realpathSync(pathToCred));
  } catch (err) {
    log('google applications credentials file failed to require');
    console.error(err);
    // if we can't require credentials set to zero;
    return zero;
  }
  if (
    !credentials.private_key ||
    !credentials.client_email ||
    !credentials.viewId
  ) {
    log(dedent`
      google applications credentials json should have a
      * private_key
      * client_email
      * viewId
      but none were found
    `);
    return zero;
  }

  const client = new goog.auth.JWT(
    credentials['client_email'],
    null,
    credentials['private_key'],
    [scope],
  );
  const authorize = observeMethod(client, 'authorize');
  const options = {
    ids: `ga:${credentials.viewId}`,
    auth: client,
    metrics: 'rt:activeUsers'
  };
  return Observable.defer(
    // we wait for authorize to complete before attempting to make request
    // this ensures our token is initialized and valid
    // we defer here to make sure the actual request is done per subscription
    // instead of once at startup
    () => authorize().flatMap(() => makeRequest(options))
  )
    // data: Array[body|Object, request: Request]
    .map(data => data[0])
    .map(
      ({ totalsForAllResults } = {}) => totalsForAllResults['rt:activeUsers']
    )
    .map(toBoundInt)
    // print errors to error log for logging, duh
    .do(null, err => console.error(err))
    // always send a number down
    .catch(() => Observable.of(0))
    // cache for 2 seconds to prevent hitting our daily request limit
    ::timeCache(2, 'seconds');
}

