import datetime
import random
import webapp2_extras.security

from google.appengine.ext import ndb

from enki.modelrestapiconnecttoken import EnkiModelRestAPIConnectToken
from enki.modelrestapitokenverify import EnkiModelRestAPITokenVerify
from enki.modelapp import EnkiModelApp
from enki.modelrestapidatastore import EnkiModelRestAPIDataStore


MAX_AGE = 5    # in minutes, duration of a connection token validity
DATASTORE_EXPIRY_DEFAULT = 86400    # 24h
DATASTORE_NON_EXPIRING = 3154000000 # 100 years
DATASTORE_NON_EXPIRING_REFRESH = DATASTORE_NON_EXPIRING / 2

APP_MAX = 10    # maximum number of apps per user
APP_MAX_NAME_LENGTH = 32


def seconds_from_epoch( date_time ):
	return int(( date_time - datetime.datetime.utcfromtimestamp( 0 )).total_seconds())


def generate_connect_code():
	return webapp2_extras.security.generate_random_string( length = 5, pool = webapp2_extras.security.UPPERCASE_ALPHANUMERIC )


def generate_auth_token():
	return webapp2_extras.security.generate_random_string( length = 42, pool = webapp2_extras.security.ALPHANUMERIC )


def cleanup_and_get_new_connection_token( user_id ):
	# note: ensure user is logged in and has display name before calling this function
	if user_id:
		# delete any existing connect token for the user
		ndb.delete_multi_async( fetch_EnkiModelRestAPIConnectToken_by_user( user_id ))
		# create a new token and return it
		token = generate_connect_code()
		entity = EnkiModelRestAPIConnectToken( token = token, user_id = int( user_id ))
		entity.put()
		return token
	return None


def check_secret( user_id, auth_token, app_secret ):
	if EnkiModelRestAPITokenVerify.exist_by_user_id_token_app_secret( user_id, auth_token, app_secret ):
		# the user verify token contains the same secret as was sent in the request
		return True
	else:
		# retrieve the app_id from the verify token and check if the corresponding registered app has a new secret
		token_verify = EnkiModelRestAPITokenVerify.get_by_user_id_token( user_id, auth_token )
		if token_verify:
			app = EnkiModelApp.get_by_id( int( token_verify.app_id ))
			if app and app.secret == app_secret:
				# update the user's verify token app_secret
				token_verify.app_secret = app.secret
				token_verify.put()
				return True
	return False


def refresh_EnkiModelRestAPIConnectToken_non_expiring():
	likelyhood = 10 # occurs with a probability of 1%
	number = random.randint( 1, 1000 )
	if number < likelyhood:
		list = fetch_EnkiModelRestAPIDataStore_non_expiring()
		for item in list:
			item.time_expires = datetime.datetime.now() + datetime.timedelta( seconds = DATASTORE_NON_EXPIRING )
		ndb.put_multi_async( list )


def apps_list( user_id ):
	list = []
	apps = EnkiModelApp.fetch_by_user_id( user_id )
	for app in apps:
		list.append([ app.name, str( app.key.id()), app.secret, app.time_created ])
	return list


def user_data_list( user_id ):
	list = []
	data_stores = fetch_EnkiModelRestAPIDataStore_by_user_id( user_id )
	for data_store in data_stores:
		app_name = ''
		app = EnkiModelApp.get_by_id( int( data_store.app_id ))
		if app:
			app_name = app.name
		list.append([ data_store.app_id, app_name, data_store.data_type, data_store.data_id, data_store.data_payload, data_store.read_access, data_store.time_expires ])
	return list


def user_apps_list( data_list ):    # data_list generated by user_data_list
	list_id = []
	list_id_name = []
	for item in data_list:
		if item[ 0 ] not in list_id:
			list_id.append( item[ 0 ])
			list_id_name.append([ item[ 0 ], item[ 1 ]])
	return list_id_name


def delete_user_app_data( user_id, app_id ):
	keys = fetch_EnkiModelRestAPIDataStore_by_user_id_app_id( user_id, app_id )
	ndb.delete_multi( keys )


#=== QUERIES ==================================================================


def get_EnkiModelRestAPIConnectToken_by_token_user_id_valid_age( token, user_id ):
	entity = EnkiModelRestAPIConnectToken.query( ndb.AND( EnkiModelRestAPIConnectToken.token == token,
	                                                      EnkiModelRestAPIConnectToken.user_id == user_id,
	                                                      EnkiModelRestAPIConnectToken.time_created > ( datetime.datetime.now() - datetime.timedelta( minutes = MAX_AGE )))).get()
	return entity


