import json, urllib3, requests, pkgutil, time, boto3
from concurrent.futures import as_completed, ThreadPoolExecutor
from requests_futures.sessions import FuturesSession
from loguru import logger
from .tools import get_class_lab_instance_type, get_class_layout_id, get_morpheus_terraform_plan_id, get_instance_provisioning_payload, get_instance, get_morpheus_terraform_inputs

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

class newClass():
    def __init__(self,url,headers,instance_map,instance_name):
        self.url = url
        self.headers = headers
        self.instance_map = instance_map
        self.instanceName = instance_name
        self.custom_options = self.instance_map["customOptions"]
        self.templateParameters = get_morpheus_terraform_inputs(self.custom_options)
        self.templateParameters["lab_name"] = self.instanceName.split("-")[-1]
        self.templateParameters["master_region"] = self.instance_map["customOptions"]["trainingZone"].split(",")[0]
        self.templateParameters["labs_region"] = self.instance_map["customOptions"]["trainingZone"].split(",")[1]
        self.instance_type = get_class_lab_instance_type(self.url,self.headers,f'ilt{self.instance_map["customOptions"]["class_name"][0:3]}')
        self.instance_layout_id = get_class_layout_id(self.url,self.headers,self.custom_options,self.instance_type)
        self.terraform_plan_id = get_morpheus_terraform_plan_id(self.url,self.headers)
        self.deploy_payload = get_instance_provisioning_payload(
            class_version=self.custom_options["class_version"],
            zone_id=self.instance_map["input"]["zoneId"],
            instance_name=self.instanceName,
            site_id=self.instance_map["input"]["site"]["id"],
            instance_type=self.instance_type["code"],
            instance_type_code=self.instance_type["code"],
            layout_id=self.instance_layout_id,
            plan_id=self.terraform_plan_id,
            template_parameters=self.templateParameters,
            )
        
    def deploy(self):
        try:
            logger.info(f'Attempting to deploy the instance: {self.instanceName}')
            resp = requests.post(f'{self.url}/api/instances',headers=self.headers,json=self.deploy_payload,verify=False)
        except Exception as e:
            logger.error(e)
        if "200" in str(resp):
            logger.info(f'Instance {self.instanceName} deployment successfully triggered...')
            self.instance_id = resp.json()["instance"]["id"]
            return
        elif "504" in str(resp):
            logger.info(f'Received a gateway timeout.')
            logger.info(f'Instance {self.instanceName} deployment possibly triggered...')
            self.instance_id = "null"
            return
        else:
            logger.error(f'Something went wrong: {resp.json()}')
            return(resp)

    def status(self):
        counter = 0
        max_counter = 20
        logger.info(f'Checking the deployment status of the instance: {self.instanceName}')
        while counter < max_counter:
            try:
                resp = requests.get(f'{self.url}/api/instances/{self.instance_id}',headers=self.headers,verify=False)
                counter += 1
            except Exception as e:
                logger.error(f'Something went wrong: {e}')
            if "200" in str(resp):
                logger.info(f'Instance {self.instanceName} instance found. Checking status...')
                logger.info(f'Status: {resp.json()["instance"]["status"]}')
                if resp.json()["instance"]["status"] == "provisioning":
                    logger.info(f'Instance {self.instanceName} is still provisioning. Waiting 60 seconds and checking again...')
                    self.status = resp.json()["instance"]["status"]
                    time.sleep(60)
                elif resp.json()["instance"]["status"] == "running":
                    logger.info(f'Instance {self.instanceName} is running. Continuing...')
                    self.status = resp.json()["instance"]["status"]
                    return
                else:
                    logger.error(f'Unexpected status: {resp.json()["instance"]["status"]}')
                    self.status = resp.json()["instance"]["status"]
                    return
            else:
                logger.error(f'Something went wrong: {resp.json()}')
                return(resp.json())

    def lock(self):
        try:
            logger.info(f'Attempting to lock the instance: {self.instanceName}')
            resp = requests.put(f'{self.url}/api/instances/{self.instance_id}/lock',headers=self.headers,verify=False)
        except Exception as e:
            logger.error(f'Something went wrong: {e}')
        if "200" in str(resp):
            logger.info(f'Instance {self.instanceName} locked successfully.')
        else:
            logger.error(f'Something went wrong: {resp.json()}')
            return(resp.json())
        
    def get_lab_data(self):
        self.lab_data = {}
        try:
            logger.info(f'Attempting to get the lab info for the instance: {self.instanceName}')
            resp = requests.get(f'{self.url}/api/instances/{self.instance_id}/state',headers=self.headers,verify=False)
        except Exception as e:
            logger.error(f'Something went wrong: {e}')
        if "200" in str(resp):
            logger.info(f'Lab info for instance {self.instanceName} found.')
            self.lab_data["lab_name"] = [i["value"] for i in resp.json()["input"]["variables"] if i["name"] == "lab_name"][0]
            self.lab_data["region"] = [i["value"] for i in resp.json()["input"]["variables"] if i["name"] == "labs_region"][0]
            self.lab_data["morpheus_external_ip"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "morpheus_external_ip"][0]
            self.lab_data["morpheus_internal_ip"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "morpheus_internal_ip"][0]
            self.lab_data["morpheus_url"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "morpheus_url"][0]
            self.lab_data["rocky_ami"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "rocky_ami"][0]
            self.lab_data["ubuntu_ami"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "ubuntu_ami"][0]
            self.lab_data["iam_key"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "iam_key"][0]
            self.lab_data["iam_secret"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "iam_secret"][0]
            return
        else:
            logger.error(f'Something went wrong: {resp.json()}')
            return(resp.json())

