#!/usr/bin/python
#
# Copyright (c) 2019 Yuwei Zhou, <yuwzho@microsoft.com>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type


DOCUMENTATION = '''
---
module: azure_rm_iothub
version_added: "0.1.2"
short_description: Manage Azure IoT hub
description:
    - Create, delete an Azure IoT hub.
options:
    resource_group:
        description:
            - Name of resource group.
        type: str
        required: true
    name:
        description:
            - Name of the IoT hub.
        type: str
        required: true
    state:
        description:
            - State of the IoT hub. Use C(present) to create or update an IoT hub and C(absent) to delete an IoT hub.
        type: str
        default: present
        choices:
            - absent
            - present
    location:
        description:
            - Location of the IoT hub.
        type: str
    sku:
        description:
            - Pricing tier for Azure IoT Hub.
            - Note that only one free IoT hub instance is allowed in each subscription. Exception will be thrown if free instances exceed one.
            - Default is C(s1) when creation.
        type: str
        choices:
            - b1
            - b2
            - b3
            - f1
            - s1
            - s2
            - s3
    unit:
        description:
            - Units in your IoT Hub.
            - Default is C(1).
        type: int
    event_endpoint:
        description:
            - The Event Hub-compatible endpoint property.
        type: dict
        suboptions:
            partition_count:
                description:
                    - The number of partitions for receiving device-to-cloud messages in the Event Hub-compatible endpoint.
                    - "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#device-to-cloud-messages)."
                    - Default is C(2).
                type: int
            retention_time_in_days:
                description:
                    - The retention time for device-to-cloud messages in days.
                    - "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#device-to-cloud-messages)."
                    - Default is C(1).
                type: int
    enable_file_upload_notifications:
        description:
            - File upload notifications are enabled if set to C(True).
        type: bool
    ip_filters:
        description:
            - Configure rules for rejecting or accepting traffic from specific IPv4 addresses.
        type: list
        elements: dict
        suboptions:
            name:
                description:
                    - Name of the filter.
                type: str
                required: yes
            ip_mask:
                description:
                    - A string that contains the IP address range in CIDR notation for the rule.
                type: str
                required: yes
            action:
                description:
                    - The desired action for requests captured by this rule.
                type: str
                required: yes
                choices:
                    - accept
                    - reject
    routing_endpoints:
        description:
            - Custom endpoints.
        type: list
        elements: dict
        suboptions:
            name:
                description:
                    - Name of the custom endpoint.
                type: str
                required: yes
            resource_group:
                description:
                    - Resource group of the endpoint.
                    - Default is the same as I(resource_group).
                type: str
            subscription:
                description:
                    - Subscription id of the endpoint.
                    - Default is the same as I(subscription).
                type: str
            resource_type:
                description:
                    - Resource type of the custom endpoint.
                type: str
                choices:
                    - eventhub
                    - queue
                    - storage
                    - topic
                required: yes
            connection_string:
                description:
                    - Connection string of the custom endpoint.
                    - The connection string should have send privilege.
                type: str
                required: yes
            container_name:
                description:
                    - Container name of the custom endpoint when I(resource_type=storage).
                type: str
                aliases:
                    - container
            encoding:
                description:
                    - Encoding of the message when I(resource_type=storage).
                type: str
    routes:
        description:
            - Route device-to-cloud messages to service-facing endpoints.
        type: list
        elements: dict
        suboptions:
            name:
                description:
                    - Name of the route.
                type: str
                required: yes
            source:
                description:
                    - The origin of the data stream to be acted upon.
                type: str
                choices:
                    - device_messages
                    - twin_change_events
                    - device_lifecycle_events
                    - device_job_lifecycle_events
                required: yes
            enabled:
                description:
                    - Whether to enable the route.
                type: bool
                required: yes
            endpoint_name:
                description:
                    - The name of the endpoint in I(routing_endpoints) where IoT Hub sends messages that match the query.
                type: str
                required: yes
            condition:
                description:
                    - "The query expression for the routing query that is run against the message application properties,
                       system properties, message body, device twin tags, and device twin properties to determine if it is a match for the endpoint."
                    - "For more information about constructing a query,
                       see U(https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-routing-query-syntax)"
                type: str
extends_documentation_fragment:
    - azure.azcollection.azure
    - azure.azcollection.azure_tags
    - azure.azcollection.azure_identity_multiple_user

author:
    - Yuwei Zhou (@yuwzho)

'''

