"""
Tencent Cloud Cloud Module
=============================

.. versionadded:: 3000

The Tencent Cloud Cloud Module is used to control access to the Tencent Cloud instance.
https://intl.cloud.tencent.com/

To use this module, set up the cloud configuration at
 ``/etc/salt/cloud.providers`` or ``/etc/salt/cloud.providers.d/*.conf``:

.. code-block:: yaml

    my-tencentcloud-config:
      driver: tencentcloud
      # Tencent Cloud Secret Id
      id: AKIDA64pOio9BMemkApzevX0HS169S4b750A
      # Tencent Cloud Secret Key
      key: 8r2xmPn0C5FDvRAlmcJimiTZKVRsk260
      # Tencent Cloud Region
      location: ap-guangzhou

:depends: tencentcloud-sdk-python
"""

import logging
import pprint
import time

import salt.config as config
import salt.utils.cloud
import salt.utils.data
import salt.utils.json
from salt.exceptions import (
    SaltCloudExecutionFailure,
    SaltCloudExecutionTimeout,
    SaltCloudNotFound,
    SaltCloudSystemExit,
)

try:
    # Try import tencentcloud sdk
    from tencentcloud.common import credential  # pylint: disable=no-name-in-module

    # pylint: disable=no-name-in-module
    from tencentcloud.common.profile.client_profile import ClientProfile
    from tencentcloud.cvm.v20170312 import cvm_client
    from tencentcloud.cvm.v20170312 import models as cvm_models
    from tencentcloud.vpc.v20170312 import models as vpc_models
    from tencentcloud.vpc.v20170312 import vpc_client

    # pylint: enable=no-name-in-module

    HAS_TENCENTCLOUD_SDK = True
except ImportError:
    HAS_TENCENTCLOUD_SDK = False

# Get logging started
log = logging.getLogger(__name__)

# The default region
DEFAULT_REGION = "ap-guangzhou"

# The Tencent Cloud
__virtualname__ = "tencentcloud"


def __virtual__():
    """
    Only load in this module if the Tencent Cloud configurations are in place
    """
    if get_configured_provider() is False:
        return False

    if get_dependencies() is False:
        return False

    return __virtualname__


def _get_active_provider_name():
    try:
        return __active_provider_name__.value()
    except AttributeError:
        return __active_provider_name__


def get_configured_provider():
    """
    Return the first configured instance.
    """
    return config.is_provider_configured(
        __opts__, _get_active_provider_name() or __virtualname__, ("id", "key")
    )


def get_dependencies():
    """
    Warn if dependencies aren't met.
    """
    return config.check_driver_dependencies(
        __virtualname__, {"tencentcloud-sdk-python": HAS_TENCENTCLOUD_SDK}
    )


def get_provider_client(name=None):
    """
    Return a new provider client
    """
    provider = get_configured_provider()

    secretId = provider.get("id")
    secretKey = provider.get("key")
    region = __get_location(None)

    cpf = ClientProfile()
    cpf.language = "en-US"
    crd = credential.Credential(secretId, secretKey)

    if name == "cvm_client":
        client = cvm_client.CvmClient(crd, region, cpf)
    elif name == "vpc_client":
        client = vpc_client.VpcClient(crd, region, cpf)
    else:
        raise SaltCloudSystemExit(f"Client name {name} is not supported")

    return client


def avail_locations(call=None):
    """
    Return Tencent Cloud available region

    CLI Example:

    .. code-block:: bash

        salt-cloud --list-locations my-tencentcloud-config
        salt-cloud -f avail_locations my-tencentcloud-config
    """
    if call == "action":
        raise SaltCloudSystemExit(
            "The avail_locations function must be called with "
            "-f or --function, or with the --list-locations option"
        )

    client = get_provider_client("cvm_client")
    req = cvm_models.DescribeRegionsRequest()
    resp = client.DescribeRegions(req)

    ret = {}
    for region in resp.RegionSet:
        if region.RegionState != "AVAILABLE":
            continue
        ret[region.Region] = region.RegionName

    return ret