def fetch_EnkiModelRestAPIConnectToken_by_user( user_id ):
	list = EnkiModelRestAPIConnectToken.query( EnkiModelRestAPIConnectToken.user_id == user_id ).fetch( keys_only = True )
	return list


def fetch_EnkiModelRestAPIConnectToken_expired():
	list = EnkiModelRestAPIConnectToken.query( EnkiModelRestAPIConnectToken.time_created < ( datetime.datetime.now() - datetime.timedelta( minutes = MAX_AGE ))).fetch( keys_only = True )
	return list


def get_EnkiModelRestAPIDataStore_by_user_id_app_id_data_type_data_id( user_id, app_id, data_type, data_id ):
	entity = EnkiModelRestAPIDataStore.query( ndb.AND( EnkiModelRestAPIDataStore.user_id == user_id,
	                                                   EnkiModelRestAPIDataStore.app_id == app_id,
	                                                   EnkiModelRestAPIDataStore.data_type == data_type,
	                                                   EnkiModelRestAPIDataStore.data_id == data_id )).get()
	return entity


def get_EnkiModelRestAPIDataStore_by_user_id_app_id_data_type_data_id_not_expired( user_id, app_id, data_type, data_id ):
	entity = EnkiModelRestAPIDataStore.query( ndb.AND( EnkiModelRestAPIDataStore.user_id == user_id,
	                                                   EnkiModelRestAPIDataStore.app_id == app_id,
	                                                   EnkiModelRestAPIDataStore.data_type == data_type,
	                                                   EnkiModelRestAPIDataStore.data_id == data_id,
	                                                   EnkiModelRestAPIDataStore.time_expires > datetime.datetime.now())).get()
	return entity


def fetch_EnkiModelRestAPIDataStore_by_user_id( user_id ):
	list = EnkiModelRestAPIDataStore.query( EnkiModelRestAPIDataStore.user_id == user_id ).order( EnkiModelRestAPIDataStore.app_id,
	                                                                                              EnkiModelRestAPIDataStore.data_type,
	                                                                                              EnkiModelRestAPIDataStore.data_id,
	                                                                                              EnkiModelRestAPIDataStore.time_expires ).fetch()
	return list


def fetch_EnkiModelRestAPIDataStore_by_user_id_app_id( user_id, app_id ):
	keys = EnkiModelRestAPIDataStore.query( ndb.AND( EnkiModelRestAPIDataStore.user_id == user_id,
	                                                 EnkiModelRestAPIDataStore.app_id == app_id )).fetch( keys_only = True )
	return keys


def fetch_EnkiModelRestAPIDataStore_by_user_id_app_id_data_type_read_access_not_expired( user_id, app_id, data_type, read_access ):
	entity = EnkiModelRestAPIDataStore.query( ndb.AND( EnkiModelRestAPIDataStore.user_id == user_id,
	                                                   EnkiModelRestAPIDataStore.app_id == app_id,
	                                                   EnkiModelRestAPIDataStore.data_type == data_type,
	                                                   EnkiModelRestAPIDataStore.read_access == read_access,
	                                                   EnkiModelRestAPIDataStore.time_expires > datetime.datetime.now())).fetch()
	return entity


def fetch_EnkiModelRestAPIDataStore_by_app_id_data_type_read_access_not_expired( app_id, data_type, read_access ):
	list = EnkiModelRestAPIDataStore.query( ndb.AND( EnkiModelRestAPIDataStore.app_id == app_id,
	                                                 EnkiModelRestAPIDataStore.data_type == data_type,
	                                                 EnkiModelRestAPIDataStore.read_access == read_access,
	                                                 EnkiModelRestAPIDataStore.time_expires > datetime.datetime.now())).fetch()
	return list


def fetch_EnkiModelRestAPIDataStore_by_user_id_app_id_data_type_data_id( user_id, app_id, data_type, data_id ):
	list = EnkiModelRestAPIDataStore.query( ndb.AND( EnkiModelRestAPIDataStore.user_id == user_id,
	                                                 EnkiModelRestAPIDataStore.app_id == app_id,
	                                                 EnkiModelRestAPIDataStore.data_type == data_type,
	                                                 EnkiModelRestAPIDataStore.data_id == data_id )).fetch( keys_only = True )
	return list


def fetch_EnkiModelRestAPIDataStore_expired():
	list = EnkiModelRestAPIDataStore.query( EnkiModelRestAPIDataStore.time_expires < datetime.datetime.now()).fetch( keys_only = True )
	return list


def fetch_EnkiModelRestAPIDataStore_non_expiring():
	list = EnkiModelRestAPIDataStore.query( EnkiModelRestAPIDataStore.time_expires > ( datetime.datetime.now() + datetime.timedelta( seconds = DATASTORE_NON_EXPIRING_REFRESH ))).fetch()
	return list
