"""
	Steve L. Nyemba <steve@the-phi.com>
	The Phi Technology, LLC - Store
	
	This file handles customer & plans associated with a given product/app
	We understand that a product will have multiple plans under it and we make sure that we always have a free version:
		- Having a free product insures there is no excuse not to signup users
		- If a product doesn't fall under this model we will find a way to fix it.
	- 
	The store must be prefixed with an authentication module, because customers must be signed in before making a purchase.
"""
from __future__ import division
from flask import Flask, request, session, render_template, Response,redirect
from flask_session import Session
from flask_cors import CORS
from flask_pymongo import PyMongo
from flask_mongo_sessions import MongoDBSessionInterface

import stripe
import json
# from StringIO import StringIO
import re
import os
from utils.params import PARAMS
from transport import factory
# from store import Store
import store
from datetime import datetime
import numpy as np
import requests
import os


# from utils.transport import Couchdb, CouchdbReader
PORT = 8100 if 'port' not in PARAMS else int(PARAMS['port']) ;
path = PARAMS['path'] #os.environ['CONFIG']
f = open(path)
CONFIG = json.loads(f.read())
CONTEXT= PARAMS['context'] if 'context' in PARAMS else ''
stripe_keys = {
    'secret_key': CONFIG['stripe']['secret'].strip(),
    'publishable_key': CONFIG['stripe']['pub'].strip()
}

stripe.api_key = stripe_keys['secret_key'].strip()


if 'CLOUDVIEW_CONTEXT' in os.environ and os.environ['CLOUDVIEW_CONTEXT'] not in CONFIG['cloud-view']:
	context = os.environ['CLOUDVIEW_CONTEXT']
	CONFIG['cloud-view'] = CONFIG['cloud-view'].replace('cloud-view',context)
	
app = Flask(__name__)
CORS(app)

SYS_STORE = CONFIG['store']

# @app.route("/donate")
# def _donate() :
	
# 	amount = float(request.args['amount']) if 'amount' in request.args else 5.0
# 	args = {'amount':amount,'context':CONTEXT}
# 	return render_template('donate.html',**args)
@app.route("/")
def index():
	if 'default' in  CONFIG:
		id = CONFIG['default']['id']
		return get_plans_ui(id)
	headers = {"content-type":"application/json"}
	mystore = store.factory.instance(name='music')
	products = mystore.get.products()
	args = {"context":CONTEXT,"products":products}
	return json.dumps(products),headers
	
	# return render_template("index.html",**args)
@app.route("/json/<id>")
def get_plans_json(id):
	"""
	This function returns the plans for a given product
	"""
	HEADER={"Content-type":"application/json"}
	try:
		mystore = store.factory.instance(name=id)
		return json.dumps(mystore.get.plans()),HEADER
	except Exception as e:
		#
		# Log this shit or not
		pass
	return "[]",HEADER

@app.route("/store",methods=['POST','PUT'])
def to_session():
	"""
	This function will store/persist session variables for reuse
	"""
	info = request.json
	if info :
		for key in info :
			session[key] = info[key]
	return "{}",200
@app.route("/signup",methods=['GET','POST'])
def signup():
	"""
	This function will signup a user to a plan or upgrade them if necessary
	The assumption here is one product - one plan
	:request.form	stripeToken (optional)
	"""
	if request.method == 'GET' :
		if 'auth' not in session :
			return "0",403
		_object = {"data-key":CONFIG['stripe']['pub'].strip()}	
		return json.dumps(_object),{"Content-Type":"application/json"}
	else:
		#
		# We are receiving a call from stripe framework and we need create a charge for this
		# @NOTE: https://stripe.com/docs/payments/accept-a-payment-charges#web-create-charge
		token 	= request.form['stripeToken'] if 'stripeToken' in request.form else None
		email 	= session['auth']['user']['uid']
		plan_id	= session['plan']['id']
		id	= session['product']['name']
		mystore = store.factory.instance(name=id,email=email)
		user	= mystore.user
		plans 	= user.plan.info()
		plans	= [plans] if isinstance(plans,dict) else plans
		
		has_plan = [1 for item in plans if item['id'] == plan_id]
		if np.sum(has_plan) == 0 and len(plans) == 0:
			
			mystore.subscribe(id=plan_id,email=email)
		elif np.sum(has_plan)==0 and len(plans) > 0:
			#
			# We should upgrade/downgrade the current plan
			#
			mystore.plan.upgrade(plan_id,email)
		uri = session['product']['metadata']['uri'] if 'uri' in session['product']['metadata'] else '#status'
		
		#
		# @NOTE:
		#	This function assumes one product one plan, operations outside of this scope will require a new function
		return """
			<script>
				window.location = ':uri'
			</script>
		""".replace(":uri",uri)