EXAMPLES = '''
- name: Create a simplest IoT hub
  azure_rm_iothub:
    name: Testing
    resource_group: myResourceGroup
- name: Create an IoT hub with route
  azure_rm_iothub:
    resource_group: myResourceGroup
    name: Testing
    routing_endpoints:
      - connection_string: "Endpoint=sb://qux.servicebus.windows.net/;SharedAccessKeyName=quux;SharedAccessKey=****;EntityPath=myQueue"
        name: foo
        resource_type: queue
        resource_group: myResourceGroup1
    routes:
      - name: bar
        source: device_messages
        endpoint_name: foo
        enabled: true
'''

RETURN = '''
id:
    description:
        - Resource ID of the IoT hub.
    sample: "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/myResourceGroup/providers/Microsoft.Devices/IotHubs/Testing"
    returned: success
    type: str
name:
    description:
        - Name of the IoT hub.
    sample: Testing
    returned: success
    type: str
resource_group:
    description:
        - Resource group of the IoT hub.
    sample: myResourceGroup.
    returned: success
    type: str
location:
    description:
        - Location of the IoT hub.
    sample: eastus
    returned: success
    type: str
unit:
    description:
        - Units in the IoT Hub.
    sample: 1
    returned: success
    type: int
sku:
    description:
        - Pricing tier for Azure IoT Hub.
    sample: f1
    returned: success
    type: str
cloud_to_device:
    description:
        - Cloud to device message properties.
    contains:
        max_delivery_count:
            description:
                - The number of times the IoT hub attempts to deliver a message on the feedback queue.
                - "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#cloud-to-device-messages)."
            type: int
            returned: success
            sample: 10
        ttl_as_iso8601:
            description:
                - The period of time for which a message is available to consume before it is expired by the IoT hub.
                - "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#cloud-to-device-messages)."
            type: str
            returned: success
            sample: "1:00:00"
    returned: success
    type: complex
enable_file_upload_notifications:
    description:
        - Whether file upload notifications are enabled.
    sample: True
    returned: success
    type: bool
event_endpoints:
    description:
        - Built-in endpoint where to deliver device message.
    contains:
        endpoint:
            description:
                - The Event Hub-compatible endpoint.
            type: str
            returned: success
            sample: "sb://iothub-ns-testing-1478811-9bbc4a15f0.servicebus.windows.net/"
        partition_count:
            description:
                - The number of partitions for receiving device-to-cloud messages in the Event Hub-compatible endpoint.
                - "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#device-to-cloud-messages)."
            type: int
            returned: success
            sample: 2
        retention_time_in_days:
            description:
                - The retention time for device-to-cloud messages in days.
                - "See U(https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-messaging#device-to-cloud-messages)."
            type: int
            returned: success
            sample: 1
        partition_ids:
            description:
                - List of the partition id for the event endpoint.
            type: list
            returned: success
            sample: ["0", "1"]
    returned: success
    type: complex
host_name:
    description:
        - Host of the IoT hub.
    sample: "testing.azure-devices.net"
    returned: success
    type: str
ip_filters:
    description:
        - Configure rules for rejecting or accepting traffic from specific IPv4 addresses.
    contains:
        name:
            description:
                - Name of the filter.
            type: str
            returned: success
            sample: filter
        ip_mask:
            description:
                - A string that contains the IP address range in CIDR notation for the rule.
            type: str
            returned: success
            sample: 40.54.7.3
        action:
            description:
                - The desired action for requests captured by this rule.
            type: str
            returned: success
            sample: Reject
    returned: success
    type: complex
routing_endpoints:
    description:
        - Custom endpoints.
    contains:
        event_hubs:
            description:
                - List of custom endpoints of event hubs.
            type: complex
            returned: success
            contains:
                name:
                    description:
                        - Name of the custom endpoint.
                    type: str
                    returned: success
                    sample: foo
                resource_group:
                    description:
                        - Resource group of the endpoint.
                    type: str
                    returned: success
                    sample: bar
                subscription:
                    description:
                        - Subscription id of the endpoint.
                    type: str
                    returned: success
                    sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
                connection_string:
                    description:
                        - Connection string of the custom endpoint.
                    type: str
                    returned: success
                    sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
        service_bus_queues:
            description:
                - List of custom endpoints of service bus queue.
            type: complex
            returned: always
            contains:
                name:
                    description:
                        - Name of the custom endpoint.
                    type: str
                    returned: success
                    sample: foo
                resource_group:
                    description:
                        - Resource group of the endpoint.
                    type: str
                    returned: success
                    sample: bar
                subscription:
                    description:
                        - Subscription ID of the endpoint.
                    type: str
                    returned: success
                    sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
                connection_string:
                    description:
                        - Connection string of the custom endpoint.
                    type: str
                    returned: success
                    sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
        service_bus_topics:
            description:
                - List of custom endpoints of service bus topic.
            type: complex
            returned: success
            contains:
                name:
                    description:
                        - Name of the custom endpoint.
                    type: str
                    returned: success
                    sample: foo
                resource_group:
                    description:
                        - Resource group of the endpoint.
                    type: str
                    returned: success
                    sample: bar
                subscription:
                    description:
                        - Subscription ID of the endpoint.
                    type: str
                    returned: success
                    sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
                connection_string:
                    description:
                        - Connection string of the custom endpoint.
                    type: str
                    returned: success
                    sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
        storage_containers:
            description:
                - List of custom endpoints of storage
            type: complex
            returned: success
            contains:
                name:
                    description:
                        - Name of the custom endpoint.
                    type: str
                    returned: success
                    sample: foo
                resource_group:
                    description:
                        - Resource group of the endpoint.
                    type: str
                    returned: success
                    sample: bar
                subscription:
                    description:
                        - Subscription ID of the endpoint.
                    type: str
                    returned: success
                    sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
                connection_string:
                    description:
                        - Connection string of the custom endpoint.
                    type: str
                    returned: success
                    sample: "Endpoint=sb://quux.servicebus.windows.net:5671/;SharedAccessKeyName=qux;SharedAccessKey=****;EntityPath=foo"
    returned: success
    type: complex
routes:
    description:
        - Route device-to-cloud messages to service-facing endpoints.
    type: complex
    returned: success
    contains:
        name:
            description:
                - Name of the route.
            type: str
            returned: success
            sample: route1
        source:
            description:
                - The origin of the data stream to be acted upon.
            type: str
            returned: success
            sample: device_messages
        enabled:
            description:
                - Whether to enable the route.
            type: str
            returned: success
            sample: true
        endpoint_name:
            description:
                - The name of the endpoint in C(routing_endpoints) where IoT Hub sends messages that match the query.
            type: str
            returned: success
            sample: foo
        condition:
            description:
                - "The query expression for the routing query that is run against the message application properties,
                    system properties, message body, device twin tags, and device twin properties to determine if it is a match for the endpoint."
                - "For more information about constructing a query,
                    see I(https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-routing-query-syntax)"
            type: bool
            returned: success
            sample: "true"
'''  # NOQA

