"""
This file is a wrapper for stripe and store handling operations :

@TODO:
    - Add logging using transport
"""
import stripe
from utils.params import PARAMS as SYS_ARGS
from utils import void
import json
class User :
    def __init__(self,email,plans=None):
        self.plans = plans
        self.me = {"plan":[],"customer":{},"payments":[],"plan2sub":{}}
        self.init(email)
        self.format() #-- creating properties
        pass
    def info(self):
        return self.me['customer']
    def _add_card(self,**_args):
        """
            creating a card token, we do not have to save this it could just be used as a one-time thing,
            This will automatically add a card to the users session profile (if she has not specified to save the card)
            :number
            :cvc
            :exp mm/yyyy
            
        """
        try:
            card = dict(_args['card'] )
            
            values = card['exp'].split('/') if 'exp' in card else None,None
            
            if 'exp' in card :
                
                card['exp_month'] = card['exp'].split('/')[0]
                card['exp_year'] = int(card['exp'].split('/')[1])
                del card['exp']
            for field in ['name','email'] :
                    
                if field in card : 
                    self.me['customer'][field] = card[field]
                    del card[field]
            
            # tok_1HPyukEUWsmgY81ALQuKGBmT
            # address information should be added to the card
            #
            # save_card = _args['save'] if 'save' in _args else 0
            # if save_card :
                # r = stripe.token.Token.create(card=card)
            # else:
            #
            # Links the customer to the source ..
            if 'currency' in card :
                currency = card['currency']
                del card['currency']
            else:
                currency = 'usd'
            for item in self.me['payments'] :
                stripe.customer.Customer.delete_source(self.me['customer']['id'],item['id'])
            #
            #
            
            owner = {"email":self.me['customer']['email'],"name":self.me['customer']['name']}
            # r = stripe.source.Source.create(type='card',currency=currency,card=card)
            r = stripe.token.Token.create(card=card)
            
            o = stripe.source.Source.create(type='card',currency=currency,owner=owner,token=r.id,usage='reusable')
            o = stripe.customer.Customer.create_source(self.me['customer']['id'],source=o.id)
            # r = stripe.source.Source.create(type='card',currency=currency,card=card)
            self.me['payments'] = [r.card.to_dict_recursive()]+self.me['payments']
            self.init(self.me["customer"]['email'])
            return r
        except Exception as error :
            print ([' ** ',error])
        return 0
    def invoices(self):
        # r = stripe.charge.Charge.list(customer=self.me['customer']['id']).data
        r = []
        _logs = stripe.invoice.Invoice.list(customer=self.me['customer']['id'])
        for item in _logs.auto_paging_iter() :
            r += [{"id":item.id,"date":item.date*1000,"amount":item.amount_paid,"paid":item.paid,"status":item.status,"label":item.lines.data[0].description,"invoice":item.id,"pdf":item.invoice_pdf,"hosted":item.hosted_invoice_url} ]
            # r += [{"id":item.id,"date":item.created*1000,"amount":item.amount,"paid":item.paid,"status":item.status,"label":item.statement_descriptor if item.statement_descriptor else item.calculated_statement_descriptor,"invoice":item.invoice} for item in logs]
        return r
    def charge(self,**args) :
        """
        This function will charge a user for a service (given a plan id)
        :card|token     {number,cvc,exp_month,exp_year,[address_1,city,zip,state]}
        :amount
        :email
        :
        """
        # plan_id     = args['plan_id']
        # if len(self.me["payments"]) == 1 :
        #     card_index = 0
        # else:
        #     card_index  = args['card_index'] 
        # plan = [item[plan_id] for item in self.plans if item['id'] == plan_id]
        
        # card = {} if plan['amount'] == 0 else self.me["payments"][card_index]
        # params = {}
        
        # params['customer']  = self.me["customer"]['id']
        # params ['amount']   = plan['amount']
        # params ['currency'] = plan['currency']
        # params ['description'] = " ".join([self.product['statement_descriptor'],plan['nickname']])
        # if 'source' in card :
        #     params ['source']   = card['source'] 
        
        #
        # Lets create a token for the card 
        #
        
        token = self.card.add(card = args['card'])     
        if token != 0 :
            _args = {"customer":self.me["customer"]["id"], "receipt_email":self.me['customer']['email'],"amount":args['amount'],"currency":"usd"}
            _args["source"] = token["id"]
            if 'metadata' in args :
                _args['metadata'] = args['metadata']
            # stripe.customer.Customer.update(self.me['customer']['id'],source=token['id'])
            _args['description'] = args['description']
            stripe.charge.Charge.create(**_args)
            # if 'save' not in args :
            #     #
            #     # drop the card ...
            #     stripe.customer.Customer.delete_source(self.me['customer']['id'],token['id'])
            # del params['amount'], params['currency']

            # stripe.invoice.Invoice.create(**params) 
            return 1
        else:
            #
            # An invalid card number or cvc we returned
            return 0
        pass
    
    def format(self):
        
        self.plan = void()
        self.plan.info =  lambda: self.me["plan"] 
        self.plan.sub_id = lambda: self.me['plan2sub'].get(self.plan.info()['id'],{})
        # self.user.plan.cancel = lambda id: stripe.subscription.Subscription.delete(self.customer['id'],prorate=True,invoice_now=True)

        self.card = void ()
        self.card.exists    = lambda: len(self.me["payments"]) > 0
        self.card.add       = self._add_card
        self.card.charge    = self.charge
        self.card.html      = lambda: [{"number":card['last4'],"type":card['brand']} for card in self.me["payments"]]
        self.card.delete    = lambda index: stripe.customer.Customer.delete_source(self.me["customer"]['id'],self.me["payment"][index])
    
    def init(self,email):
        #
        # get customer information
        # email = args['email']
        # self.user_plan  = []
        # self.customer   = {}
        customer = stripe.customer.Customer.list(email=email).to_dict_recursive()['data']
        free_plan = [item for item in self.plans if 'amount' in item and item['amount'] == 0]
        if customer :
            customer = customer[0]
        else:
            #
            # The user doesn't exist, we must create the user (without payment initially)
            # We can probably assign the user to a free plan if available
            #
            customer = stripe.customer.Customer.create(email=email)
            
            # if free_plan :
            #     free_plan = free_plan[0]
            #     self.plan.subscribe(free_plan['id'],email)
        # self.customer = {"email":email,"id":customers['id']}
        self.me["customer"] = {"email":email,"id":customer['id']}
        lsub = customer.subscriptions.data
        for sub in lsub :
            
            id =  sub.plan.id
            self.me['plan2sub'][id] = sub.id
            
        #
        # extract payments available,
        if 'sources' in customer and 'data' in customer['sources'] :
            if customer['sources']['data'] :
                # self.payment = [ card.to_dict_recursive() for card in customers['sources']['data'] ]
                self.me['payments'] = [ card.card.to_dict_recursive() if 'card' in card else card.card.to_dict_recursive() for card in customer['sources']['data'] ]
        # if not self._has_plan(customer) and free_plan:
        #     free_plan = free_plan[0]
        #     self.subscribe(free_plan['id'],email)
        # info = customers #self.me['customer']
        # subscriptions = info['subscriptions']['data']
        # ids = [plan['id'] for plan in self.plans ]
        # _found = None
        # for sub in subscriptions :
        #     aplan = sub['plan']
            
        #     if set([aplan['id']]) & set(ids):
        #         _found = sub.to_dict_recursive()                       
        #         break
        self.me["plan"] = self._has_plan(customer) #_found if _found else []
        # self.user_plan = _found if _found else None 
    def _has_plan(self,customer):
        subscriptions = customer['subscriptions']['data']
        
        ids = [plan['id'] for plan in self.plans if 'id' in plan and plan['active'] == True]
        _found = None
        for sub in subscriptions :
            aplan = sub['plan']
            
            if set([aplan['id']]) & set(ids):
                _found = sub.to_dict_recursive()['plan']
                if 'features' in _found['metadata'] :
                    _found ['metadata']['features'] = json.loads(_found['metadata']['features'])
                break
        return _found if _found else []
