"""
	This class is designed to handle Customers in coordination with stripes.
	
	Identity Federation
		A user with several emails should not have to pay for a service more than once because she has several email/cloud accounts.
		We have therefore decided to tie several emails to a stripe customer identity
	
"""
import stripe
from couchdbkit import Server, Document
import json

from sets import Set
class User:
	def __init__(self,db,stripe=None,stripeToken=None) :
		self.db 	= db
		self.stripe 	= stripe;
		self.stripeToken= stripeToken
		self.user = None
	def initialize(self,uid) :
		id = self.getId(uid)

		if id is not None and self.db.doc_exist(id) :
			self.user = self.db.get(id)
	"""
		This function will cast an object to JSON, 
		It is designed to address an inherent limitation of stripe object hierarchy

	"""
	def cast(self,object) :
		return json.loads(json.dumps(object, sort_keys=True, indent=2))
	"""
		This function creates a stripe customer, and saves a copy with us for internal use
		We use couchdb as a cache of sorts 
		@param uid	customer's email
		@param plans	list of plans the user is subscribing 
	"""
	def getId(self,uid):
		r = self.db.view('federation/uid_map',key=uid)
		
		if r.count() > 0 :
			r  = r.first()
			return r['value']
		else:
			return None
	
	def init (self,uid,plans):
		customer = {}
		
		id = self.getId(uid)
		if id is not None:
			self.user = self.db.get(id)
		# if self.db.doc_exist(uid) :
			# self.user = self.db.get(uid) 
		
		if self.stripeToken is not None:
			
			if 'sources' not in self.user or self.user['sources'] is None:
				#@TODO: get the appropriate id
				# customer = stripe.Customer.retrieve(self.user['id'])
				#customer = stripe.Customer.retrieve(id)
				#customer.source=self.stripeToken
				#customer.save()
				pass
		# self.hasPlan(uid,plan)
		has_plan = True
		if  id is None :
			#
			# First time customer, register them and sign them up for the first plan
			#
			#customer['sources'] = str(self.stripeToken)							
			customer = self.stripe.Customer.create(
					source=self.stripeToken,
					email=uid
					
			) ;
			#
			# The customer is being persisted on our end
			# We do this considering user federations i.e several emails attached to an account
			#
			has_plan = False
			id = customer['id']
			#self.user = {"_id":uid,"id":id,"source":self.stripeToken}			
			#
			# At this point we have a new user and a plan identifier
			# We formt the plan as it is given to us as a string ... later will be subscribed.
			#
			
			self.user = {"_id":id,"emails":[uid],"source":self.stripeToken}			
		else:
			#
			# The user exists but let's see if the user is subscribed to this plan
			# If she is and the plan is still live then there is nothing to do
			#
			
			#self.user = self.db.get(uid) ;
			self.user = self.db.get(id)
			# id = self.user['id']
		#
		# If this user's isn't already subscribed we should subscribe her
		# We perform a set operation to determine if she is alread susbscribed
		#	

		plans = [{"id":plans}]
		lsub 	= self.subscriptions()					
		if len(lsub.data) > 0 and len(plans[0]["id"]) > 0:
			lplans 	= [str(item['plan']['id']) for item in lsub.data if item.ended_at in [None,""]]
			x_plans	= [item['id'] for item in plans]
			print lplans,'\n',plans
			if lplans and not set(x_plans) - set(lplans) :
				has_plans = False
				x = list(set(x_plans) - set(lplans))
				plans = [ item for item in plans if item['id'] in x]			
			else:
				#
				# In case the user doesn't have any plans with us
				#
				has_plan = False

			
		if has_plan == False :			
			r = self.subscribe(id,plans)			
			lsub.data.append(r[0])
		
		#
		# Backing up the information to be processed later 
		#	- We assume any interaction with the payment classes will start by updating information associated operation
		lsub = self.cast(lsub.data)
		self.user['subscriptions'] = lsub
		self.db.save_doc(self.user)
	def subscriptions(self):
		#
		# call stripe to retrieve subscriptions for this user ...
		#
		
		if self.user is not None :
			id = self.user['_id']
			r = stripe.Customer.retrieve(id)							
			return r.subscriptions
		else:
			return []
	def plans(self):
		lsub = self.subscriptions()	
		plans = [ dict({"status":sub['status']},**sub['plan']) for sub in lsub if sub['ended_at'] is None  ]
		return plans		
		
	"""
		This function subscribes a customer to n-plans
		@pre isinstance(plans,list)
	"""
	def subscribe(self,id,plans):
		r = []
		for plan in plans:
			
			if self.stripeToken is None:
				sub = self.stripe.Subscription.create(
						customer=id,
						plan=plan['id']
				) ;
			else:
				#
				# The provided token has been updated on the customer's profile (init function)
				# Because of this we can just subscribe her and the card will be used accordingly
				#
				customer 	= stripe.Customer.retrieve(id)				
				self.user['sources'] = self.cast(customer.sources)				
				sub = self.stripe.Subscription.create(
						customer=id,
						plan=plan['id'],
						source=self.stripeToken
				) ;
			r.append(sub)
				
			#
			# We need to create an invoiced item out of this plan
			# We will attach it later to an invoice ...
			#
			#r.append(x)
		return r
	def invoice(self,uid,sid):
		#self.user = self.db.get(uid)
		#id = self.user['id']
		id = self.getId(uid) ;
		r = [ stripe.InvoiceItem.create(customer=id,amount=item['amount'],currency=item['currency'],description=item['statement_descriptor'],subscription=item['subscription'],invoice=iid) for item in plans]
		return r
	"""
		This function creates an invoice
		@pre : an item with amount > 0 and status past_due
	"""		
	def get_invoices(self,uid):
		#info = self.db.get(uid)
		#id = info['id']
		id = self.getId(uid)
		return stripe.Invoice.list(customer=id)
		
	"""
		This function will clear a particular invoice (hopefully). No partial payments should be accepted
		@pre : card,invoice
	"""					
	def charge(self,uid,token,plans):
		
		#info 	= self.db.get(uid)
		#id	= info['id']
		id 	= self.getId(uid)
		sid	= plans[0]['subscription']
		user 	= stripe.Customer.retrieve(id)
		user.source = token
		user.save()
		for plan in plans:
			sid = plan['subscription']
			if plan['amount'] > 0 :
				_invoice= stripe.Invoice.create(customer=id,subscription=sid)
				r = _invoice.pay()
		
	"""
		This function is designed to determine if the user exists or not
		We will check the couchdb and the stripe backend
	"""
	def exists(self,uid):
		return self.db.doc_exist(uid)
		# return self.user is not None ;
	
	"""
		This function will store the user within our system
	"""
	def attach(self,attachment,name,mime):
			self.db.put_attachment(self.user,attachment,name,mime) ;	

	def save(self,uid=None):
		if self.exists() == False:
			self.init(uid) ;
		else:
			#perform an update
			pass
	def update_user(self,**args) :
		id = args['id']
		uid= args['uid']
		print ['update ...',id,uid]
		self.user = self.db.get(id)
		self.user['emails'].append(uid)
		self.user['emails'] = list(set(self.user['emails']))
		self.db.save_doc(self.user)
#f = open('config.json')
#conf = json.loads(f.read())
#f.close()
#server = Server(uri=conf['couchdb']['uri']) ;
#db = server.get_db(conf['couchdb']['db']) ;
#print db.doc_exist('steve@gmail.com')
#doc = db.get('steve@gmail.com')
#doc['type'] = 'business' 
#db.save_doc(doc) ;