def avail_images(call=None):
    """
    Return Tencent Cloud available image

    CLI Example:

    .. code-block:: bash

        salt-cloud --list-images my-tencentcloud-config
        salt-cloud -f avail_images my-tencentcloud-config
    """
    if call == "action":
        raise SaltCloudSystemExit(
            "The avail_images function must be called with "
            "-f or --function, or with the --list-images option"
        )

    return _get_images(
        ["PUBLIC_IMAGE", "PRIVATE_IMAGE", "IMPORT_IMAGE", "SHARED_IMAGE"]
    )


def avail_sizes(call=None):
    """
    Return Tencent Cloud available instance type

    CLI Example:

    .. code-block:: bash

        salt-cloud --list-sizes my-tencentcloud-config
        salt-cloud -f avail_sizes my-tencentcloud-config
    """
    if call == "action":
        raise SaltCloudSystemExit(
            "The avail_sizes function must be called with "
            "-f or --function, or with the --list-sizes option"
        )

    client = get_provider_client("cvm_client")
    req = cvm_models.DescribeInstanceTypeConfigsRequest()
    resp = client.DescribeInstanceTypeConfigs(req)

    ret = {}
    for typeConfig in resp.InstanceTypeConfigSet:
        ret[typeConfig.InstanceType] = {
            "Zone": typeConfig.Zone,
            "InstanceFamily": typeConfig.InstanceFamily,
            "Memory": f"{typeConfig.Memory}GB",
            "CPU": f"{typeConfig.CPU}-Core",
        }
        if typeConfig.GPU:
            ret[typeConfig.InstanceType]["GPU"] = f"{typeConfig.GPU}-Core"

    return ret


def list_securitygroups(call=None):
    """
    Return all Tencent Cloud security groups in current region

    CLI Example:

    .. code-block:: bash

        salt-cloud -f list_securitygroups my-tencentcloud-config
    """
    if call == "action":
        raise SaltCloudSystemExit(
            "The list_securitygroups function must be called with -f or --function."
        )

    client = get_provider_client("vpc_client")
    req = vpc_models.DescribeSecurityGroupsRequest()
    req.Offset = 0
    req.Limit = 100
    resp = client.DescribeSecurityGroups(req)

    ret = {}
    for sg in resp.SecurityGroupSet:
        ret[sg.SecurityGroupId] = {
            "SecurityGroupName": sg.SecurityGroupName,
            "SecurityGroupDesc": sg.SecurityGroupDesc,
            "ProjectId": sg.ProjectId,
            "IsDefault": sg.IsDefault,
            "CreatedTime": sg.CreatedTime,
        }

    return ret


def list_custom_images(call=None):
    """
    Return all Tencent Cloud images in current region

    CLI Example:

    .. code-block:: bash

        salt-cloud -f list_custom_images my-tencentcloud-config
    """
    if call == "action":
        raise SaltCloudSystemExit(
            "The list_custom_images function must be called with -f or --function."
        )

    return _get_images(["PRIVATE_IMAGE", "IMPORT_IMAGE"])


def list_availability_zones(call=None):
    """
    Return all Tencent Cloud availability zones in current region

    CLI Example:

    .. code-block:: bash

        salt-cloud -f list_availability_zones my-tencentcloud-config
    """
    if call == "action":
        raise SaltCloudSystemExit(
            "The list_availability_zones function must be called with -f or --function."
        )

    client = get_provider_client("cvm_client")
    req = cvm_models.DescribeZonesRequest()
    resp = client.DescribeZones(req)

    ret = {}
    for zone in resp.ZoneSet:
        if zone.ZoneState != "AVAILABLE":
            continue
        ret[zone.Zone] = (zone.ZoneName,)

    return ret


