#!/usr/bin/python
#
# Copyright (c) 2018 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_servicebus_info

version_added: "0.1.2"

short_description: Get servicebus facts

description:
    - Get facts for a specific servicebus or all servicebus in a resource group or subscription.

options:
    name:
        description:
            - Limit results to a specific servicebus.
        type: str
    resource_group:
        description:
            - Limit results in a specific resource group.
        type: str
    tags:
        description:
            - Limit results by providing a list of tags. Format tags as 'key' or 'key:value'.
        type: list
        elements: str
    namespace:
        description:
            - Servicebus namespace name.
            - A namespace is a scoping container for all messaging components.
            - Multiple queues and topics can reside within a single namespace, and namespaces often serve as application containers.
            - Required when I(type=namespace).
        type: str
    type:
        description:
            - Type of the resource.
        type: str
        required: true
        choices:
            - namespace
            - queue
            - topic
            - subscription
    topic:
        description:
            - Topic name.
            - Required when I(type=subscription).
        type: str
    show_sas_policies:
        description:
            - Whether to show the SAS policies.
            - Not support when I(type=subscription).
            - Note if enable this option, the facts module will raise two more HTTP call for each resources, need more network overhead.
        type: bool
extends_documentation_fragment:
    - azure.azcollection.azure

author:
    - Yuwei Zhou (@yuwzho)

'''

EXAMPLES = '''
- name: Get all namespaces under a resource group
  azure_rm_servicebus_info:
    resource_group: myResourceGroup
    type: namespace

- name: Get all topics under a namespace
  azure_rm_servicebus_info:
    resource_group: myResourceGroup
    namespace: bar
    type: topic

- name: Get a single queue with SAS policies
  azure_rm_servicebus_info:
    resource_group: myResourceGroup
    namespace: bar
    type: queue
    name: sbqueue
    show_sas_policies: true

- name: Get all subscriptions under a resource group
  azure_rm_servicebus_info:
    resource_group: myResourceGroup
    type: subscription
    namespace: bar
    topic: sbtopic