from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common_ext import AzureRMModuleBaseExt
from ansible.module_utils.common.dict_transformations import _snake_to_camel, _camel_to_snake
import re

try:
    from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common_ext import AzureRMModuleBaseExt
    from azure.mgmt.iothub.v2023_06_30_preview import models as IotHubModels
except ImportError:
    # This is handled in azure_rm_common
    pass


ip_filter_spec = dict(
    name=dict(type='str', required=True),
    ip_mask=dict(type='str', required=True),
    action=dict(type='str', required=True, choices=['accept', 'reject'])
)


routing_endpoints_spec = dict(
    connection_string=dict(type='str', required=True),
    name=dict(type='str', required=True),
    resource_group=dict(type='str'),
    subscription=dict(type='str'),
    resource_type=dict(type='str', required=True, choices=['eventhub', 'queue', 'storage', 'topic']),
    container_name=dict(type='str', aliases=['container']),
    encoding=dict(type='str')
)


routing_endpoints_resource_type_mapping = {
    'eventhub': {'model': 'RoutingEventHubProperties', 'attribute': 'event_hubs'},
    'queue': {'model': 'RoutingServiceBusQueueEndpointProperties', 'attribute': 'service_bus_queues'},
    'topic': {'model': 'RoutingServiceBusTopicEndpointProperties', 'attribute': 'service_bus_topics'},
    'storage': {'model': 'RoutingStorageContainerProperties', 'attribute': 'storage_containers'}
}