# @app.route("/ui/signup/<id>",methods=['GET','PUT'])
@app.route("/<id>/signup",methods=['GET','PUT'])
def signup_form(id):
	"""
	This function loads the signup form with cloud-view authentication support
	This allows us to know who is signing up or upgrading their plan
	"""
	
	mystore = store.factory.instance(name=id)
	plans = mystore.get.plans()
	index = int(request.args['index'])
	plan = plans[index]
	
	args = {"product":id,"label":mystore.product['statement_descriptor'],"plan":plan,"context":CONTEXT,"now":datetime.now().year}
	args['theme'] = 'theme-clouds'
	args['cloud-view'] = CONFIG['cloud-view']
	
	return render_template("signup.html",**args)
@app.route("/me")
def me ():
	#
	#{'user': {'uid': 'nyemba@gmail.com', 'uii': 'Steve Nyemba', 'usage': {'used': 1.012175691, 'size': 4.02653184, 'units': 'GB'}, 'sid': 'dropbox'}, 'key': 'nsfCKKUWunUAAAAAAAAAAUMzU6oR6qp9TK7t-_zop-sBOcEaM3eb1mRi_PG8bM69', 'features': {}}
	
	# mystore = store.factory.instance(name = session['product']['name'])
	# mystore.init(email=session['auth']['user']['uid'])
	try:
		if 'auth' in session and 'product' in session :
			email = session['auth']['user']['uid']
			name = session['product']['name']
			
			mystore = store.factory.instance(name=name,email=email)
			payments = mystore.user.invoices()
			
			cards = mystore.user.card.html() if mystore.user.card.exists() else None
			
			_args = {'context':CONTEXT,"user":session['auth']['user'],"product":session['product'],"plan":session['plan'],"payments":payments,"cards":cards}
			return render_template('me.html',**_args)
	except Exception as e:
		print (e)
		pass
	return render_template("none.html",context=CONTEXT)
	# return redirect("http://healthcareio.the-phi.com")

@app.route("/me/card",methods=['POST'])
def me_card ():
	if 'auth' in session :
		email = session['auth']['user']['uid']
		name = session['product']['name']
		mystore = store.factory.instance(name=name,email = email)
		body  = request.json
		
		try:
			mystore.user.card.add(card=body)
		except Exception as e:
			print (e)
			return "0",200
		return "1",200
		# pricing = mystore.get.checkout() #-- pricing
		# pricing = [{"unit_amount":500,"currency":"usd"},{"unit_amount":1000,"currency":"usd"},{"unit_amount":2500,"currency":"usd"}]
		# args = {"product":session['product']}
	else:
		return "0",403
	return render_template("card.html")
@app.route("/<id>/cancel",methods=['POST'])
def cancel(id):
	#
	# This function will cancel the user's current plan 
	STATUS = "1"
	try:
		
		if 'auth' in session :
			email = session['auth']['user']['uid']	
			mystore = store.factory.instance(name=id,email=email)
			_id = mystore.user.plan.sub_id()
			mystore.cancel(_id)
			session['plan'] = None
			session['payments'] = mystore.user.invoices()
		else:
			STATUS = "0"
	except Exception as e:
		print (e)
		STATUS = "0"
		pass
	return STATUS,200
# @app.route("/ui/<id>")