'''
RETURN = '''
servicebuses:
    description:
        - List of servicebus dicts.
    returned: always
    type: complex
    contains:
        id:
            description:
                - Resource ID.
            returned: always
            type: str
            sample: "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/foo/providers/Microsoft.ServiceBus/
                     namespaces/bar/topics/baz/subscriptions/qux"
        name:
            description:
                - Resource name.
            returned: always
            type: str
            sample: qux
        location:
            description:
                - The Geo-location where the resource lives.
            returned: always
            type: str
            sample: eastus
        namespace:
            description:
                - I(namespace) name of the C(queue) or C(topic), C(subscription).
            returned: always
            type: str
            sample: bar
        topic:
            description:
                - Topic name of a subscription.
            returned: always
            type: str
            sample: baz
        tags:
            description:
                - Resource tags.
            returned: always
            type: dict
            sample: {env: sandbox}
        sku:
            description:
                - Properties of namespace's SKU.
            returned: always
            type: str
            sample: Standard
        provisioning_state:
            description:
                - Provisioning state of the namespace.
            returned: always
            type: str
            sample: Succeeded
        service_bus_endpoint:
            description:
                - Endpoint you can use to perform Service Bus operations.
            returned: always
            type: str
            sample: "https://bar.servicebus.windows.net:443/"
        metric_id:
            description:
                - Identifier for Azure Insights metrics of namespace.
            returned: always
            type: str
            sample: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX:bar"
        type:
            description:
                - Resource type.
                - Namespace is a scoping container for all messaging components.
                - Queue enables you to store messages until the receiving application is available to receive and process them.
                - Topic and subscriptions enable 1:n relationships between publishers and subscribers.
            returned: always
            type: str
            sample: "Microsoft.ServiceBus/Namespaces/Topics"
        created_at:
            description:
                - Exact time the message was created.
            returned: always
            type: str
            sample: "2019-01-25 02:46:55.543953+00:00"
        updated_at:
            description:
                - The exact time the message was updated.
            returned: always
            type: str
            sample: "2019-01-25 02:46:55.543953+00:00"
        accessed_at:
            description:
                - Last time the message was sent, or a request was received for this topic.
            returned: always
            type: str
            sample: "2019-01-25 02:46:55.543953+00:00"
        subscription_count:
            description:
                - Number of subscriptions under a topic.
            returned: always
            type: int
            sample: 1
        count_details:
            description:
                - Message count details.
            returned: always
            type: complex
            contains:
                active_message_count:
                    description:
                        - Number of active messages in the C(queue), C(topic), or C(subscription).
                    returned: always
                    type: int
                    sample: 0
                dead_letter_message_count:
                    description:
                        - Number of messages that are dead lettered.
                    returned: always
                    type: int
                    sample: 0
                scheduled_message_count:
                    description:
                        - Number of scheduled messages.
                    returned: always
                    type: int
                    sample: 0
                transfer_message_count:
                    description:
                        - Number of messages transferred to another C(queue), C(topic), or C(subscription).
                    returned: always
                    type: int
                    sample: 0
                transfer_dead_letter_message_count:
                    description:
                        - Number of messages transferred into dead letters.
                    returned: always
                    type: int
                    sample: 0
        support_ordering:
            description:
                - Value that indicates whether the C(topic) supports ordering.
            returned: always
            type: bool
            sample: true
        status:
            description:
              - The status of a messaging entity.
            returned: always
            type: str
            sample: active
        requires_session:
            description:
                - A value that indicates whether the C(queue) or C(topic) supports the concept of sessions.
            returned: always
            type: bool
            sample: true
        requires_duplicate_detection:
            description:
               - A value indicating if this C(queue) or C(topic) requires duplicate detection.
            returned: always
            type: bool
            sample: true
        max_size_in_mb:
            description:
                - Maximum size of the C(queue) or C(topic) in megabytes, which is the size of the memory allocated for the C(topic).
            returned: always
            type: int
            sample: 5120
        max_delivery_count:
            description:
                - The maximum delivery count.
                - A message is automatically deadlettered after this number of deliveries.
            returned: always
            type: int
            sample: 10
        lock_duration_in_seconds:
            description:
                - ISO 8601 timespan duration of a peek-lock.
                - The amount of time that the message is locked for other receivers.
                - The maximum value for LockDuration is 5 minutes.
            returned: always
            type: int
            sample: 60
        forward_to:
            description:
                - C(queue) or C(topic) name to forward the messages.
            returned: always
            type: str
            sample: quux
        forward_dead_lettered_messages_to:
            description:
                - C(queue) or C(topic) name to forward the Dead Letter message.
            returned: always
            type: str
            sample: corge
        enable_partitioning:
            description:
                - Value that indicates whether the C(queue) or C(topic) to be partitioned across multiple message brokers is enabled.
            returned: always
            type: bool
            sample: true
        enable_express:
            description:
                - Value that indicates whether Express Entities are enabled.
                - An express topic holds a message in memory temporarily before writing it to persistent storage.
            returned: always
            type: bool
            sample: true
        enable_batched_operations:
            description:
                - Value that indicates whether server-side batched operations are enabled.
            returned: always
            type: bool
            sample: true
        duplicate_detection_time_in_seconds:
            description:
                - ISO 8601 timeSpan structure that defines the duration of the duplicate detection history.
            returned: always
            type: int
            sample: 600
        default_message_time_to_live_seconds:
            description:
                - ISO 8061 Default message timespan to live value.
                - This is the duration after which the message expires, starting from when the message is sent to Service Bus.
                - This is the default value used when TimeToLive is not set on a message itself.
            returned: always
            type: int
            sample: 0
        dead_lettering_on_message_expiration:
            description:
                - A value that indicates whether this C(queue) or C(topic) has dead letter support when a message expires.
            returned: always
            type: int
            sample: 0
        dead_lettering_on_filter_evaluation_exceptions:
            description:
                - Value that indicates whether a subscription has dead letter support on filter evaluation exceptions.
            returned: always
            type: int
            sample: 0
        auto_delete_on_idle_in_seconds:
            description:
                - ISO 8061 timeSpan idle interval after which the  queue or topic is automatically deleted.
                - The minimum duration is 5 minutes.
            returned: always
            type: int
            sample: true
        size_in_bytes:
            description:
                - The size of the C(queue) or C(topic) in bytes.
            returned: always
            type: int
            sample: 0
        message_count:
            description:
                - Number of messages.
            returned: always
            type: int
            sample: 10
        sas_policies:
            description:
                - Dict of SAS policies.
                - Will not be returned until I(show_sas_policy) set.
            returned: always
            type: dict
            sample:  {
                        "testpolicy1": {
                            "id": "/subscriptions/XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXX/resourceGroups/
                                   foo/providers/Microsoft.ServiceBus/namespaces/bar/queues/qux/authorizationRules/testpolicy1",
                            "keys": {
                                "key_name": "testpolicy1",
                                "primary_connection_string": "Endpoint=sb://bar.servicebus.windows.net/;
                                                              SharedAccessKeyName=testpolicy1;SharedAccessKey=XXXXXXXXXXXXXXXXX;EntityPath=qux",
                                "primary_key": "XXXXXXXXXXXXXXXXX",
                                "secondary_connection_string": "Endpoint=sb://bar.servicebus.windows.net/;
                                                                SharedAccessKeyName=testpolicy1;SharedAccessKey=XXXXXXXXXXXXXXX;EntityPath=qux",
                                "secondary_key": "XXXXXXXXXXXXXXX"
                            },
                            "name": "testpolicy1",
                            "rights": "listen_send",
                            "type": "Microsoft.ServiceBus/Namespaces/Queues/AuthorizationRules"
                        }
                     }
        private_endpoint_connections:
            description:
                - Properties of the PrivateEndpointConnection.
            type: list
            returned: always
            sample: [{
                        "id": "/subscriptions/xxxxxx/resourceGroups/myRG/providers/Microsoft.ServiceBus/namespaces/fredVM/privateEndpointConnections/xxxxxxxx",
                        "name": "xxxxxx",
                        "private_endpoint": {
                            "id": "/subscriptions/xxxxx/resourceGroups/myRG/providers/Microsoft.Network/privateEndpoints/fredprivateendpoint"
                        },
                        "private_link_service_connection_state": {
                            "description": "Auto-Approved",
                            "status": "Approved"
                        },
                        "provisioning_state": "Succeeded",
                        "type": "Microsoft.ServiceBus/Namespaces/PrivateEndpointConnections"
                    }]