class existingClass():
    def __init__(self,url,headers,instance_map) :
        self.url = url
        self.headers = headers
        self.instance_map = instance_map

    def show(self):
        logger.info(f'Instance map:')
        print(json.dumps(self.instance_map, indent = 4))

    def unlock(self):
        for lab in self.instance_map["attributes"]["labs"]:
            try:
                logger.info(f'Attempting to unlock the instance: {lab["lab_name"]}')
                resp = requests.put(f'{self.url}/api/instances/{lab["instance_id"]}/unlock',headers=self.headers,verify=False)
            except Exception as e:
                logger.error(f'Something went wrong: {e}')
            if "200" in str(resp):
                logger.info(f'Instance {lab["lab_name"]} unlocked successfully.')
            else:
                logger.error(f'Something went wrong: {resp.json()}')
                return(resp.json())


    def delete(self):
        for lab in self.instance_map["attributes"]["labs"]:
            try:
                logger.info(f'Attempting to delete the instance: {lab["lab_name"]}')
                resp = requests.delete(f'{self.url}/api/instances/{lab["instance_id"]}',headers=self.headers,verify=False)
            except Exception as e:
                logger.error(f'Something went wrong: {e}')
            if "200" in str(resp):
                logger.info(f'Instance {lab["lab_name"]} delete initiated successfully.')
                tries = 0
                maxTries = 10
                while tries < maxTries:
                    try:
                        tries += 1
                        ins = get_instance(self.url,self.headers,lab["instance_id"])
                    except Exception as e:
                        logger.error(f'Something went wrong: {e}')
                    if ins["status"] == "removing":
                        logger.info(f'Instance {lab["lab_name"]} still removing.')
                        logger.info(f'Waiting 60 seconds and checking again...')
                        time.sleep(60)
                        break
                    elif ins["status"] == "stopped":
                        logger.info(f'Instance {lab["lab_name"]} stopped but not yet deleted')
                        logger.info(f'Waiting 60 seconds and checking again...')
                        time.sleep(60)
                    elif "Instance not found" in str(ins):
                        logger.info(f'Instance not found. Assuming it is gone....')
                        self.instance_map["attributes"]["labs"].remove(lab)
                        return(self.instance_map)
                logger.info(f'Instance {lab["lab_name"]} not gone after 10 minutes. You may need to check on this')
            else:
                logger.error(f'Something went wrong: {resp.json()}')
                return(resp.json())