def list_nodes(call=None):
    """
    Return a list of instances that are on the provider

    CLI Examples:

    .. code-block:: bash

        salt-cloud -Q
    """
    if call == "action":
        raise SaltCloudSystemExit(
            "The list_nodes function must be called with -f or --function."
        )

    ret = {}
    nodes = _get_nodes()
    for instance in nodes:
        ret[instance.InstanceId] = {
            "InstanceId": instance.InstanceId,
            "InstanceName": instance.InstanceName,
            "InstanceType": instance.InstanceType,
            "ImageId": instance.ImageId,
            "PublicIpAddresses": instance.PublicIpAddresses,
            "PrivateIpAddresses": instance.PrivateIpAddresses,
            "InstanceState": instance.InstanceState,
        }

    return ret


def list_nodes_full(call=None):
    """
    Return a list of instances that are on the provider, with full details

    CLI Examples:

    .. code-block:: bash

        salt-cloud -F
    """
    if call == "action":
        raise SaltCloudSystemExit(
            "The list_nodes_full function must be called with -f or --function."
        )

    ret = {}
    nodes = _get_nodes()
    for instance in nodes:
        instanceAttribute = vars(instance)
        ret[instance.InstanceName] = instanceAttribute
        for k in [
            "DataDisks",
            "InternetAccessible",
            "LoginSettings",
            "Placement",
            "SystemDisk",
            "Tags",
            "VirtualPrivateCloud",
        ]:
            ret[instance.InstanceName][k] = str(instanceAttribute[k])

    provider = _get_active_provider_name() or "tencentcloud"
    if ":" in provider:
        comps = provider.split(":")
        provider = comps[0]

    __opts__["update_cachedir"] = True
    __utils__["cloud.cache_node_list"](ret, provider, __opts__)

    return ret


def list_nodes_select(call=None):
    """
    Return a list of instances that are on the provider, with select fields

    CLI Examples:

    .. code-block:: bash

        salt-cloud -S
    """
    return salt.utils.cloud.list_nodes_select(
        list_nodes_full("function"),
        __opts__["query.selection"],
        call,
    )


def list_nodes_min(call=None):
    """
    Return a list of instances that are on the provider, Only names, and their state, is returned.

    CLI Examples:

    .. code-block:: bash

        salt-cloud -f list_nodes_min my-tencentcloud-config
    """
    if call == "action":
        raise SaltCloudSystemExit(
            "The list_nodes_min function must be called with -f or --function."
        )

    ret = {}
    nodes = _get_nodes()
    for instance in nodes:
        ret[instance.InstanceName] = {
            "InstanceId": instance.InstanceId,
            "InstanceState": instance.InstanceState,
        }

    return ret