'''

try:
    from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import AzureRMModuleBase
    from azure.mgmt.servicebus.v2022_10_01_preview.models import Identity
except Exception:
    # This is handled in azure_rm_common
    pass
from ansible.module_utils.common.dict_transformations import _camel_to_snake
from ansible.module_utils._text import to_native
from datetime import datetime, timedelta

duration_spec_map = dict(
    default_message_time_to_live='default_message_time_to_live_seconds',
    duplicate_detection_history_time_window='duplicate_detection_time_in_seconds',
    auto_delete_on_idle='auto_delete_on_idle_in_seconds',
    lock_duration='lock_duration_in_seconds'
)


def is_valid_timedelta(value):
    if value == timedelta(10675199, 10085, 477581):
        return None
    return value


class AzureRMServiceBusInfo(AzureRMModuleBase):

    def __init__(self):

        self.module_arg_spec = dict(
            name=dict(type='str'),
            resource_group=dict(type='str'),
            tags=dict(type='list', elements='str'),
            type=dict(type='str', required=True, choices=['namespace', 'topic', 'queue', 'subscription']),
            namespace=dict(type='str'),
            topic=dict(type='str'),
            show_sas_policies=dict(type='bool')
        )

        required_if = [
            ('type', 'subscription', ['topic', 'resource_group', 'namespace']),
            ('type', 'topic', ['resource_group', 'namespace']),
            ('type', 'queue', ['resource_group', 'namespace'])
        ]

        self.results = dict(
            changed=False,
            servicebuses=[]
        )

        self.name = None
        self.resource_group = None
        self.tags = None
        self.type = None
        self.namespace = None
        self.topic = None
        self.show_sas_policies = None

        super(AzureRMServiceBusInfo, self).__init__(self.module_arg_spec,
                                                    supports_check_mode=True,
                                                    supports_tags=False,
                                                    required_if=required_if,
                                                    facts_module=True)

    def exec_module(self, **kwargs):

        for key in self.module_arg_spec:
            setattr(self, key, kwargs[key])

        response = []
        if self.name:
            response = self.get_item()
        elif self.resource_group:
            response = self.list_items()
        else:
            response = self.list_all_items()

        self.results['servicebuses'] = [self.instance_to_dict(x) for x in response]
        return self.results

    def instance_to_dict(self, instance):
        result = dict()
        instance_type = getattr(self.servicebus_models, 'SB{0}'.format(str.capitalize(self.type)))
        attribute_map = instance_type._attribute_map
        for attribute in attribute_map.keys():
            value = getattr(instance, attribute)
            if attribute_map[attribute]['type'] == 'duration':
                if is_valid_timedelta(value):
                    key = duration_spec_map.get(attribute) or attribute
                    result[key] = int(value.total_seconds())
            elif attribute == 'status':
                result['status'] = _camel_to_snake(value)
            elif isinstance(value, self.servicebus_models.MessageCountDetails):
                result[attribute] = value.as_dict()
            elif isinstance(value, self.servicebus_models.SBSku):
                result[attribute] = value.name.lower()
            elif isinstance(value, datetime):
                result[attribute] = str(value)
            elif isinstance(value, Identity):
                result['identity'] = value.as_dict()
            elif isinstance(value, str):
                result[attribute] = to_native(value)
            elif attribute == 'max_size_in_megabytes':
                result['max_size_in_mb'] = value
            elif attribute == 'private_endpoint_connections' and isinstance(value, list):
                result['private_endpoint_connections'] = [item.as_dict() for item in value]
            else:
                result[attribute] = value
        if self.show_sas_policies and self.type != 'subscription':
            policies = self.get_auth_rules()
            for name in policies.keys():
                policies[name]['keys'] = self.get_sas_key(name)
            result['sas_policies'] = policies
        if self.namespace:
            result['namespace'] = self.namespace
        if self.topic:
            result['topic'] = self.topic
        return result

    def _get_client(self):
        return getattr(self.servicebus_client, '{0}s'.format(self.type))

    def get_item(self):
        try:
            client = self._get_client()
            if self.type == 'namespace':
                item = client.get(self.resource_group, self.name)
                return [item] if self.has_tags(item.tags, self.tags) else []
            elif self.type == 'subscription':
                return [client.get(self.resource_group, self.namespace, self.topic, self.name)]
            else:
                return [client.get(self.resource_group, self.namespace, self.name)]
        except Exception:
            pass
        return []

    def list_items(self):
        try:
            client = self._get_client()
            if self.type == 'namespace':
                response = client.list_by_resource_group(self.resource_group)
                return [x for x in response if self.has_tags(x.tags, self.tags)]
            elif self.type == 'subscription':
                return client.list_by_topic(self.resource_group, self.namespace, self.topic)
            else:
                return client.list_by_namespace(self.resource_group, self.namespace)
        except Exception as exc:
            self.fail("Failed to list items - {0}".format(str(exc)))
        return []

    def list_all_items(self):
        self.log("List all items in subscription")
        try:
            if self.type != 'namespace':
                return []
            response = self.servicebus_client.namespaces.list()
            return [x for x in response if self.has_tags(x.tags, self.tags)]
        except Exception as exc:
            self.fail("Failed to list all items - {0}".format(str(exc)))
        return []

    def get_auth_rules(self):
        result = dict()
        try:
            client = self._get_client()
            if self.type == 'namespace':
                rules = client.list_authorization_rules(self.resource_group, self.name)
            else:
                rules = client.list_authorization_rules(self.resource_group, self.namespace, self.name)
            while True:
                rule = rules.next()
                result[rule.name] = self.policy_to_dict(rule)
        except StopIteration:
            pass
        except Exception as exc:
            self.fail('Error when getting SAS policies for {0} {1}: {2}'.format(self.type, self.name, exc.message or str(exc)))
        return result

    def get_sas_key(self, name):
        try:
            client = self._get_client()
            if self.type == 'namespace':
                return client.list_keys(self.resource_group, self.name, name).as_dict()
            else:
                return client.list_keys(self.resource_group, self.namespace, self.name, name).as_dict()
        except Exception as exc:
            self.fail('Error when getting SAS policy {0}\'s key - {1}'.format(name, exc.message or str(exc)))
        return None

    def policy_to_dict(self, rule):
        result = rule.as_dict()
        rights = result['rights']
        if 'Manage' in rights:
            result['rights'] = 'manage'
        elif 'Listen' in rights and 'Send' in rights:
            result['rights'] = 'listen_send'
        else:
            result['rights'] = rights[0].lower()
        return result


def main():
    AzureRMServiceBusInfo()


if __name__ == '__main__':
    main()