class existingLab():
    def __init__(self,url,headers,instance_id) -> None:
        self.instance_map = get_instance(url,headers,instance_id)
        self.instance_name = self.instance_map["name"]
        self.instance_id = instance_id
        self.status = self.instance_map["status"]
        logger.info(f'Instance map:')
        logger.info(self.instance_map)
    
    def get_lab_data(self):
        self.lab_data = {}
        try:
            logger.info(f'Attempting to get the lab info for the instance: {self.instance_name}')
            resp = requests.get(f'{self.url}/api/instances/{self.instance_id}/state',headers=self.headers,verify=False)
        except Exception as e:
            logger.error(f'Something went wrong: {e}')
        if "200" in str(resp):
            logger.info(f'Lab info for instance {self.instance_name} found.')
            self.lab_data["lab_name"] = [i["value"] for i in resp.json()["input"]["variables"] if i["name"] == "lab_name"][0]
            self.lab_data["region"] = [i["value"] for i in resp.json()["input"]["variables"] if i["name"] == "labs_region"][0]
            self.lab_data["morpheus_external_ip"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "morpheus_external_ip"][0]
            self.lab_data["morpheus_internal_ip"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "morpheus_internal_ip"][0]
            self.lab_data["morpheus_url"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "morpheus_url"][0]
            self.lab_data["rocky_ami"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "rocky_ami"][0]
            self.lab_data["ubuntu_ami"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "ubuntu_ami"][0]
            self.lab_data["iam_key"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "iam_key"][0]
            self.lab_data["iam_secret"] = [i["value"]["value"] for i in resp.json()["output"]["outputs"] if i["name"] == "iam_secret"][0]
            return
        else:
            logger.error(f'Something went wrong: {resp.json()}')
            return(resp.json())
        
    def unlock(self):
        try:
            logger.info(f'Attempting to unlock the instance: {self.instance_name}')
            resp = requests.put(f'{self.url}/api/instances/{self.instance_id}/unlock',headers=self.headers,verify=False)
        except Exception as e:
            logger.error(f'Something went wrong: {e}')
        if "200" in str(resp):
            logger.info(f'Instance {self.instance_name} unlocked successfully.')
        else:
            logger.error(f'Something went wrong: {resp.json()}')
            return(resp.json())

    def delete(self):
        try:
            logger.info(f'Attempting to delete the instance: {self.instance_name}')
            resp = requests.delete(f'{self.url}/api/instances/{self.instance_id}',headers=self.headers,verify=False)
        except Exception as e:
            logger.error(f'Something went wrong: {e}')
        if "200" in str(resp):
            logger.info(f'Instance {self.instance_name} deleted successfully.')
        else:
            logger.error(f'Something went wrong: {resp.json()}')
            return(resp.json())
        
class newLab():
    def __init__(self,url,headers,instance_map):
        self.url = url
        self.headers = headers
        self.instance_map = instance_map

    def setup(self):

        pass