def create(vm_):
    """
    Create a single Tencent Cloud instance from a data dict.

    Tencent Cloud profiles require a ``provider``, ``availability_zone``, ``image`` and ``size``.
    Set up profile at ``/etc/salt/cloud.profiles`` or ``/etc/salt/cloud.profiles.d/*.conf``:

    .. code-block:: yaml

        tencentcloud-guangzhou-s1sm1:
            provider: my-tencentcloud-config
            availability_zone: ap-guangzhou-3
            image: img-31tjrtph
            size: S1.SMALL1
            allocate_public_ip: True
            internet_max_bandwidth_out: 1
            password: '153e41ec96140152'
            securitygroups:
                - sg-5e90804b

    CLI Examples:

    .. code-block:: bash

        salt-cloud -p tencentcloud-guangzhou-s1 myinstance
    """
    try:
        # Check for required profile parameters before sending any API calls.
        if (
            vm_["profile"]
            and config.is_profile_configured(
                __opts__,
                _get_active_provider_name() or "tencentcloud",
                vm_["profile"],
                vm_=vm_,
            )
            is False
        ):
            return False
    except AttributeError:
        pass

    __utils__["cloud.fire_event"](
        "event",
        "starting create",
        "salt/cloud/{}/creating".format(vm_["name"]),
        args=__utils__["cloud.filter_event"](
            "creating", vm_, ["name", "profile", "provider", "driver"]
        ),
        sock_dir=__opts__["sock_dir"],
        transport=__opts__["transport"],
    )

    log.debug("Try creating instance: %s", pprint.pformat(vm_))

    # Init cvm client
    client = get_provider_client("cvm_client")
    req = cvm_models.RunInstancesRequest()
    req.InstanceName = vm_["name"]

    # Required parameters
    req.InstanceType = __get_size(vm_)
    req.ImageId = __get_image(vm_)

    zone = __get_availability_zone(vm_)
    projectId = vm_.get("project_id", 0)
    req.Placement = {"Zone": zone, "ProjectId": projectId}

    # Optional parameters

    req.SecurityGroupIds = __get_securitygroups(vm_)
    req.HostName = vm_.get("hostname", vm_["name"])

    req.InstanceChargeType = vm_.get("instance_charge_type", "POSTPAID_BY_HOUR")
    if req.InstanceChargeType == "PREPAID":
        period = vm_.get("instance_charge_type_prepaid_period", 1)
        renewFlag = vm_.get(
            "instance_charge_type_prepaid_renew_flag", "NOTIFY_AND_MANUAL_RENEW"
        )
        req.InstanceChargePrepaid = {"Period": period, "RenewFlag": renewFlag}

    allocate_public_ip = vm_.get("allocate_public_ip", False)
    internet_max_bandwidth_out = vm_.get("internet_max_bandwidth_out", 0)
    if allocate_public_ip and internet_max_bandwidth_out > 0:
        req.InternetAccessible = {
            "PublicIpAssigned": allocate_public_ip,
            "InternetMaxBandwidthOut": internet_max_bandwidth_out,
        }
        internet_charge_type = vm_.get("internet_charge_type", "")
        if internet_charge_type != "":
            req.InternetAccessible["InternetChargeType"] = internet_charge_type

    req.LoginSettings = {}
    req.VirtualPrivateCloud = {}
    req.SystemDisk = {}

    keyId = vm_.get("key_name", "")
    if keyId:
        req.LoginSettings["KeyIds"] = [keyId]

    password = vm_.get("password", "")
    if password:
        req.LoginSettings["Password"] = password

    private_ip = vm_.get("private_ip", "")
    if private_ip:
        req.VirtualPrivateCloud["PrivateIpAddresses"] = private_ip

    vpc_id = vm_.get("vpc_id", "")
    if vpc_id:
        req.VirtualPrivateCloud["VpcId"] = vpc_id

    subnetId = vm_.get("subnet_id", "")
    if subnetId:
        req.VirtualPrivateCloud["SubnetId"] = subnetId

    system_disk_size = vm_.get("system_disk_size", 0)
    if system_disk_size:
        req.SystemDisk["DiskSize"] = system_disk_size

    system_disk_type = vm_.get("system_disk_type", "")
    if system_disk_type:
        req.SystemDisk["DiskType"] = system_disk_type

    __utils__["cloud.fire_event"](
        "event",
        "requesting instance",
        "salt/cloud/{}/requesting".format(vm_["name"]),
        args=__utils__["cloud.filter_event"]("requesting", vm_, list(vm_)),
        sock_dir=__opts__["sock_dir"],
        transport=__opts__["transport"],
    )

    try:
        resp = client.RunInstances(req)
        if not resp.InstanceIdSet:
            raise SaltCloudSystemExit("Unexpected error, no instance created")
    except Exception as exc:  # pylint: disable=broad-except
        log.error(
            "Error creating %s on tencentcloud\n\n"
            "The following exception was thrown when trying to "
            "run the initial deployment: %s",
            vm_["name"],
            str(exc),
            # Show the traceback if the debug logging level is enabled
            exc_info_on_loglevel=logging.DEBUG,
        )
        return False

    time.sleep(5)

    def __query_node_data(vm_name):
        data = show_instance(vm_name, call="action")
        if not data:
            return False
        if data["InstanceState"] != "RUNNING":
            return False
        if data["PrivateIpAddresses"]:
            return data

    try:
        data = salt.utils.cloud.wait_for_ip(
            __query_node_data,
            update_args=(vm_["name"],),
            timeout=config.get_cloud_config_value(
                "wait_for_ip_timeout", vm_, __opts__, default=10 * 60
            ),
            interval=config.get_cloud_config_value(
                "wait_for_ip_interval", vm_, __opts__, default=10
            ),
        )
    except (SaltCloudExecutionTimeout, SaltCloudExecutionFailure) as exc:
        try:
            destroy(vm_["name"])
        except SaltCloudSystemExit:
            pass
        finally:
            raise SaltCloudSystemExit(str(exc))

    if data["PublicIpAddresses"]:
        ssh_ip = data["PublicIpAddresses"][0]
    elif data["PrivateIpAddresses"]:
        ssh_ip = data["PrivateIpAddresses"][0]
    else:
        log.error("No available ip: cant connect to salt")
        return False

    log.debug("Instance %s: %s is now running", vm_["name"], ssh_ip)
    vm_["ssh_host"] = ssh_ip

    # The instance is booted and accessible, let's Salt it!
    ret = __utils__["cloud.bootstrap"](vm_, __opts__)
    ret.update(data)

    log.debug("'%s' instance creation details:\n%s", vm_["name"], pprint.pformat(data))

    __utils__["cloud.fire_event"](
        "event",
        "created instance",
        "salt/cloud/{}/created".format(vm_["name"]),
        args=__utils__["cloud.filter_event"](
            "created", vm_, ["name", "profile", "provider", "driver"]
        ),
        sock_dir=__opts__["sock_dir"],
        transport=__opts__["transport"],
    )

    return ret