class Store :
    def __init__(self,**args):
        """
        Creating an instance of a store. A store is a proxy to stripe (https://stripe.com)
        :name   name of the product
        :secret stripe api key
        """
        self.products   = []
        self.plans      = []
        self.product    = {}
        
        product_name    = args['name']
        stripe.api_key  = args['secret'] if 'secret' in args else stripe.api_key
        resp = stripe.product.Product.list().to_dict_recursive()['data']
        if resp :
            self.products = [item.to_dict_recursive() for item in resp]
            if len(self.products) > 1 :                
                self.product = [item for item in self.products if item['name'] == product_name]
                self.product = self.product[0] if self.product else []
            else:
                self.product = self.products[0]
        #
        # if we have a product we should get the list of plans associated
        #
    def init(self,email):
        """
        This function pulls a user's information, think of it like a customer walks into a store ...
        :email
        """
        pass
class Product(Store):
    """
    This class implements a wrapper around products, services or just simple donations
    we are inheriting product, plans
    """
    def __init__(self,**args):
        Store.__init__(self,**args)

    def set(self,key,value):
        if key == 'plan' :
            #
            # do we have this plan
            found = [item for item in self.plans if item['id'] == value['id']]
            if not found :
                plan = value
            else:
                plan = dict(found[0],**value)
            if 'metadata' in value :
                plan['metadata'] = self.meta(plan,value)
                self.save('plan',plan)
            
            pass
        else :
            if key in ['type','name','id','statement_descriptor'] :
                if value :
                    self.product[key] = value
                elif key in self.product :
                    del self.product[key]
            elif key == 'metadata' :
                self.product['metadata'] = self.meta(self.product,value)
            self.save('product',self.product)

        
    def meta(self,object,value) :
        
        for k in value :
            if value[k] :
                object['metadata'][k] = json.dumps(value[k]) if type(value[k]) == dict else value[k]
            else:
                del object['metadata'][k]
        return object

    def save(self,id,object):
        pointer = None
        if id == 'product' :
            p = 'about' in object['metadata']
            q =  'uri'  in object['metadata']
            if p and q :
                pointer = stripe.product.Product
                return 1
            else :
                return 0
        elif self.product['id']:
            pointer = stripe.plan.Plan
            object = dict(object,**{"product":{"name":self.product['id']}} )
        if pointer :
            pointer.create(**object)
            return 1
        return 0

    def get(self):
        pass
