#!/usr/bin/env python
#
# @author:  Gunnar Schaefer

import os
import json
import uuid
import hashlib
import logging
import pymongo
import tarfile
import webapp2
import requests
import zipfile
import argparse
import bson.json_util
import webapp2_extras.routes

import nimsutil

import epochs
import sessions
import experiments
import nimsapiutil

log = logging.getLogger('nimsapi')


class NIMSAPI(nimsapiutil.NIMSRequestHandler):

    def head(self):
        """Return 200 OK."""
        self.response.set_status(200)

    def get(self):
        """Return API documentation"""
        self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
        self.response.write('nimsapi - {0}\n'.format(self.app.config['site_id']))

    def upload(self):
        # TODO add security: either authenticated user or machine-to-machine CRAM
        if 'Content-MD5' not in self.request.headers:
            self.abort(400, 'Request must contain a valid "Content-MD5" header.')
        filename = self.request.get('filename', 'anonymous')
        stage_path = self.app.config['stage_path']
        with nimsutil.TempDir(prefix='.tmp', dir=stage_path) as tempdir_path:
            hash_ = hashlib.md5()
            upload_filepath = os.path.join(tempdir_path, filename)
            log.info(os.path.basename(upload_filepath))
            with open(upload_filepath, 'wb') as upload_file:
                for chunk in iter(lambda: self.request.body_file.read(2**20), ''):
                    hash_.update(chunk)
                    upload_file.write(chunk)
            if hash_.hexdigest() != self.request.headers['Content-MD5']:
                self.abort(400, 'Content-MD5 mismatch.')
            if not tarfile.is_tarfile(upload_filepath) and not zipfile.is_zipfile(upload_filepath):
                self.abort(415)
            os.rename(upload_filepath, os.path.join(stage_path, str(uuid.uuid1()) + '_' + fid)) # add UUID to prevent clobbering files

    def download(self):
        paths = []
        symlinks = []
        for js_id in self.request.get('id', allow_multiple=True):
            type_, _id = js_id.split('_')
            _idpaths, _idsymlinks = resource_types[type_].download_info(_id)
            paths += _idpaths
            symlinks += _idsymlinks

    def dump(self):
        self.response.write(json.dumps(list(self.app.db.sessions.find()), default=bson.json_util.default))


class Users(nimsapiutil.NIMSRequestHandler):

    def count(self, iid):
        """Return the number of Users."""
        self.response.write('%d users\n' % self.app.db.users.count())

    def post(self, iid):
        """Create a new User"""
        self.response.write('users post\n')

    def get(self, iid):
        """Return the list of Users."""
        projection = ['firstname', 'lastname', 'email_hash']
        users = list(self.app.db.users.find({}, projection))
        self.response.write(json.dumps(users, default=bson.json_util.default))

    def put(self, iid):
        """Update many Users."""
        self.response.write('users put\n')


class User(nimsapiutil.NIMSRequestHandler):

    def get(self, iid, uid):
        """Return User details."""
        user = self.app.db.users.find_one({'_id': uid})
        self.response.write(json.dumps(user, default=bson.json_util.default))

    def put(self, iid, uid):
        """Update an existing User."""
        user = self.app.db.users.find_one({'_id': uid})
        if not user:
            self.abort(404)
        if uid == self.userid or self.user_is_superuser: # users can only update their own info
            updates = {'$set': {}, '$unset': {}}
            for k, v in self.request.params.iteritems():
                if k != 'superuser' and k in []:#user_fields:
                    updates['$set'][k] = v # FIXME: do appropriate type conversion
                elif k == 'superuser' and uid == self.userid and self.user_is_superuser is not None: # toggle superuser for requesting user
                    updates['$set'][k] = v.lower() in ('1', 'true')
                elif k == 'superuser' and uid != self.userid and self.user_is_superuser:             # enable/disable superuser for other user
                    if v.lower() in ('1', 'true') and user.get('superuser') is None:
                        updates['$set'][k] = False # superuser is tri-state: False indicates granted, but disabled, superuser privileges
                    elif v.lower() not in ('1', 'true'):
                        updates['$unset'][k] = ''
            user = self.app.db.users.find_and_modify({'_id': uid}, updates, new=True)
        else:
            self.abort(403)
        self.response.write(json.dumps(user, default=bson.json_util.default) + '\n')

    def delete(self, iid, uid):
        """Delete an User."""
        self.response.write('user %s delete, %s\n' % (uid, self.request.params))


class Groups(nimsapiutil.NIMSRequestHandler):

    def count(self, iid):
        """Return the number of Groups."""
        self.response.write('%d groups\n' % self.app.db.groups.count())

    def post(self, iid):
        """Create a new Group"""
        self.response.write('groups post\n')

    def get(self, iid):
        """Return the list of Groups."""
        projection = ['_id']
        groups = list(self.app.db.groups.find({}, projection))
        self.response.write(json.dumps(groups, default=bson.json_util.default))

    def put(self, iid):
        """Update many Groups."""
        self.response.write('groups put\n')


class Group(nimsapiutil.NIMSRequestHandler):

    def get(self, iid, gid):
        """Return Group details."""
        group = self.app.db.groups.find_one({'_id': gid})
        self.response.write(json.dumps(group, default=bson.json_util.default))

    def put(self, iid, gid):
        """Update an existing Group."""
        self.response.write('group %s put, %s\n' % (gid, self.request.params))

    def delete(self, iid, gid):
        """Delete an Group."""