def start(name, call=None):
    """
    Start a Tencent Cloud instance
    Notice: the instance state must be stopped

    CLI Examples:

    .. code-block:: bash

        salt-cloud -a start myinstance
    """
    if call != "action":
        raise SaltCloudSystemExit("The stop action must be called with -a or --action.")

    node = _get_node(name)

    client = get_provider_client("cvm_client")
    req = cvm_models.StartInstancesRequest()
    req.InstanceIds = [node.InstanceId]
    resp = client.StartInstances(req)

    return resp


def stop(name, force=False, call=None):
    """
    Stop a Tencent Cloud running instance
    Note: use `force=True` to make force stop

    CLI Examples:

    .. code-block:: bash

        salt-cloud -a stop myinstance
        salt-cloud -a stop myinstance force=True
    """
    if call != "action":
        raise SaltCloudSystemExit("The stop action must be called with -a or --action.")

    node = _get_node(name)

    client = get_provider_client("cvm_client")
    req = cvm_models.StopInstancesRequest()
    req.InstanceIds = [node.InstanceId]
    if force:
        req.ForceStop = "TRUE"
    resp = client.StopInstances(req)

    return resp


def reboot(name, call=None):
    """
    Reboot a Tencent Cloud instance

    CLI Examples:

    .. code-block:: bash

        salt-cloud -a reboot myinstance
    """
    if call != "action":
        raise SaltCloudSystemExit("The stop action must be called with -a or --action.")

    node = _get_node(name)

    client = get_provider_client("cvm_client")
    req = cvm_models.RebootInstancesRequest()
    req.InstanceIds = [node.InstanceId]
    resp = client.RebootInstances(req)

    return resp


def destroy(name, call=None):
    """
    Destroy a Tencent Cloud instance

    CLI Example:

    .. code-block:: bash

        salt-cloud -a destroy myinstance
        salt-cloud -d myinstance
    """
    if call == "function":
        raise SaltCloudSystemExit(
            "The destroy action must be called with -d, --destroy, -a or --action."
        )

    __utils__["cloud.fire_event"](
        "event",
        "destroying instance",
        f"salt/cloud/{name}/destroying",
        args={"name": name},
        sock_dir=__opts__["sock_dir"],
        transport=__opts__["transport"],
    )

    node = _get_node(name)

    client = get_provider_client("cvm_client")
    req = cvm_models.TerminateInstancesRequest()
    req.InstanceIds = [node.InstanceId]
    resp = client.TerminateInstances(req)

    __utils__["cloud.fire_event"](
        "event",
        "destroyed instance",
        f"salt/cloud/{name}/destroyed",
        args={"name": name},
        sock_dir=__opts__["sock_dir"],
        transport=__opts__["transport"],
    )

    return resp