class Plans(Store) :
    def __init__(self,**args):
        """
        This function will retrieve plan information associated with the given product id
        """
        Store.__init__(self,**args)
        contract = {"nickname":"Enterprise","metadata":{}} 
        info = stripe.price.Price.list(product=self.product['id']).data
        self.plans = []
        self.checkout = []
        for item in info :
            setattr(item,'amount',item.unit_amount)
            _item = item.to_dict_recursive()
            _item['amount'] = item['unit_amount']
            if item.active == False :
                continue
            if item.recurring == None :
                self.checkout.append(_item)
            else:
                self.plans.append(_item)
            if 'features' in item.metadata :
                _item['metadata']['features'] = json.loads(item.metadata['features'])
                if 'contract' in self.product['metadata'] and not contract['metadata']:
                    contract['metadata']={'features':{}}
                    for feature_key in _item['metadata']['features'] :
                        contract['metadata']['features'][feature_key] = 1

            else:
                _item['metadata']['features'] = {}
                # self.plans.append(_item)
            # index = self.plans.index(item)
            # self.plans[index] = item.to_dict_recursive()
            # if 'features' in self.plans[index]['metadata'] :
            #     self.plans[index]['metadata']['features'] = json.loads(self.plans[index]['metadata']['features'])
            # elif self.plans
        self.plans.sort(key=lambda item: item['unit_amount'])
        self.checkout.sort(key=lambda item:item['unit_amount'])
        if contract['metadata'] :
            self.plans.append(contract)
 
        if 'email' in args :
            self.user = User(args['email'],self.plans)
        else:
            self.user = None
        # self.plans = [item.to_dict_recursive() for item in self.plans]
        self.format()
    def format(self):
        self.get = void()
        self.get.plans      = lambda:self.plans
        self.get.checkout   = lambda: self.checkout
        self.get.products   = lambda:self.products
        self.plan = void()
        self.plan.upgrade   = self.upgrade
        self.plan.cancel    = self.cancel        
        self.plan.subscribe = self.subscribe
    def cancel(self,id) :
        """
        This function will cancel a subscription given a user's email/or a subscription for the given product/user
        We are assuming one subscription one plan, no complex billing is involved (not good for startups)
        :email,subscription
        """
        stripe.subscription.Subscription.delete(id,prorate=True,invoice_now=True)
        pass
    def upgrade(self,id,email):
        """
        A change of plan suggests cancelling the current plan and assigning a new one.
        If it's a paid plan, the calling code must insure the user has an active token
        """
        if not self.user and email:
            self.user  = User(email,self.plans)

        # sub_id = self.user.plan.info()['id']
        sub_id = self.user.plan.sub_id()
        try:
            self.cancel(sub_id)
            r = self.subscribe(id=id,email=email)
            #
            # We need to reload everything
            self.user.init(email)
        except Exception as error :
            print (error) ;
            return 0
        return 1

        pass
    # def subscribe(self,id,email):
    def subscribe(self,**args):
        """
        Subscribe a user to a given plan assuming the user has already been initialized
        The responsibility falls upon the calling code to perform the cancellatioin of the existing plan and perform an upgrade
        :email  user's email
        :id     plan id (optional) will default to free plan
        """
        free_plan = [item['id'] for item in self.plans if item['amount'] == 0]
        if free_plan :
            free_plan = free_plan[0]
        # id = args['id'] if 'id' in args else free_plan
        email = args['email']
        if not self.user :
            self.user = User(email,self.plans)
        
        
        plan = {"plan": args['id'] if 'id' in args else free_plan}
        amount = sum([item['amount'] for item in self.plans if item['id'] == 0])

        if 'email' in self.user.info() and email == self.user.info()['email'] :
            email  = self.user.info()['email']
        else:
            self.user.init(email)
        #
        # We must insure the user is one of our customers i.e perhaps
        if amount == 0 or self.payment and plan:
            r = stripe.subscription.Subscription.create(
                customer = self.user.info()['id'],
                items = [plan]
            )
            # stripe.subscription.Subscription.create(
            #     customer=self.user.info()['id'], #if not email else email,
            #     items=[plan]
            # )

            self.user.init(email)
            return 1
        else:
            return 0
class factory:
    @staticmethod
    def instance (**args) :
        p =  Plans(**args)
        # p.user.subscribe = p.subscribe
        return p
            
 