@app.route("/<id>/plans")
def get_plans_ui(id):
	"""
	This function loads the form for signup and information about the various plans for a given product.
	:id	name of the products
	"""	
	mystore = store.factory.instance(name=id)
	#
	# sort plans by 
	
	description = mystore.product['metadata']['about'] if 'about' in mystore.product['metadata'] else ''
	label = mystore.product['statement_descriptor']
	args = {"product":id,"label":label,"description":description,"context":CONTEXT,"plans":mystore.get.plans(),"now":datetime.now().year}
	args['product'] = mystore.product
	args['theme'] = 'theme-clouds'
	# print (mystore.product.keys())
	session['cloud-view'] = CONFIG['cloud-view'].replace(':id',id)
	session['product'] = mystore.product
	return render_template('plans.html',**args)
@app.route("/goto/<id>",methods=['POST','GET'])
def redirect(id ) :
	"""
	This function will redirect to a given product page given a payload. The payload is intended to set the a session
	:info	
	"""

	mystore = store.factory.instance(name=id)
	args 	= {"context":CONTEXT,"product":mystore.product}
	HEADER 	= {"Location":mystore.product['metadata']['uri']}
	info	= request.json
	if info :
		session['auth'] = info['auth']
		session['plan'] = info['plan']
		session['product'] = mystore.product
		return "1",HEADER
	else:

		return "0",403
	pass
@app.route("/<id>/donate")
def donate (id) :
	"""
	This will charge a given user/card for a given product. i.e it is designed for a one-time payment
	"""
	mystore = store.factory.instance(name=id)
	# pricing = mystore.get.checkout() #-- pricing
	pricing = [{"unit_amount":500,"currency":"usd"},{"unit_amount":1000,"currency":"usd"},{"unit_amount":2500,"currency":"usd"}]
	args = {"product":mystore.product,"plans":mystore.get.plans(),"context":CONTEXT,"pricing":pricing}
	
	return render_template("card.html",**args)
@app.route("/<id>/pay")
def pay(id):
	email = session['auth']['user']['uid']
	fname  	= session['auth']['user']['uii'].split(' ')[0] if 'uii' in session['auth']['user'] else ''
	lname 	= " ".join(session['auth']['user']['uii'].split(' ')[1:]) if 'uii' in session['auth']['user'] else ''
	mystore = store.factory.instance(name=id,email=email) #--
	amount	= session['plan']['amount']
	# cards = mystore.user.card.html() if mystore.user.card.exists() else None
	_args = {"product":mystore.product,"pricing":None,"context":CONTEXT,"email":email,"lname":lname,"fname":fname,"amount":amount} #,"cards":cards}
	return render_template("card.html",**_args)

@app.route("/html/dialog",methods=['POST'])
def get_dialog():
	info = request.get_json()
	
	_args = {"context":CONTEXT}
	_args = dict(_args,**info)	
	return render_template('dialog.html',**_args)
@app.route('/<id>/pay',methods=['POST','PUT'])
def _payme(id)	:
	info = request.get_json()
	
	mystore = store.factory.instance(name=id,email=info['email'])
	user 	= mystore.user
	#
	# ... Let us massage the data so it can go through properly
	_args = {}
	if 'has_card' not in info and not info['has_card'] :
		card = {"number":info['number'],"cvc":info['cvc']}
		card['exp_month'] = info['exp'].split("/")[0]
		card['exp_year'] = info['exp'].split("/")[1]
		card['name']		= " ".join([info["fname"],info["lname"]])
		_args = {"email":info['email'],"amount":info['amount'],"currency":"usd"}
	else:
		#
		# Let us make a payment using the source we already have
		card = {}
	_args['card'] = card
	
	#
	# if a plan information is provided we are running the card for the plan ?
	# 
	if 'plan' in session :
		#
		# We are dealing with a subscription here and we should run this through the subscription engine
		# and NOT charge the user immediately (handled by stripe)
		#
		# _args['amount'] = info['plan']['amount']
		plan = session['plan']
		
		_args['metadata'] = {"payment":plan['nickname'],"product":id}
		_args['plan'] = plan
		#
		# @TODO: If we have a card we must add it to the cards on file 
		#
		if card :
			user.card.add(card=card)
		if user.plan.info() :
		
			r = mystore.plan.upgrade(plan['id'],info['email'])
		else:
			r = mystore.plan.subscribe(id=plan['id'],email=info['email'])
		pass
	else:
		#
		# For now we consider this a donation or other payment
		_args['metadata'] = {"payment":"charge","product":id}
		_args['statement_descriptor'] = " ".join([str(info['amount']),id])
		
		r = user.charge(**_args)
	
	if not r :
		msg,status = "Payment error enountered",403 
	else :
		msg,status = "Payment accepted, Thank you", 200
	return Response(msg,status=status)

