import json
import urllib3
from concurrent.futures import as_completed
from requests_futures.sessions import FuturesSession
import requests
import pkgutil
from loguru import logger
import time
from .helpers import _get_instance_ids_from_names

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

def get_lab_terraform_inputs(morpheus_custom_options):
    """
    The get_lab_terraform_inputs function takes the provided Morpheus custom options and parses them to determine which template parameters need to be filled in. It then fills in the appropriate values for those parameters and returns a dictionary of all of the template parameters with their corresponding values.
    
    :param morpheus_custom_options: Pass in the custom options that have been provided by the user
    :return: A dictionary of the key value pairs that should be used for the lab
    :doc-author: Trelent
    """
    # Validate the class type and import the correct file template for the template parameters to fill in
    if "administration" in morpheus_custom_options["class_type"]:
        logger.info(f'Administration class selected. Importing admin_class_config.json')
        f = pkgutil.get_data(__name__, "template_files/admin_class_config.json")
        f = json.loads(f)
    elif "installation" in morpheus_custom_options["class_type"]:
        logger.info(f'Installation class selected. Importing instal_class_config.json')
        f = pkgutil.get_data(__name__, "./template_files/install_class_config.json")
        f = json.loads(f)
    elif 'automation' in morpheus_custom_options["class_type"]:
        logger.info(f'Automation class selected. Importing automation_class_config.json')
        f = pkgutil.get_data(__name__, "./template_files/automation_class_config.json")
        f = json.loads(f)
    elif 'troubleshooting' in morpheus_custom_options["class_type"]:
        logger.info(f'Troubleshooting class selected. Importing troubleshooting_class_config.json')
        f = pkgutil.get_data(__name__, "./template_files/troubleshooting_class_config.json")
        f = json.loads(f)
    terraform_inputs = {}
    # Parse the key value pairs of the provided Morpheus custom options
    for k, v in morpheus_custom_options.items():
        logger.info(f'Checking for a match on the option type: {k}')
        try: 
            if k in f:
                logger.info(f'Found {k}, updating with a value of {v}')
                update = {k: v}
                terraform_inputs.update(update)
            else:
                logger.info(f'{k} not found in the template. Skipping it.')
        except Exception as e:
            logger.error(f'Something went wrong {e}')
    for k, v in f.items():
        try:
            if v :
                logger.info(f'Item {k} with a value of {v} discovered. Adding it to the payload.')
                update = {k: v}
                terraform_inputs.update(update)
            else:
                logger.info(f'{k} does not have a default value in the template. Skipping it')
        except Exception as e:
            logger.error(f'Something went wrong: {e}')
    return(terraform_inputs)

def get_terraform_layout_id(url,headers,instance_type_code):
    """
    The get_terraform_layout_id function takes in a url, headers and instance_type_code.
    It then makes a request to the AS API for all of the instance type layouts that have terraform as their provisionTypeCode.
    The function then returns the layout id of that specific layout.
    
    :param url: Pass in the url of the instance type
    :param headers: Pass in the api key
    :param instance_type_code: Get the instance type code from the instance_type_code variable
    :return: The layout id for the terraform provision type
    :doc-author: Trelent
    """
    endpoint = "/api/library/instance-types"
    try:
        logger.info(f'Attempting to get the instance type with the code of: {instance_type_code}')
        resp = requests.get(f'{url}{endpoint}?code={instance_type_code}', headers=headers, verify=False).json()["instanceTypes"][0]["instanceTypeLayouts"]
    except Exception as e:
        logger.error(f'Something went horribly wrong here: {e}')
    for l in resp:
        if "terraform" in l["provisionTypeCode"]:
            layout_id = l["id"]
    return(layout_id)

def get_morpheus_terraform_plan_id(url,headers):
    """
    The get_morpheus_terraform_plan_id function is used to get the id of the Morpheus Terraform plan.
    This function returns a single integer value which represents the ID of the plan.
    
    :param url: Specify the morpheus api url
    :param headers: Pass the token generated by morpheus
    :return: The id of the morpheus terraform plan
    :doc-author: Trelent
    """
    endpoint = "/api/service-plans?phrase=Terraform"
    try:
        logger.info(f'Attempting to get the Morpheus Terraform plan id.')
        resp = requests.get(f'{url}{endpoint}', headers=headers, verify=False).json()
    except Exception as e:
        logger.error(f'Something went horribly wrong {e}')
    return(resp["servicePlans"][0]["id"])