class awsEnv():
    def __init__(self,instance_map) :
        self.instance_map = instance_map
        self.region = self.instance_map["attributes"]["labs"][0]["region"]
        access_key = self.instance_map["attributes"]["labs"][0]["iam_key"]
        secret_key = self.instance_map["attributes"]["labs"][0]["iam_secret"]
        self.ec2 = boto3.client('ec2',region_name=self.region,aws_access_key_id=access_key,aws_secret_access_key=secret_key)
        self.s3 = boto3.client('s3',region_name=self.region,aws_access_key_id=access_key,aws_secret_access_key=secret_key)
        self.rds = boto3.client('rds',region_name=self.region,aws_access_key_id=access_key,aws_secret_access_key=secret_key)
   
    def delete_s3_bucket_versions(self,bucket_name, object_versions):
        # Get all object versions
        logger.info(f'Attempting to delete all object versions in the bucket: {bucket_name}')
        versions = object_versions.get('Versions', [])
        if versions:
            delete_keys = {'Objects': [{'Key': version['Key'], 'VersionId': version['VersionId']} for version in versions]}
            try:
                resp = self.s3.delete_objects(Bucket=bucket_name, Delete=delete_keys)
                logger.info(resp)
            except Exception as e:
                logger.error(f'Something went wrong: {e}')
        
    def delete_s3_bucket_delete_markers(self,bucket_name, object_versions):
    # Get all object versions in the bucket's versioning-enabled delete marker
        logger.info(f'Attempting to delete all delete markers in the bucket: {bucket_name}')
        delete_markers = object_versions.get('DeleteMarkers', [])
        if delete_markers:
            # Delete all object versions in the bucket's versioning-enabled delete marker
            delete_marker_keys = {'Objects': [{'Key': marker['Key'], 'VersionId': marker['VersionId']} for marker in delete_markers]}
            try: 
                resp = self.s3.delete_objects(Bucket=bucket_name, Delete=delete_marker_keys)
                logger.info(resp)
            except Exception as e:
                logger.error(f'Something went wrong: {e}')

    def delete_s3_bucket(self,bucket_name):
        # Get all object versions
        logger.info(f'Attempting to delete the S3 bucket: {bucket_name}')
        object_versions = self.s3.list_object_versions(Bucket=bucket_name, MaxKeys=1000)
        while True:
            # Delete all object versions
            self.delete_s3_bucket_versions(bucket_name, object_versions)
            # Delete all delete markers
            self.delete_s3_bucket_delete_markers(bucket_name, object_versions)
            # Check if there are more object versions to delete
            if object_versions.get('IsTruncated'):
                object_versions = self.s3.list_object_versions(Bucket=bucket_name, MaxKeys=1000, KeyMarker=response['NextKeyMarker'], VersionIdMarker=response['NextVersionIdMarker'])
            else:
                break
        # Deleting S3 bucket
        self.s3.delete_bucket(bucket_name)

    def delete_db_instance(self,db_instance_identifier):
        logger.info(f'Attempting to delete the RDS instance: {db_instance_identifier}')
        try:
            self.rds.delete_db_instance(DBInstanceIdentifier=db_instance_identifier, SkipFinalSnapshot=True,DeleteAutomatedBackups=True)
        except Exception as e:
            logger.error(f'Something went wrong: {e}')

    def delete_db_cluster(self,db_cluster_identifier):
        logger.info(f'Attempting to delete the RDS cluster: {db_cluster_identifier}')
        try:
            resp = self.rds.delete_db_cluster(DBClusterIdentifier=db_cluster_identifier, SkipFinalSnapshot=True)
            logger.info(resp)
        except Exception as e:
            logger.error(f'Something went wrong: {e}')

    def terminate_ec2_instance(self,instance_id):
        logger.info(f'Attempting to terminate the EC2 instance: {instance_id}')
        try:
            resp = self.ec2.terminate_instances(InstanceIds=[instance_id])
            logger.info(resp)
        except Exception as e:
            logger.error(f'Something went wrong: {e}')

    def get_rds_clusters(self):
        rds_clusters = self.rds.describe_db_clusters()
        return(rds_clusters)
    
    def get_s3_buckets_in_region(self):
        bucket_list = []
        s3_buckets = self.s3.list_buckets()
        for bucket in s3_buckets["Buckets"]:
            if self.s3.get_bucket_location(Bucket=bucket["Name"])["LocationConstraint"] == self.region:
                bucket_list.append(bucket["Name"])
        return(bucket_list)
    
    def get_ec2_instances(self):
        ec2_instances = self.ec2.describe_instances()
        return(ec2_instances["Reservations"])