import _ from 'lodash';
import moment from 'moment';
import loopback from 'loopback';
import path from 'path';
import dedent from 'dedent';
import { Observable } from 'rx';
import debug from 'debug';

import {
  ifNoUser401,
  ifNoUserSend
} from '../utils/middleware';

import { observeQuery } from '../utils/rx';

import {
  frontEndChallengeId,
  dataVisChallengeId,
  backEndChallengeId
} from '../utils/constantStrings.json';

import {
  completeCommitment$
} from '../utils/commit';

import certTypes from '../utils/certTypes.json';

const log = debug('fcc:certification');
const renderCertifedEmail = loopback.template(path.join(
  __dirname,
  '..',
  'views',
  'emails',
  'certified.ejs'
));
const renderNotifyEmail = loopback.template(path.join(
  __dirname,
  '..',
  'views',
  'emails',
  'a-new-user.ejs'
));
const sendMessageToNonUser = ifNoUserSend(
  'must be logged in to complete.'
);

function isCertified(ids, challengeMap = {}) {
  return _.every(ids, ({ id }) => challengeMap[id]);
}

function getIdsForCert$(id, Challenge) {
  return observeQuery(
    Challenge,
    'findById',
    id,
    {
      id: true,
      tests: true,
      name: true,
      challengeType: true
    }
  )
    .shareReplay();
}

// getFormatedDate(challengeMap: Object, challengeId: String) => String, throws
function getFormatedDate(challengeMap, challengeId) {
  return moment(challengeMap[challengeId].completedDate)
    .format('MMM Do, YYYY');
}

// sendCertifiedEmail(
//   {
//     email: String,
//     username: String,
//     isFrontEndCert: Boolean,
//     isBackEndCert: Boolean,
//     isDataVisCert: Boolean
//   },
//   send$: Observable
// ) => Observable
function sendCertifiedEmail(
  {
    email,
    name,
    username,
    isFrontEndCert,
    isBackEndCert,
    isDataVisCert,
    challengeMap
  },
  send$
) {
  if (
    !isFrontEndCert ||
    !isBackEndCert ||
    !isDataVisCert
  ) {
    return Observable.just(false);
  }
  let frontEndDate;
  let backEndDate;
  let dataVisDate;
  try {
    frontEndDate = getFormatedDate(challengeMap, frontEndChallengeId);
    backEndDate = getFormatedDate(challengeMap, backEndChallengeId);
    dataVisDate = getFormatedDate(challengeMap, dataVisChallengeId);
  } catch (err) {
    return Observable.throw(err);
  }

  const notifyTeam = {
    type: 'email',
    to: 'Michael@FreeCodeCamp.com',
    from: 'Team@FreeCodeCamp.com',
    subject: 'A new user has arrived!',
    text: renderNotifyEmail({
      username,
      name,
      frontEndDate,
      dataVisDate,
      backEndDate
    })
  };
  const notifyUser = {
    type: 'email',
    to: email,
    from: 'Michael@FreeCodeCamp.com',
    subject: 'Congratulation on gaining your third certification!',
    text: renderCertifedEmail({
      username,
      name
    })
  };
  return Observable.combineLatest(
    send$(notifyTeam),
    send$(notifyUser),
    () => true
  );
}

export default function certificate(app) {
  const router = app.loopback.Router();
  const { Email, Challenge } = app.models;

  const certTypeIds = {
    [certTypes.frontEnd]: getIdsForCert$(frontEndChallengeId, Challenge),
    [certTypes.dataVis]: getIdsForCert$(dataVisChallengeId, Challenge),
    [certTypes.backEnd]: getIdsForCert$(backEndChallengeId, Challenge)
  };

  router.post(
    '/certificate/verify/front-end',
    ifNoUser401,
    verifyCert.bind(null, certTypes.frontEnd)
  );

  router.post(
    '/certificate/verify/back-end',
    ifNoUser401,
    verifyCert.bind(null, certTypes.backEnd)
  );

  router.post(
    '/certificate/verify/data-visualization',
    ifNoUser401,
    verifyCert.bind(null, certTypes.dataVis)
  );

  router.post(
    '/certificate/honest',
    sendMessageToNonUser,
    postHonest
  );

  app.use(router);

  function verifyCert(certType, req, res, next) {
    const { user } = req;
    return user.getChallengeMap$()
      .flatMap(() => certTypeIds[certType])
      .flatMap(challenge => {
        const {
          id,
          tests,
          name,
          challengeType
        } = challenge;
        if (
          user[certType] ||
          !isCertified(tests, user.challengeMap)
        ) {
          return Observable.just(false);
        }
        const updateData = {
          $set: {
            [`challengeMap.${id}`]: {
              id,
              name,
              completedDate: new Date(),
              challengeType
            },
            [certType]: true
          }
        };
        // set here so sendCertifiedEmail works properly
        // not used otherwise
        user[certType] = true;
        user.challengeMap[id] = { completedDate: new Date() };
        return Observable.combineLatest(
          // update user data
          user.update$(updateData),
          // If user has committed to nonprofit,
          // this will complete their pledge
          completeCommitment$(user),
          // sends notification email is user has all three certs
          // if not it noop
          sendCertifiedEmail(user, Email.send$),
          ({ count }, pledgeOrMessage) => ({ count, pledgeOrMessage })
        )
          .map(
            ({ count, pledgeOrMessage }) => {
              if (typeof pledgeOrMessage === 'string') {
                log(pledgeOrMessage);
              }
              log(`${count} documents updated`);
              return true;
            }
          );
      })
      .subscribe(
        (didCertify) => {
          if (didCertify) {
            return res.status(200).send(true);
          }
          return res.status(200).send(
            dedent`
              Looks like you have not completed the neccessary steps.
              Please return to the challenge map.
            `
          );
        },
        next
      );
  }

  function postHonest(req, res, next) {
    return req.user.update$({ $set: { isHonest: true } }).subscribe(
      () => res.status(200).send(true),
      next
    );
  }
}