def get_instance_provisioning_payload(zone_id,instance_name,site_id,instance_type,instance_type_code,layout_id,plan_id,template_parameters,tags:list):
    """
    The get_instance_provisioning_payload function takes the provided variables and loads a template file that contains
    the payload for the instance provisioning API call. The function then updates the template with all of the provided 
    variables, and returns it as a JSON object.
    
    :param zone_id: Identify the zone in which to create the instance
    :param instance_name: Name the instance
    :param site_id: Determine the site to which the instance will be attached
    :param instance_type: Determine the instance type code for the payload
    :param instance_type_code: Determine the type of instance to be provisioned
    :param layout_id: Specify the layout of the instance
    :param plan_id: Determine the instance type
    :param template_parameters: Pass in the parameters that are needed to provision an instance
    :param tags:list: Add tags to the instance
    :return: A payload template that can be used to provision an instance
    :doc-author: Trelent
    """
    try:
        logger.info(f'Attempting to load the payload template file')
        f = pkgutil.get_data(__name__, "template_files/instance_provisioning_payload.json")
        f = json.loads(f)
    except Exception as e:
        logger.error(f'Something has gone awry. {e}')
    try:
        logger.info(f'Updating the payload template with the provided variables')
        f["zoneId"] = zone_id
        f["instance"]["name"] = instance_name
        f["instance"]["site"]["id"] = site_id
        f["instance"]["type"] = instance_type
        f["instance"]["instanceType"]["code"] = instance_type_code
        f["instance"]["layout"]["id"] = layout_id
        f["instance"]["plan"]["id"] = plan_id
        f["config"]["templateParameter"] = template_parameters
        f["tags"] = tags
    except Exception as e:
        logger.error(f'Terrible failure herein {e}')
    return(f)

def set_cloud_availability(url,headers,cloud_id:int,available:bool = None):
    """
    The set_cloud_availability function takes a cloud_id and an availability status.
    It then uses the location in conjunction with the Available Clouds option list to update the location to set availability.
    
    
    :param url: Set the url for the api call
    :param headers: Pass the authentication token to the api call
    :param cloud_id:int: Identify the cloud to update
    :param available:bool: Set the cloud to available or used
    :return: A dictionary of the new cloud location
    :doc-author: Trelent
    """
    # Uses the location in conjunction with the Available Clouds option list to update the location to set availability
    try:
        logger.info(f'Attempting to get the cloud data')
        endpoint = "/api/zones"
        get = requests.get(f'{url}{endpoint}/{cloud_id}', headers=headers, verify=False).json()
        logger.info(f'Parsing the response...')
        location = get["zone"]["location"].split("_")[0]
    except Exception as e:
        logger.error(f'Something went wrong {e}')
    logger.info(f'Setting up the payload')
    if available:
        newLoc = f'{location}_available'
    else:
        newLoc = f'{location}_used'
    body = json.dumps({"zone":{"location": newLoc}})
    try:
        logger.info(f'Attempting to set location on the cloud to {newLoc}')
        put = requests.put(f'{url}{endpoint}/{cloud_id}', headers=headers, verify=False, data=body).json()
    except Exception as e:
         logger.error(f'Something went wrong {e}')
    return(put)

def deploy_class_labs(url,headers,payloads):
    """
    The deploy_class_labs function takes a list of payloads and deploys them to the AS instance.
    It returns a list of instances that were deployed.
    
    :param url: Specify the url of the instance endpoint
    :param headers: Pass in the authentication token
    :param payloads: Pass in the payloads that were generated by the generate_payloads function
    :return: A list of instance ids
    :doc-author: Trelent
    """
    logger.info(f'Atttempting to provision {len(payloads)} instances')
    session = FuturesSession()
    instances = []
    endpoint = "/api/instances"
    futures=[session.post(f'{url}{endpoint}', headers=headers, timeout=None, verify=False, data=json.dumps(p)) for p in payloads]
    for future in as_completed(futures):
        logger.info(f'Attempting to deploy instance for student')
        try:
            resp = future.result()
        except Exception as e:
            logger.error(f'Something went wrong: {e}')
        if "200" in str(resp):
            logger.info("It appears the initiation of the deployment was successful")
            instance = resp.json()["instance"]["id"]
            logger.info(f'Deploying instance: {instance}')
            instances.append(instance)
        elif "504" in str(resp):
            logger.info(f'The connection has timed out on initiating the instance provisioning: {resp}')
            logger.info(f'Let\'s try to find them by name')
            for p in payloads:
                labs = []
                labs.append(p["instance"]["name"])
                instances = _get_instance_ids_from_names(url,headers,labs)
        elif "405" in str(resp):
            logger.error(f'Hit a 405 error. Got some bad formatting?')
        else:
            logger.error(f'An error occurred: {resp}')
    return(instances)