def script(vm_):
    """
    Return the script deployment object
    """
    return salt.utils.cloud.os_script(
        config.get_cloud_config_value("script", vm_, __opts__),
        vm_,
        __opts__,
        salt.utils.cloud.salt_config_to_yaml(
            salt.utils.cloud.minion_config(__opts__, vm_)
        ),
    )


def show_image(kwargs, call=None):
    """
    Show the details of Tencent Cloud image

    CLI Examples:

    .. code-block:: bash

        salt-cloud -f show_image tencentcloud image=img-31tjrtph
    """
    if call != "function":
        raise SaltCloudSystemExit(
            "The show_image function must be called with -f or --function"
        )

    if not isinstance(kwargs, dict):
        kwargs = {}

    if "image" not in kwargs:
        raise SaltCloudSystemExit("No image specified.")

    image = kwargs["image"]

    client = get_provider_client("cvm_client")
    req = cvm_models.DescribeImagesRequest()
    req.ImageIds = [image]
    resp = client.DescribeImages(req)

    if not resp.ImageSet:
        raise SaltCloudNotFound(f"The specified image '{image}' could not be found.")

    ret = {}
    for image in resp.ImageSet:
        ret[image.ImageId] = {
            "ImageName": image.ImageName,
            "ImageType": image.ImageType,
            "ImageSource": image.ImageSource,
            "Platform": image.Platform,
            "Architecture": image.Architecture,
            "ImageSize": f"{image.ImageSize}GB",
            "ImageState": image.ImageState,
        }

    return ret


def show_instance(name, call=None):
    """
    Show the details of Tencent Cloud instance

    CLI Examples:

    .. code-block:: bash

        salt-cloud -a show_instance myinstance
    """
    if call != "action":
        raise SaltCloudSystemExit(
            "The show_instance action must be called with -a or --action."
        )

    node = _get_node(name)
    ret = vars(node)
    for k in [
        "DataDisks",
        "InternetAccessible",
        "LoginSettings",
        "Placement",
        "SystemDisk",
        "Tags",
        "VirtualPrivateCloud",
    ]:
        ret[k] = str(ret[k])

    return ret


def show_disk(name, call=None):
    """
    Show the disk details of Tencent Cloud instance

    CLI Examples:

    .. code-block:: bash

        salt-cloud -a show_disk myinstance
    """
    if call != "action":
        raise SaltCloudSystemExit(
            "The show_disks action must be called with -a or --action."
        )

    node = _get_node(name)

    ret = {}
    ret[node.SystemDisk.DiskId] = {
        "SystemDisk": True,
        "DiskSize": node.SystemDisk.DiskSize,
        "DiskType": node.SystemDisk.DiskType,
        "DeleteWithInstance": True,
        "SnapshotId": "",
    }

    if node.DataDisks:
        for disk in node.DataDisks:
            ret[disk.DiskId] = {
                "SystemDisk": False,
                "DiskSize": disk.DiskSize,
                "DiskType": disk.DiskType,
                "DeleteWithInstance": disk.DeleteWithInstance,
                "SnapshotId": disk.SnapshotId,
            }

    return ret


def _get_node(name):
    """
    Return Tencent Cloud instance detail by name
    """
    attempts = 5
    while attempts >= 0:
        try:
            client = get_provider_client("cvm_client")
            req = cvm_models.DescribeInstancesRequest()
            req.Filters = [{"Name": "instance-name", "Values": [name]}]
            resp = client.DescribeInstances(req)
            return resp.InstanceSet[0]
        except Exception as ex:  # pylint: disable=broad-except
            attempts -= 1
            log.debug(
                "Failed to get data for node '%s': %s. Remaining attempts: %d",
                name,
                ex,
                attempts,
            )
            time.sleep(0.5)

    raise SaltCloudNotFound(f"Failed to get instance info {name}")