@app.route("/me/logout",methods=["POST","GET"])	
def logout():
	session.clear()
	return "1",200
	
@app.route("/init/<product>",methods=['POST'])
def init(product):
	"""
		This function initializes/logs a product to a given user, i.e it will create a session for users/product/plans 
		if the user has provided a user identifier it will be used as her primary email. The understanding is that a product may have multiple plans under it but always a free one
		@param uid	user's email (primary)
		@param pid	plan identifier
	"""
	#
	# get product and plans
	#
	if 'uid' in request.headers :
		uid	= request.headers['uid']
		plan_id = request.headers['pid'] if 'pid' in request.headers else None
	else:
		_info = request.json()

	mystore =  store.factory.instance(name=product,email=uid)
	
	
	user = mystore.user
	user.init (uid)
	if not user.plan.info() :
		mystore.plan.subscribe(email = user.info()['email'])
	
	
	features = user.plan.info()['metadata']['features'] if 'features' in user.plan.info()['metadata'] else {}	
	#
	#@TODO: Log that uid, has initiated use of product
	#
	HEADERS ={"content-type":"application/json"}
 
	return features,HEADERS	
@app.route("/<id>/init",methods=['POST','PUT'])
def _init(id):
    return init(id)
	
def get_plans(product) :
	lproducts = stripe.Product.list()
	lproducts = [item for item in lproducts.auto_paging_iter() if item.name == product ]
	if lproducts :
		product_id = lproducts[0].id
		alias = lproducts[0].statement_descriptor
		plans = stripe.Plan.list(product=product_id)
		i = 0
		for plan in plans :
			plan['product_alias'] = alias if alias is not None else ''
			if 'features' in plan['metadata']:
				plan['metadata']['features'] = json.loads(plan['metadata']['features'])
				plans[i] = plan
			i += 1
		if plans :
			plans = plans.data
		plans.sort(key = lambda x:x.amount)	
		
		return plans
	else:
		return []

# @app.route('/features/<product>')
@app.route('/<product>/features')
def features(product):
	"""
		This function returns the plan/features of a user for a given application if provided a uid (email)
		if no uid are provided then the function will return all the plans/features associated with the given product
		@header uid	user's email
	"""

	plans = []
	if 'uid' in request.headers :
		uid = request.headers['uid']
		couchdb = CouchdbReader(uri=CONFIG['couchdb']['uri'],dbname=product,uid=uid,create=False)
		key  = couchdb.view("users/uid_map",key=uid)
		if key :
			key = key[0]['value']
			plans = couchdb.view('users/active_plan',key=key)
			plans =  [plan['value'] for plan in plans if 'value' in plan]
		else:
			plans = []
	else:
		plans = get_plans(product)
	#
	#  formatting plans for the output
	# 
	return json.dumps(plans),{"content-type":"application/json"}

if __name__ == '__main__' :
	#
	# setup mongodb session management (not sure why)
	app.config['SESSION_TYPE'] = 'mongodb'
	app.config['MONGO_URI'] = 'mongodb://localhost:27017'
	app.config['SESSION_MONGODB_DB'] = 'sessions'
	app.config['SESSION_MONGODB_COLLECT'] = 'store'
	mongo = PyMongo(app)
	app.session_interface = MongoDBSessionInterface(app,mongo.db, 'store')

	app.debug = True ;
	app.secret_key = '360-8y-[0v@t10n]+kr81v17y'
	app.config['MAX_CONTENT_LENGTH'] = 1600 * 1024 * 1024
	Session(app)	
	app.run(port=PORT,threaded=True,host='0.0.0.0')
	# app.run() #'0.0.0.0',PORT,True,threaded=True)
