import time
import os
from huaweicloudsdkcore.auth.credentials import BasicCredentials, DerivedCredentials
from huaweicloudsdkcore.exceptions.exceptions import ConnectionException
from huaweicloudsdkcore.exceptions.exceptions import RequestTimeoutException
from huaweicloudsdkcore.exceptions.exceptions import ServiceResponseException
from huaweicloudsdkcore.region.region import Region
from huaweicloudsdkiotda.v5.iotda_client import IoTDAClient
from huaweicloudsdkiotda.v5.model.create_batch_task import CreateBatchTask
from huaweicloudsdkiotda.v5.model.create_batch_task_request import CreateBatchTaskRequest
from huaweicloudsdkiotda.v5.model.show_batch_task_request import ShowBatchTaskRequest
from huaweicloudsdkiotda.v5.model.show_device_shadow_request import ShowDeviceShadowRequest
from huaweicloudsdkiotda.v5.model.update_desired import UpdateDesired
from huaweicloudsdkiotda.v5.model.update_desireds import UpdateDesireds
from huaweicloudsdkiotda.v5.model.update_device_shadow_desired_data_request import UpdateDeviceShadowDesiredDataRequest


class DeviceShadow:
    # REGION_ID:If your region is 上海一,please fill in "cn-east-3";If your region is 北京四,please fill in "cn-north-4"; If your region is 华南广州,please fill in "cn-south-4"
    __REGION_ID = "<YOUR REGION ID>"
    # ENDPOINT:On the console, choose **Overview** in the navigation pane and click **Access Addresses** to view the HTTPS application access address.
    __ENDPOINT = "<YOUR ENDPOINT>"
    # ak/sk:For details, see the method of obtaining AK/SK in iotda document https://support.huaweicloud.com/api-iothub/iot_06_v5_0091.html.
    # There will be security risks if the AK/SK used for authentication is directly written into code. We suggest encrypting the AK/SK in the configuration file or environment variables for storage.
    # In this sample, the AK/SK is stored in environment variables for identity authentication. Before running this sample, set the environment variables HUAWEICLOUD_SDK_AK and HUAWEICLOUD_SDK_SK.
    __AK = os.environ["HUAWEICLOUD_SDK_AK"]
    __SK = os.environ["HUAWEICLOUD_SDK_SK"]
    __PROJECT_ID = "<YOUR PROJECTID>"
    # Instance ID
    __INSTANCE_ID = "<YOUR INSTANCEID>"

    def __init__(self):
        pass

    @staticmethod
    def __init_client():
        # Create a credential
        # Derivative algorithms are used for the standard and enterprise editions. For the basic edition, delete the configuration "withDerivedPredicate".
        auth = BasicCredentials(
            ak=DeviceShadow.__AK,
            sk=DeviceShadow.__SK,
            project_id=DeviceShadow.__PROJECT_ID
        ).with_derived_predicate(DerivedCredentials.get_default_derived_predicate())

        # Create and initialize an IoTDAClient instance
        # Use IoTDARegion for basic editions like "withRegion(IoTDARegion.CN_NORTH_4)", select different values based on the different clusters of the Region object. Create regions for standard/enterprise editions.
        return IoTDAClient.new_builder() \
            .with_region(Region(id=DeviceShadow.__REGION_ID, endpoint=DeviceShadow.__ENDPOINT)) \
            .with_credentials(auth) \
            .build()

    @staticmethod
    def __update_shadow(iotda_client, device_id, body):
        """
        Configure Desired Values in a Device Shadow

        @param iotda_client iotda client
        @param device_id    ID of the device whose device shadow needs to be configured
        @param body        Structure for configuring the device shadow
        """
        request = UpdateDeviceShadowDesiredDataRequest()
        request.device_id = device_id
        request.body = body
        request.instance_id = DeviceShadow.__INSTANCE_ID
        response = iotda_client.update_device_shadow_desired_data(request)
        print("The device shadow configuration result is:", response)

    @staticmethod
    def __query_shadow_detail(iotda_client, device_id):
        """
        Query the Details About a Device Shadow

        @param iotda_client iotda client
        @param device_id    Device ID
        """
        request = ShowDeviceShadowRequest()
        request.instance_id = DeviceShadow.__INSTANCE_ID
        request.device_id = device_id
        message_response = iotda_client.show_device_shadow(request)
        print("The device shadow query result is:", message_response)

    @staticmethod
    def __create_task(iotda_client, create_batch_task):
        """
        Create a Batch Task

        @param iotda_client     iotda client
        @param createBatchTask Structure for a batch task creation
        @return The task obtained after the creation
        """
        request = CreateBatchTaskRequest()
        request.instance_id = DeviceShadow.__INSTANCE_ID
        request.body = create_batch_task
        batch_task = iotda_client.create_batch_task(request)
        print("The batch task creation result is:", batch_task)
        return batch_task

    @staticmethod
    def __query_task(iotda_client, task_id):
        """
        Query the Details About a Batch Task

        @param iotda_client iotda client
        @param taskId      Task ID
        @return The details of the task
        """
        request = ShowBatchTaskRequest()
        request.instance_id = DeviceShadow.__INSTANCE_ID
        request.task_id = task_id
        show_batch_task_response = iotda_client.show_batch_task(request)
        print("The batch task details query result is:", show_batch_task_response)
        return show_batch_task_response.batchtask

    @staticmethod
    def main(args):
        # Initialize the IoTDA client
        iotda_client = DeviceShadow.__init_client()
        # Enter the deviceId of the device shadow to be configured.
        device_id = "64111bcd06ca5933b28a058b_saadas"
        # ID of the resource space to which the device belongs. Set this parameter to the ID of your resource space.
        app_id = "9d988b5efdf646f0828724c45e1d794e"
        try:
            print("========1.Query delivery shadow details========")
            DeviceShadow.__query_shadow_detail(iotda_client, device_id)
            print("========1.Querying the delivered shadow details completed========")
            print("========2.Configuring the Device Shadow========")
            # Set the expected structure of the device shadow.
            # The structure must be the same as that defined in the product. Modify the structure based on the site requirements.
            update_shadow_request = UpdateDesireds()
            shadow = []
            update_desired = UpdateDesired()
            update_desired.service_id = "BasicData"
            _map = {}
            _map["luminance"] = 99
            shadow.append(update_desired)
            update_desired.desired = _map
            update_shadow_request.shadow = shadow
            # Update Device Shadow
            DeviceShadow.__update_shadow(iotda_client, device_id, update_shadow_request)
            print("========2.Configuring the device shadow is complete========")
            print("========3.Querying Device Shadow Details========")
            DeviceShadow.__query_shadow_detail(iotda_client, device_id)
            print("========3.Querying device shadow details is complete========")
            print("========4.Creating a Task for Configuring Expected Values of Device Shadows in Batches========")
            # If you need to deliver the expected device shadow values of multiple devices at the same time,
            # you can use the function of setting expected device shadow values in batches.
            create_batch_task = CreateBatchTask()
            create_batch_task.app_id = app_id
            # Task name, which is unique in the resource space. If the task is invoked for multiple times, the task name needs to be changed.
            create_batch_task.task_name = "batchShadows"
            # The task type is fixed to updateDeviceShadows. Do not change it.
            create_batch_task.task_type = "updateDeviceShadows"
            # The target deviceId is the deviceId to be delivered and is added to the list.
            target_device_ids = []
            target_device_ids.append(device_id)
            create_batch_task.targets = target_device_ids
            # Expected value of the device shadow to be set. The format is as follows:
            # document {"shadow": [{"service_id": "WaterMeter","desired": {"temperature": "60"}}]}
            document = {}
            document["shadow"] = shadow
            create_batch_task.document = document
            # After a task is created, obtain the task ID.
            create_shadow_task = DeviceShadow.__create_task(iotda_client, create_batch_task)
            print("========4.The task for configuring expected values of device shadows in batches is created successfully.========")
            print("========5.Querying Batch Task Details========")
            # Query the execution details of the batch task to know the task execution status.
            status = create_shadow_task.status
            times = 0
            # Wait until the task is complete. The task status is Initializing. Waiting: The task is waiting. Processing: The task is being executed.
            # Success: The operation is successful. Fail: failed. PartialSuccess: partial success. Stopped: stopped. StoppingStopping.
            while (("Initializing" == status or "Waitting" == status or "Processing" == status) and times < 5):
                #  Query the execution details of the batch task to know the task execution status.
                task = DeviceShadow.__query_task(iotda_client, create_shadow_task.task_id)
                status = task.status
                time.sleep(10)
                times+=1
            print("========5.Querying the batch task details is complete========")
            print("========6.Querying Device Shadow Details========")
            # Querying and Delivering the Device Shadow List
            DeviceShadow.__query_shadow_detail(iotda_client, device_id)
            print("========6.Querying device shadow details is complete========")
            print("========7.Deleting a Device Shadow========")
            # Deletes the expected value of the device shadow. If the value of an attribute is set to null, the expected value is deleted.
            _map["luminance"] = None
            DeviceShadow.__update_shadow(iotda_client, device_id, update_shadow_request)
            print("========7.Deleting the device shadow is complete========")
            print("========8.Querying device shadow details is complete========")
            DeviceShadow.__query_shadow_detail(iotda_client, device_id)
            print("========8.Querying device shadow details is complete========")
        except (ConnectionException, RequestTimeoutException) as e:
            print("Demonstration failed. Exception information is", e.err_message)
        except ServiceResponseException as e:
            print("Demonstration failed. Exception information is", e.error_code, e.error_msg)


if __name__ == "__main__":
    DeviceShadow().main(any)