def _get_nodes():
    """
    Return all list of Tencent Cloud instances
    """
    ret = []
    offset = 0
    limit = 100

    while True:
        client = get_provider_client("cvm_client")
        req = cvm_models.DescribeInstancesRequest()
        req.Offset = offset
        req.Limit = limit
        resp = client.DescribeInstances(req)
        for v in resp.InstanceSet:
            ret.append(v)
        if len(ret) >= resp.TotalCount:
            break
        offset += len(resp.InstanceSet)

    return ret


def _get_images(image_type):
    """
    Return all list of Tencent Cloud images
    """
    client = get_provider_client("cvm_client")
    req = cvm_models.DescribeImagesRequest()
    req.Filters = [{"Name": "image-type", "Values": image_type}]
    req.Offset = 0
    req.Limit = 100
    resp = client.DescribeImages(req)

    ret = {}
    for image in resp.ImageSet:
        if image.ImageState != "NORMAL":
            continue
        ret[image.ImageId] = {
            "ImageName": image.ImageName,
            "ImageType": image.ImageType,
            "ImageSource": image.ImageSource,
            "Platform": image.Platform,
            "Architecture": image.Architecture,
            "ImageSize": f"{image.ImageSize}GB",
        }

    return ret


def __get_image(vm_):
    vm_image = str(
        config.get_cloud_config_value("image", vm_, __opts__, search_global=False)
    )

    if not vm_image:
        raise SaltCloudNotFound("No image specified.")

    images = avail_images()
    if vm_image in images:
        return vm_image

    raise SaltCloudNotFound(f"The specified image '{vm_image}' could not be found.")


def __get_size(vm_):
    vm_size = str(
        config.get_cloud_config_value("size", vm_, __opts__, search_global=False)
    )

    if not vm_size:
        raise SaltCloudNotFound("No size specified.")

    sizes = avail_sizes()
    if vm_size in sizes:
        return vm_size

    raise SaltCloudNotFound(f"The specified size '{vm_size}' could not be found.")


def __get_securitygroups(vm_):
    vm_securitygroups = config.get_cloud_config_value(
        "securitygroups", vm_, __opts__, search_global=False
    )

    if not vm_securitygroups:
        return []

    securitygroups = list_securitygroups()
    for idx, value in enumerate(vm_securitygroups):
        vm_securitygroups[idx] = str(value)
        if vm_securitygroups[idx] not in securitygroups:
            raise SaltCloudNotFound(
                "The specified securitygroups '{}' could not be found.".format(
                    vm_securitygroups[idx]
                )
            )

    return vm_securitygroups


def __get_availability_zone(vm_):
    vm_availability_zone = str(
        config.get_cloud_config_value(
            "availability_zone", vm_, __opts__, search_global=False
        )
    )

    if not vm_availability_zone:
        raise SaltCloudNotFound("No availability_zone specified.")

    availability_zones = list_availability_zones()
    if vm_availability_zone in availability_zones:
        return vm_availability_zone

    raise SaltCloudNotFound(
        "The specified availability_zone '{}' could not be found.".format(
            vm_availability_zone
        )
    )


def __get_location(vm_):
    """
    Return the Tencent Cloud region to use, in this order:
        - CLI parameter
        - VM parameter
        - Cloud profile setting
    """
    vm_location = str(
        __opts__.get(
            "location",
            config.get_cloud_config_value(
                "location",
                vm_ or get_configured_provider(),
                __opts__,
                default=DEFAULT_REGION,
                search_global=False,
            ),
        )
    )

    if not vm_location:
        raise SaltCloudNotFound("No location specified.")

    return vm_location