class Remotes(nimsapiutil.NIMSRequestHandler):

    def get(self):
        """Return Remote NIMS sites"""
        logging.info(self.user)
        # TODO: remotes by default will show all registered remote site
        if self.request.get('all'):
            projection = ['_id', 'hostname', 'ip4']
            sites = list(self.app.db.remotes.find({}, projection))
            self.response.write(json.dumps(sites, default=bson.json_util.default))
        # if 'all' not specificed, then user MUST be speficied
        else:
            """Return the list of remotes where user has membership"""
            logging.info(self.user['_id'])
            # projection = ['_id', 'hostname', 'ip4']
            projection = ['_id', 'hostname', 'ip4', 'users']

            remotes = list(self.app.db.remotes.find({'users': {'$in': [self.user['_id']]}}, projection))
            self.response.write(json.dumps(remotes, default=bson.json_util.default))

            # TODO: IMPORTANT; refine how remotes are returned from pymongo queries

            # in the list of remotes; in which sites, are there experiments, which the person has access to?
            # return ONLY remote site names.

            # if not superuser, does person have permissions to what is being queried
            # query = {'permissions.' + self.userid: {'$in': 'true'}} if not self.user_is_superuser else None
            # projection = ['_id', 'users', 'hostname', 'ip4']
            # remotes = list(self.app.db.remotes.find(query, projection))

            # session_aggregates = self.app.db.sessions.aggregate([
            # #         {'$match': {'experiment': {'$in': [exp['_id'] for exp in experiments]}}},
            #         {'$group': {'_id': '$experiment', 'timestamp': {'$max': '$timestamp'}}},
            #         ])['result']
            # timestamps = {sa['_id']: sa['timestamp'] for sa in session_aggregates}
            # for exp in experiments:
            #     exp['timestamp'] = timestamps[exp['_id']]
            # self.response.write(json.dumps(experiments, default=bson.json_util.default))


class ArgumentParser(argparse.ArgumentParser):

    def __init__(self):
        super(ArgumentParser, self).__init__()
        self.add_argument('uri', help='NIMS DB URI')
        self.add_argument('stage_path', help='path to staging area')
        self.add_argument('--pubkey', default='internims/NIMSpubkey.pub', help='path to ssl pubkey')
        self.add_argument('-u', '--uid', default='local', help='site uid')
        self.add_argument('-f', '--logfile', help='path to log file')
        self.add_argument('-l', '--loglevel', default='info', help='path to log file')
        self.add_argument('-q', '--quiet', action='store_true', default=False, help='disable console logging')

routes = [
    webapp2.Route(r'/nimsapi',                                      NIMSAPI),
    webapp2_extras.routes.PathPrefixRoute(r'/nimsapi', [
        webapp2.Route(r'/download',                                 NIMSAPI, handler_method='download', methods=['GET']),
        webapp2.Route(r'/dump',                                     NIMSAPI, handler_method='dump', methods=['GET']),
        webapp2.Route(r'/upload/<fid>',                             NIMSAPI, handler_method='upload', methods=['PUT']),
        webapp2.Route(r'/remotes',                                  Remotes),
        ]),
    # webapp2_extras.routes.PathPrefixRoute has bug, variable MUST have regex
    webapp2_extras.routes.PathPrefixRoute(r'/nimsapi/<iid:[^/]+>', [
        webapp2.Route(r'/users',                                    Users),
        webapp2.Route(r'/users/count',                              Users, handler_method='count', methods=['GET']),
        webapp2.Route(r'/users/<uid>',                              User),
        webapp2.Route(r'/groups',                                   Groups),
        webapp2.Route(r'/groups/count',                             Groups, handler_method='count', methods=['GET']),
        webapp2.Route(r'/groups/<gid>',                             Group),
        webapp2.Route(r'/experiments',                              experiments.Experiments),
        webapp2.Route(r'/experiments/count',                        experiments.Experiments, handler_method='count', methods=['GET']),
        webapp2.Route(r'/experiments/<xid:[0-9a-f]{24}>',           experiments.Experiment),
        webapp2.Route(r'/experiments/<xid:[0-9a-f]{24}>/sessions',  sessions.Sessions),
        webapp2.Route(r'/sessions/count',                           sessions.Sessions, handler_method='count', methods=['GET']),
        webapp2.Route(r'/sessions/<sid:[0-9a-f]{24}>',              sessions.Session),
        webapp2.Route(r'/sessions/<sid:[0-9a-f]{24}>/move',         sessions.Session, handler_method='move'),
        webapp2.Route(r'/sessions/<sid:[0-9a-f]{24}>/epochs',       epochs.Epochs),
        webapp2.Route(r'/epochs/count',                             epochs.Epochs, handler_method='count', methods=['GET']),
        webapp2.Route(r'/epochs/<eid:[0-9a-f]{24}>',                epochs.Epoch),
    ]),
]


if __name__ == '__main__':
    args = ArgumentParser().parse_args()
    nimsutil.configure_log(args.logfile, not args.quiet, args.loglevel)

    from paste import httpserver
    app = webapp2.WSGIApplication(routes, debug=True, config=dict(stage_path=args.stage_path, site_id=args.uid, pubkey=args.pubkey))
    app.db = (pymongo.MongoReplicaSetClient(args.uri) if 'replicaSet' in args.uri else pymongo.MongoClient(args.uri)).get_default_database()
    httpserver.serve(app, host=httpserver.socket.gethostname(), port='8080')