def lock_instance(url,headers,ids):
    """
    The lock_instance function takes a list of instance ids and attempts to lock them.
    If successful, the function returns a list of locked instances.
    
    :param url: Pass the url of the instance to be locked
    :param headers: Pass the authentication token to the api call
    :param ids: Pass the instance ids to be locked
    :return: A list of locked instances
    :doc-author: Trelent
    """
    locked_instances = []
    logger.info(f'Attempting to lock instances: {ids}')
    session = FuturesSession()
    endpoint = "/api/instances/"
    futures=[session.put(f'{url}{endpoint}{i}/lock', headers=headers, verify=False) for i in ids]
    for future in as_completed(futures):
        resp = future.result()
        if "200" in str(resp):
            instance = resp.json()
            logger.info(f'Instance is now locked')
        else:
            logger.error(f'Instance lock failed: {instance}')
    return()

def await_instance_deployment_status(url,headers,instance_ids):
    """
    The await_instance_deployment_status function is used to check the status of instances that are being provisioned.
    It will continue to check the status until all instances have a running or failed state. It returns a dictionary with two keys: &quot;running_instances&quot; and &quot;failed_instances&quot;. The value for each key is an array of instance IDs.
    
    :param url: Pass the url of the instance deployment api
    :param headers: Pass the api key to the function
    :param instance_ids: Pass in the list of instance ids that are being checked
    :return: A dictionary of running and failed instances
    :doc-author: Trelent
    """
    session = FuturesSession()
    endpoint = "/api/instances/"
    provisioning_instances = len(instance_ids)
    running = []
    failed = []
    while provisioning_instances > 0:
        logger.info(f'Current instance IDs to check: {instance_ids}')
        futures=[session.get(f'{url}{endpoint}{i}', headers=headers, verify=False) for i in instance_ids]
        for future in as_completed(futures):
            resp = future.result()
            if "200" in str(resp):
                instance = resp.json()
                logger.info(f'Checking the status for the instance: {instance["instance"]["id"]}')
                if instance["instance"]["status"] == "running":
                    running.append(instance["instance"]["id"])
                    logger.info(f'Found running instance: {instance["instance"]["id"]}')
                    logger.info(f'Removing instance {instance["instance"]["id"]} from list.')
                    instance_ids.remove(instance["instance"]["id"])
                    logger.info(f'Running instances: {running}')
                elif instance["instance"]["status"] == "failed":
                    failed.append(instance["instance"]["id"])
                    logger.info(f'Found failed instance: {instance["instance"]["id"]}')
                    logger.info(f'Removing instance {instance["instance"]["id"]} from list.')
                    instance_ids.remove(instance["instance"]["id"])
                    logger.info(f'Failed instances: {failed}')
                else:
                    logger.info("Instance still provisioning")
            else:
                logger.error(f'An error occurred: {resp.json()}')
        provisioning_instances = len(instance_ids)
        if provisioning_instances > 0:
            logger.info(f'{len(instance_ids)} instances are still provisioning. Sleeping 30 seconds before checking again...')
            time.sleep(30)
    logger.info(f'Out of instances to check')
    data = { "running_instances": running, "failed_instances": failed}
    return(data)

def tag_with_lab_ids(url,headers,target_id,instance_ids:list,tag_name):
    """
    The tag_with_lab_ids function takes in a target_id, instance_ids and tag name.
    It then adds the tag to the instances with the given ids
    
    :param url: Specify the url of the instance
    :param headers: Pass in the authentication token
    :param target_id: Identify the instance that we want to add a tag to
    :param instance_ids:list: Pass in a list of instance ids that you want to tag
    :param tag_name: Specify the name of the tag that will be added to the instance
    :return: Nothing
    :doc-author: Trelent
    """
    endpoint = "/api/instances"
    payload = {'instance':{'addTags':[{'name': tag_name,'value': instance_ids}]}}
    try:
        logger.info('Attempting to add tag to the instance')
        resp = requests.put(f'{url}{endpoint}/{target_id}', headers=headers, verify=False, data=json.dumps(payload))
        if "200" in resp:
            logger.info(f'Looks like it took')
            return()
        else:
            logger.info(f'Not sure what happened')
            return()
    except Exception as e:
        logger.error(f'Something went wrong inthe operation')