import uuid
import json
import logging
from botocore.exceptions import ClientError
from common import aws_clients
logger = logging.getLogger()

class SC_Provision(aws_clients):
    def __init__(self, event):
        super().__init__(event)
    
    def UploadCSV(self):
        '''
        uploads data from the given csv to dynamo with an initial status of NEW
        '''
        # get the csv file
        csv_bucket = self.get_config_value("csvbucket")
        csv_key = self.get_config_value("csvkey")        
        BatchId = self.get_config_value("BatchId")
        input_dict = self.s3_get_csvasdict(csv_bucket, csv_key)    
        
        # build the dynamo object
        arr = [ ]
        for item in input_dict:
            if BatchId is not None:
                item["BatchId"] = BatchId
            obj={
                "guidkey": str(uuid.uuid4()),
                "status": "NEW" ,                
                "launchparams": item
            }
            arr.append(obj)
        count = len(arr)
        # upload this to the dynamo table
        self.dynamo_write_batch(arr)
        logger.info("Loaded {} new entries to {}".format(count, self.getDynamoTableName()))
        return(self.generate_return(count))

    def TerminateProducts(self):
        '''
        Terminates entries with the incmoing status 
        '''
        qry_status = self.get_config_value("status")
        new_items = self.dynamo_query("status",qry_status)
        logger.info("Found {} items to Terminate with status {}".format(len(new_items), qry_status))
        #track success and failure
        count = 0
        errors = 0
        for drow in new_items:
            # setup variables to use later
            failed = False
            errordetails = None
            # keep the error message if we have one
            if "errordetails" in drow:
                errordetails = drow["errordetails"]                
            dbstatus = "TERMINATING"
            ppid= drow["scproductdetails"]["ProvisionedProductId"]
            param_dict = drow["launchparams"]
            guidkey = drow["guidkey"]            
            ppdetails = {
                'ProvisionedProductId':ppid,
                'RecordId':drow["scproductdetails"]['RecordId'],
                'CreatedTime':str(drow["scproductdetails"]['CreatedTime']),
                'Status':drow["scproductdetails"]['Status']
            }
            
            try:
                # terminate the product and record the important parts of the response
                resp = self.sc_terminate_product(ppid)
                ppdetails['RecordId']=resp['RecordDetail']['RecordId']
                ppdetails['Status'] = resp['RecordDetail']['Status']                    
            except ClientError as ce:
                msg = ce.response['Error']['Message']
                if msg.startswith("Provisioned product not found: "):
                    #This was a good status termination, lets mark it and we're done with it now.
                    dbstatus = "TERMINATED"
                    ppdetails["Status"] = "TERMINATED" 
                    count += 1
                else:
                    # something wrong from the API call. this is where you will see the CFn Errors
                    logger.error("ClientError: {}".format(msg))    
                    errordetails = ce.response['Error']
                    failed = True
            except Exception as e:
                # Something else wrong?
                logger.error(e)                
                failed = True
            else:
                count += 1
                                    
            if failed:
                errors += 1
                dbstatus = "TERMINATION-ERROR"
            
            #update the dynamo table
            self.updateItem(guidkey,drow["status"],dbstatus, param_dict, ppdetails, errordetails)
        if len(new_items) > 0:
            logger.info("Terminated {} of {} products with {} errors using status:{}".format(count, len(new_items), errors, qry_status))
        return(self.generate_return(count))
        
    def ProvisionProducts(self):
        '''
        This will go through the Dynamo table from the config and provision the given product using the params from matching the incoming scparams list.
        '''
        # config overides SSM
        sc_param_keys = self.get_config_value("scparams").split(',')
        sc_productid = self.get_config_value("scproductid")
        sc_paid = self.get_config_value("scpaid")
        provision_threshold = self.get_config_value("provision_threshold")
        if provision_threshold is None:
            provision_threshold = 10
        
        # try to get them from SSM
        if sc_productid is None:
            sc_productid = self.ssm_get_parameter("/bulkdeploy/productid")
        if sc_paid is None:
            arr = self.ssm_get_parameter("/bulkdeploy/provisioningid").split('|')
            # grab the first if there are many
            sc_paid = arr[0]
        
        # get new items        
        new_items = self.dynamo_query_multi("status",["NEW","RETRY"])
        logger.info("Found {} new items to provision".format(len(new_items)))
        count = 0
        errors = 0
        for drow in new_items[:provision_threshold]:
            # setup variables to use later
            arr_params = []
            guidkey = drow["guidkey"]                                                    
            param_dict = drow["launchparams"]
            failed = False
            ppdetails = None
            errordetails = None
            dbstatus = "PROVISIONING"
            # keep the error message if we have one
            if "errordetails" in drow:
                errordetails = drow["errordetails"]
            
            try:
                # Map the incoming launchparams from the dynamo entry to the launch params from our event config
                for paramkey in sc_param_keys:
                    par_obj = { "Key":paramkey,"Value":param_dict[paramkey] }
                    arr_params.append(par_obj)               
                
                # provision the product and record the important parts of the response
                resp = self.sc_provision_product(sc_productid, sc_paid, guidkey, arr_params)
                ppdetails = {
                    'ProvisionedProductId':resp['RecordDetail']['ProvisionedProductId'],
                    'RecordId':resp['RecordDetail']['RecordId'],
                    'CreatedTime':str(resp['RecordDetail']['CreatedTime']),
                    'Status': resp['RecordDetail']['Status']
                }
            except ClientError as ce:
                # something wrong from the API call. this is where you will see the CFn Errors
                msg = ce.response['Error']['Message']
                logger.error("ClientError: {}".format(msg))
                if ce.response['Error']['Code'] == "InvalidParametersException" and msg == "A stack named {} already exists.".format(guidkey):
                    # we got a duplicate???  just fail it, no need to retry
                    errors += 1
                    dbstatus = "DUPLICATE"
                else:
                    errordetails = ce.response['Error']
                    failed = True            
            except  KeyError as ke:
                msg = "PROVISION-ERROR: Keys from CSV do not match template! Could not find {}".format(ke)
                logger.error(msg)
                errordetails = msg
                failed = True            
            except Exception as e:
                # Something else wrong?
                logger.error(e)                                
                failed = True
            else:
                count += 1
            
            # mark the status according to the outcome from above            
            if failed:
                errors += 1
                dbstatus = "PROVISION-ERROR"
                
            #update the dynamo table
            self.updateItem(guidkey,drow["status"],dbstatus, param_dict, ppdetails, errordetails)            
                    
        if len(new_items) > 0:
            logger.info("Provisioned {} of {} new products with {} errors using ProductID:{} PAID:{}".format(count, len(new_items), errors, sc_productid, sc_paid))
        return(self.generate_return(count))