routes_spec = dict(
    name=dict(type='str', required=True),
    source=dict(type='str', required=True, choices=['device_messages', 'twin_change_events', 'device_lifecycle_events', 'device_job_lifecycle_events']),
    enabled=dict(type='bool', required=True),
    endpoint_name=dict(type='str', required=True),
    condition=dict(type='str')
)


event_endpoint_spec = dict(
    partition_count=dict(type='int'),
    retention_time_in_days=dict(type='int')
)


class AzureRMIoTHub(AzureRMModuleBaseExt):

    def __init__(self):

        self.module_arg_spec = dict(
            resource_group=dict(type='str', required=True),
            name=dict(type='str', required=True),
            state=dict(type='str', default='present', choices=['present', 'absent']),
            location=dict(type='str'),
            sku=dict(type='str', choices=['b1', 'b2', 'b3', 'f1', 's1', 's2', 's3']),
            unit=dict(type='int'),
            event_endpoint=dict(type='dict', options=event_endpoint_spec),
            enable_file_upload_notifications=dict(type='bool'),
            ip_filters=dict(type='list', elements='dict', options=ip_filter_spec),
            routing_endpoints=dict(type='list', elements='dict', options=routing_endpoints_spec),
            routes=dict(type='list', elements='dict', options=routes_spec),
            identity=dict(
                type='dict',
                options=self.managed_identity_multiple_user_assigned_spec
            ),
        )

        self.results = dict(
            changed=False,
            id=None
        )

        self.resource_group = None
        self.name = None
        self.state = None
        self.location = None
        self.sku = None
        self.unit = None
        self.event_endpoint = None
        self.tags = None
        self.enable_file_upload_notifications = None
        self.ip_filters = None
        self.routing_endpoints = None
        self.routes = None
        self.identity = None
        self._managed_identity = None

        super(AzureRMIoTHub, self).__init__(self.module_arg_spec, supports_check_mode=True)

    @property
    def managed_identity(self):
        if not self._managed_identity:
            self._managed_identity = {"identity": IotHubModels.ArmIdentity,
                                      "user_assigned": IotHubModels.ManagedIdentity
                                      }
        return self._managed_identity

    def exec_module(self, **kwargs):

        for key in list(self.module_arg_spec.keys()) + ['tags']:
            setattr(self, key, kwargs[key])

        changed = False

        if not self.location:
            # Set default location
            resource_group = self.get_resource_group(self.resource_group)
            self.location = resource_group.location
        self.sku = str.capitalize(self.sku) if self.sku else None
        iothub = self.get_hub()
        if self.state == 'present':
            if not iothub:
                changed = True
                self.sku = self.sku or 'S1'
                self.unit = self.unit or 1
                self.event_endpoint = self.event_endpoint or {}
                self.event_endpoint['partition_count'] = self.event_endpoint.get('partition_count') or 2
                self.event_endpoint['retention_time_in_days'] = self.event_endpoint.get('retention_time_in_days') or 1
                event_hub_properties = dict()
                event_hub_properties['events'] = self.IoThub_models.EventHubProperties(**self.event_endpoint)
                iothub_property = self.IoThub_models.IotHubProperties(event_hub_endpoints=event_hub_properties)
                if self.enable_file_upload_notifications:
                    iothub_property.enable_file_upload_notifications = self.enable_file_upload_notifications
                if self.ip_filters:
                    iothub_property.ip_filter_rules = self.construct_ip_filters()
                routing_endpoints = None
                routes = None
                if self.routing_endpoints:
                    routing_endpoints = self.construct_routing_endpoint(self.routing_endpoints)
                if self.routes:
                    routes = [self.construct_route(x) for x in self.routes]
                if routes or routing_endpoints:
                    routing_property = self.IoThub_models.RoutingProperties(endpoints=routing_endpoints,
                                                                            routes=routes)
                    iothub_property.routing = routing_property
                if self.identity:
                    identities_changed, self.identity = self.update_managed_identity(new_identity=self.identity)
                iothub = self.IoThub_models.IotHubDescription(location=self.location,
                                                              sku=self.IoThub_models.IotHubSkuInfo(name=self.sku, capacity=self.unit),
                                                              properties=iothub_property,
                                                              tags=self.tags,
                                                              identity=self.identity)
                if not self.check_mode:
                    iothub = self.create_or_update_hub(iothub)
            else:
                # compare sku
                original_sku = iothub.sku
                if self.sku and self.sku != original_sku.name:
                    self.log('SKU changed')
                    iothub.sku.name = self.sku
                    changed = True
                if self.unit and self.unit != original_sku.capacity:
                    self.log('Unit count changed')
                    iothub.sku.capacity = self.unit
                    changed = True
                # compare event hub property
                event_hub = iothub.properties.event_hub_endpoints or dict()
                if self.event_endpoint:
                    item = self.event_endpoint
                    original_item = event_hub.get('events')
                    if not original_item:
                        changed = True
                        event_hub['events'] = self.IoThub_models.EventHubProperties(partition_count=item.get('partition_count') or 2,
                                                                                    retention_time_in_days=item.get('retention_time_in_days') or 1)
                    elif item.get('partition_count') and original_item.partition_count != item['partition_count']:
                        changed = True
                        original_item.partition_count = item['partition_count']
                    elif item.get('retention_time_in_days') and original_item.retention_time_in_days != item['retention_time_in_days']:
                        changed = True
                        original_item.retention_time_in_days = item['retention_time_in_days']
                # compare endpoint
                original_endpoints = iothub.properties.routing.endpoints
                endpoint_changed = False
                if self.routing_endpoints:
                    # find the total length
                    total_length = 0
                    for item in routing_endpoints_resource_type_mapping.values():
                        attribute = item['attribute']
                        array = getattr(original_endpoints, attribute)
                        total_length += len(array or [])
                    if total_length != len(self.routing_endpoints):
                        endpoint_changed = True
                    else:  # If already changed, no need to compare any more
                        for item in self.routing_endpoints:
                            if not self.lookup_endpoint(item, original_endpoints):
                                endpoint_changed = True
                                break
                    if endpoint_changed:
                        iothub.properties.routing.endpoints = self.construct_routing_endpoint(self.routing_endpoints)
                        changed = True
                # compare routes
                original_routes = iothub.properties.routing.routes
                routes_changed = False
                if self.routes:
                    if len(self.routes) != len(original_routes or []):
                        routes_changed = True
                    else:
                        for item in self.routes:
                            if not self.lookup_route(item, original_routes):
                                routes_changed = True
                                break
                    if routes_changed:
                        changed = True
                        iothub.properties.routing.routes = [self.construct_route(x) for x in self.routes]
                # compare IP filter
                ip_filter_changed = False
                original_ip_filter = iothub.properties.ip_filter_rules
                if self.ip_filters:
                    if len(self.ip_filters) != len(original_ip_filter or []):
                        ip_filter_changed = True
                    else:
                        for item in self.ip_filters:
                            if not self.lookup_ip_filter(item, original_ip_filter):
                                ip_filter_changed = True
                                break
                    if ip_filter_changed:
                        changed = True
                        iothub.properties.ip_filter_rules = self.construct_ip_filters()

                # compare tags
                tag_changed, updated_tags = self.update_tags(iothub.tags)
                iothub.tags = updated_tags

                # compare identity
                identity_changed = False
                if self.identity:
                    identity_changed, iothub.identity = self.update_managed_identity(new_identity=self.identity, curr_identity=iothub.identity.as_dict())

                if (changed or identity_changed) and not self.check_mode:
                    changed = True
                    iothub = self.create_or_update_hub(iothub)
                # only tags changed
                if not changed and tag_changed:
                    changed = True
                    if not self.check_mode:
                        iothub = self.update_instance_tags(updated_tags)
            self.results = self.to_dict(iothub)
        elif iothub:
            changed = True
            if not self.check_mode:
                self.delete_hub()
        self.results['changed'] = changed
        return self.results

    def lookup_ip_filter(self, target, ip_filters):
        if not ip_filters or len(ip_filters) == 0:
            return False
        for item in ip_filters:
            if item.filter_name == target['name']:
                if item.ip_mask != target['ip_mask']:
                    return False
                if item.action.lower() != target['action']:
                    return False
                return True
        return False

    def lookup_route(self, target, routes):
        if not routes or len(routes) == 0:
            return False
        for item in routes:
            if item.name == target['name']:
                if target['source'] != _camel_to_snake(item.source):
                    return False
                if target['enabled'] != item.is_enabled:
                    return False
                if target['endpoint_name'] != item.endpoint_names[0]:
                    return False
                if target.get('condition') and target['condition'] != item.condition:
                    return False
                return True
        return False

    def lookup_endpoint(self, target, routing_endpoints):
        resource_type = target['resource_type']
        attribute = routing_endpoints_resource_type_mapping[resource_type]['attribute']
        endpoints = getattr(routing_endpoints, attribute)
        if not endpoints or len(endpoints) == 0:
            return False
        for item in endpoints:
            if item.name == target['name']:
                if target.get('resource_group') and target['resource_group'] != (item.resource_group or self.resource_group):
                    return False
                if target.get('subscription_id') and target['subscription_id'] != (item.subscription_id or self.subscription_id):
                    return False
                connection_string_regex = item.connection_string.replace('****', '.*')
                connection_string_regex = re.sub(r':\d+/;', '/;', connection_string_regex)
                if not re.search(connection_string_regex, target['connection_string']):
                    return False
                if resource_type == 'storage':
                    if target.get('container_name') and item.container_name != target['container_name']:
                        return False
                    if target.get('encoding') and item.encoding != target['encoding']:
                        return False
                return True
        return False

    def construct_ip_filters(self):
        return [self.IoThub_models.IpFilterRule(filter_name=x['name'],
                                                action=self.IoThub_models.IpFilterActionType[x['action']],
                                                ip_mask=x['ip_mask']) for x in self.ip_filters]

    def construct_routing_endpoint(self, routing_endpoints):
        if not routing_endpoints or len(routing_endpoints) == 0:
            return None
        result = self.IoThub_models.RoutingEndpoints()
        for endpoint in routing_endpoints:
            resource_type_property = routing_endpoints_resource_type_mapping.get(endpoint['resource_type'])
            resource_type = getattr(self.IoThub_models, resource_type_property['model'])
            array = getattr(result, resource_type_property['attribute']) or []
            array.append(resource_type(**endpoint))
            setattr(result, resource_type_property['attribute'], array)
        return result

    def construct_route(self, route):
        if not route:
            return None
        return self.IoThub_models.RouteProperties(name=route['name'],
                                                  source=_snake_to_camel(snake=route['source'], capitalize_first=True),
                                                  is_enabled=route['enabled'],
                                                  endpoint_names=[route['endpoint_name']],
                                                  condition=route.get('condition'))

    def get_hub(self):
        try:
            return self.IoThub_client.iot_hub_resource.get(self.resource_group, self.name)
        except Exception:
            return None

    def create_or_update_hub(self, hub):
        try:
            poller = self.IoThub_client.iot_hub_resource.begin_create_or_update(self.resource_group, self.name, hub, if_match=hub.etag)
            return self.get_poller_result(poller)
        except Exception as exc:
            self.fail('Error creating or updating IoT Hub {0}: {1}'.format(self.name, exc.message or str(exc)))

    def update_instance_tags(self, tags):
        try:
            poller = self.IoThub_client.iot_hub_resource.begin_update(self.resource_group, self.name, tags=tags)
            return self.get_poller_result(poller)
        except Exception as exc:
            self.fail('Error updating IoT Hub {0}\'s tag: {1}'.format(self.name, exc.message or str(exc)))

    def delete_hub(self):
        try:
            self.IoThub_client.iot_hub_resource.begin_delete(self.resource_group, self.name)
            return True
        except Exception as exc:
            self.fail('Error deleting IoT Hub {0}: {1}'.format(self.name, exc.message or str(exc)))
            return False

    def route_to_dict(self, route):
        return dict(
            name=route.name,
            source=_camel_to_snake(route.source),
            endpoint_name=route.endpoint_names[0],
            enabled=route.is_enabled,
            condition=route.condition
        )

    def instance_dict_to_dict(self, instance_dict):
        result = dict()
        if not instance_dict:
            return result
        for key in instance_dict.keys():
            result[key] = instance_dict[key].as_dict()
        return result

    def to_dict(self, hub):
        result = dict()
        properties = hub.properties
        result['id'] = hub.id
        result['name'] = hub.name
        result['resource_group'] = self.resource_group
        result['location'] = hub.location
        result['tags'] = hub.tags
        result['unit'] = hub.sku.capacity
        result['sku'] = hub.sku.name.lower()
        result['cloud_to_device'] = dict(
            max_delivery_count=properties.cloud_to_device.feedback.max_delivery_count,
            ttl_as_iso8601=str(properties.cloud_to_device.feedback.ttl_as_iso8601)
        ) if properties.cloud_to_device else dict()
        result['enable_file_upload_notifications'] = properties.enable_file_upload_notifications
        result['event_endpoint'] = properties.event_hub_endpoints.get('events').as_dict() if properties.event_hub_endpoints.get('events') else None
        result['host_name'] = properties.host_name
        result['ip_filters'] = [x.as_dict() for x in properties.ip_filter_rules]
        if properties.routing:
            result['routing_endpoints'] = properties.routing.endpoints.as_dict()
            result['routes'] = [self.route_to_dict(x) for x in properties.routing.routes]
            result['fallback_route'] = self.route_to_dict(properties.routing.fallback_route)
        result['status'] = properties.state
        result['storage_endpoints'] = self.instance_dict_to_dict(properties.storage_endpoints)
        if hasattr(hub, 'identity'):
            result['identity'] = hub.identity.as_dict() if hub.identity else None
        return result


def main():
    AzureRMIoTHub()


if __name__ == '__main__':
    main()
