#!/usr/bin/python
#
# Copyright (c) 2018 Hai Cao, <t-haicao@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_trafficmanager
version_added: "0.1.2"
short_description: Manage a Traffic Manager profile.
description:
    - Create, update and delete a Traffic Manager profile.

options:
    resource_group:
        description:
            - Name of a resource group where the Traffic Manager profile exists or will be created.
        required: true
        type: str
    name:
        description:
            - Name of the Traffic Manager profile.
        required: true
        type: str
    state:
        description:
            - Assert the state of the Traffic Manager profile. Use C(present) to create or update a Traffic Manager profile and C(absent) to delete it.
        default: present
        type: str
        choices:
            - absent
            - present
    location:
        description:
            - Valid azure location. Defaults to 'global'.
        type: str
    profile_status:
        description:
            - The status of the Traffic Manager profile.
        default: Enabled
        type: str
        choices:
            - Enabled
            - Disabled
    traffic_routing_method:
        description:
            - The traffic routing method of the Traffic Manager profile.
        default: Performance
        type: str
        choices:
            - Performance
            - Priority
            - Weighted
            - Geographic
    dns_config:
        description:
            - The DNS settings of the Traffic Manager profile.
        type: dict
        suboptions:
            relative_name:
                description:
                    - The relative DNS name provided by this Traffic Manager profile.
                    - If no provided, name of the Traffic Manager will be used
                type: str
            ttl:
                description:
                    - The DNS Time-To-Live (TTL), in seconds.
                type: int
    monitor_config:
        description:
            - The endpoint monitoring settings of the Traffic Manager profile.
        type: dict
        suboptions:
            profile_monitor_status:
                description:
                    - The profile-level monitoring status of the Traffic Manager.
                type: str
            protocol:
                description:
                    - The protocol (HTTP, HTTPS or TCP) used to probe for endpoint health.
                type: str
                choices:
                    - HTTP
                    - HTTPS
                    - TCP
            port:
                description:
                    - The TCP port used to probe for endpoint health.
                type: int
            path:
                description:
                    - The path relative to the endpoint domain name used to probe for endpoint health.
                type: str
            interval_in_seconds:
                description:
                    - The monitor interval for endpoints in this profile.
                type: int
            timeout_in_seconds:
                description:
                    - The monitor timeout for endpoints in this profile.
                type: int
            tolerated_number_of_failures:
                description:
                    - The number of consecutive failed health check before declaring an endpoint in this profile Degraded after the next failed health check.
                type: int
        default:
            protocol: HTTP
            port: 80
            path: /
    endpoints:
        description:
            - The list of endpoints in the Traffic Manager profile.
        type: list
        elements: dict
        default: []
        suboptions:
            endpoint_monitor_status:
                description:
                    - The monitoring status of the endpoint.
                type: str
            id:
                description:
                    - Fully qualified resource Id for the resource.
                type: str
            name:
                description:
                    - The name of the endpoint.
                type: str
            type:
                description:
                    - The type of the endpoint. Ex- Microsoft.network/TrafficManagerProfiles/ExternalEndpoints.
                type: str
            target_resource_id:
                description:
                    - The Azure Resource URI of the of the endpoint.
                    - Not applicable to endpoints of type 'ExternalEndpoints'.
                type: str
            target:
                description:
                    - The fully-qualified DNS name of the endpoint.
                type: str
            endpoint_status:
                description:
                    - The status of the endpoint.
                type: str
                choices:
                    - Enabled
                    - Disabled
            weight:
                description:
                    - The weight of this endpoint when using the 'Weighted' traffic routing method.
                    - Possible values are from 1 to 1000.
                type: int
            priority:
                description:
                    - The priority of this endpoint when using the 'Priority' traffic routing method.
                    - Possible values are from 1 to 1000, lower values represent higher priority.
                    - This is an optional parameter. If specified, it must be specified on all endpoints.
                    - No two endpoints can share the same priority value.
                type: int
            endpoint_location:
                description:
                    - Specifies the location of the external or nested endpoints when using the 'Performance' traffic routing method.
                type: str
            min_child_endpoints:
                description:
                    - The minimum number of endpoints that must be available in the child profile in order for the parent profile to be considered available.
                    - Only applicable to endpoint of type 'NestedEndpoints'.
                type: int
            geo_mapping:
                description:
                    - The list of countries/regions mapped to this endpoint when using the 'Geographic' traffic routing method.
                type: list
                elements: str

extends_documentation_fragment:
    - azure.azcollection.azure
    - azure.azcollection.azure_tags

author:
    - Hai Cao (@caohai)

'''

EXAMPLES = '''
- name: Create a Traffic Manager Profile
  azure_rm_trafficmanager:
    name: tmtest
    resource_group: tmt
    location: global
    profile_status: Enabled
    traffic_routing_method: Priority
    dns_config:
      relative_name: tmtest
      ttl: 60
    monitor_config:
      protocol: HTTPS
      port: 80
      path: '/'
    endpoints:
      - name: e1
        type: Microsoft.network/TrafficManagerProfiles/ExternalEndpoints
        endpoint_location: West US 2
        endpoint_status: Enabled
        priority: 2
        target: 1.2.3.4
        weight: 1
    tags:
      Environment: Test

- name: Delete a Traffic Manager Profile
  azure_rm_trafficmanager:
    state: absent
    name: tmtest
    resource_group: tmt
'''
RETURN = '''
state:
    description: Current state of the Traffic Manager Profile
    returned: always
    type: dict
    example:
        "dns_config": {
            "fqdn": "tmtest.trafficmanager.net",
            "relative_name": "tmtest",
            "ttl": 60
        }
        "endpoints": [
            {
                "endpoint_location": "West US 2",
                "endpoint_monitor_status": "Degraded",
                "endpoint_status": "Enabled",
                "geo_mapping": null,
                "id":   "/subscriptions/XXXXXX...XXXXXXXXX/resourceGroups/tmt/providers/Microsoft.Network/trafficManagerProfiles/tmtest/externalEndpoints/e1",
                "min_child_endpoints": null,
                "name": "e1",
                "priority": 2,
                "target": "1.2.3.4",
                "target_resource_id": null,
                "type": "Microsoft.Network/trafficManagerProfiles/externalEndpoints",
                "weight": 1
            }
        ]
        "id": "/subscriptions/XXXXXX...XXXXXXXXX/resourceGroups/tmt/providers/Microsoft.Network/trafficManagerProfiles/tmtest"
        "location": "global"
        "monitor_config": {
            "interval_in_seconds": 30,
            "path": "/",
            "port": 80,
            "profile_monitor_status": "Degraded",
            "protocol": "HTTPS",
            "timeout_in_seconds": 10,
            "tolerated_number_of_failures": 3
        }
        "name": "tmtest"
        "profile_status": "Enabled"
        "tags": {
            "Environment": "Test"
        }
        "traffic_routing_method": "Priority"
        "type": "Microsoft.Network/trafficManagerProfiles"
'''
from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import AzureRMModuleBase

try:
    from azure.core.exceptions import ResourceNotFoundError
    from azure.mgmt.trafficmanager.models import (
        Profile, Endpoint, DnsConfig, MonitorConfig
    )
except ImportError:
    # This is handled in azure_rm_common
    pass


def traffic_manager_profile_to_dict(tmp):
    result = dict(
        id=tmp.id,
        name=tmp.name,
        type=tmp.type,
        tags=tmp.tags,
        location=tmp.location,
        profile_status=tmp.profile_status,
        traffic_routing_method=tmp.traffic_routing_method,
        dns_config=dict(),
        monitor_config=dict(),
        endpoints=[]
    )
    if tmp.dns_config:
        result['dns_config']['relative_name'] = tmp.dns_config.relative_name
        result['dns_config']['fqdn'] = tmp.dns_config.fqdn
        result['dns_config']['ttl'] = tmp.dns_config.ttl
    if tmp.monitor_config:
        result['monitor_config']['profile_monitor_status'] = tmp.monitor_config.profile_monitor_status
        result['monitor_config']['protocol'] = tmp.monitor_config.protocol
        result['monitor_config']['port'] = tmp.monitor_config.port
        result['monitor_config']['path'] = tmp.monitor_config.path
        result['monitor_config']['interval_in_seconds'] = tmp.monitor_config.interval_in_seconds
        result['monitor_config']['timeout_in_seconds'] = tmp.monitor_config.timeout_in_seconds
        result['monitor_config']['tolerated_number_of_failures'] = tmp.monitor_config.tolerated_number_of_failures
    if tmp.endpoints:
        for endpoint in tmp.endpoints:
            result['endpoints'].append(dict(
                id=endpoint.id,
                name=endpoint.name,
                type=endpoint.type,
                target_resource_id=endpoint.target_resource_id,
                target=endpoint.target,
                endpoint_status=endpoint.endpoint_status,
                weight=endpoint.weight,
                priority=endpoint.priority,
                endpoint_location=endpoint.endpoint_location,
                endpoint_monitor_status=endpoint.endpoint_monitor_status,
                min_child_endpoints=endpoint.min_child_endpoints,
                geo_mapping=endpoint.geo_mapping
            ))
    return result


def create_dns_config_instance(dns_config):
    return DnsConfig(
        relative_name=dns_config['relative_name'],
        ttl=dns_config['ttl']
    )


def create_monitor_config_instance(monitor_config):
    return MonitorConfig(
        profile_monitor_status=monitor_config['profile_monitor_status'],
        protocol=monitor_config['protocol'],
        port=monitor_config['port'],
        path=monitor_config['path'],
        interval_in_seconds=monitor_config['interval_in_seconds'],
        timeout_in_seconds=monitor_config['timeout_in_seconds'],
        tolerated_number_of_failures=monitor_config['tolerated_number_of_failures']
    )


def create_endpoint_instance(endpoint):
    return Endpoint(
        id=endpoint['id'],
        name=endpoint['name'],
        type=endpoint['type'],
        target_resource_id=endpoint['target_resource_id'],
        target=endpoint['target'],
        endpoint_status=endpoint['endpoint_status'],
        weight=endpoint['weight'],
        priority=endpoint['priority'],
        endpoint_location=endpoint['endpoint_location'],
        min_child_endpoints=endpoint['min_child_endpoints'],
        geo_mapping=endpoint['geo_mapping']
    )


def create_endpoints(endpoints):
    return [create_endpoint_instance(endpoint) for endpoint in endpoints]


dns_config_spec = dict(
    relative_name=dict(type='str'),
    ttl=dict(type='int')
)

monitor_config_spec = dict(
    profile_monitor_status=dict(type='str'),
    protocol=dict(type='str', choices=['HTTP', 'HTTPS', 'TCP']),
    port=dict(type='int'),
    path=dict(type='str'),
    interval_in_seconds=dict(type='int'),
    timeout_in_seconds=dict(type='int'),
    tolerated_number_of_failures=dict(type='int')
)

endpoint_spec = dict(
    id=dict(type='str'),
    name=dict(type='str'),
    type=dict(type='str'),
    target_resource_id=dict(type='str'),
    target=dict(type='str'),
    endpoint_status=dict(type='str', choices=['Enabled', 'Disabled']),
    weight=dict(type='int'),
    priority=dict(type='int'),
    endpoint_location=dict(type='str'),
    endpoint_monitor_status=dict(type='str'),
    min_child_endpoints=dict(type='int'),
    geo_mapping=dict(type='list', elements='str')
)


class AzureRMTrafficManager(AzureRMModuleBase):

    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'
            ),
            profile_status=dict(
                type='str',
                default='Enabled',
                choices=['Enabled', 'Disabled']
            ),
            traffic_routing_method=dict(
                type='str',
                default='Performance',
                choices=['Performance', 'Priority', 'Weighted', 'Geographic']
            ),
            dns_config=dict(
                type='dict',
                options=dns_config_spec
            ),
            monitor_config=dict(
                type='dict',
                default=dict(
                    protocol='HTTP',
                    port=80,
                    path='/'
                ),
                options=monitor_config_spec
            ),
            endpoints=dict(
                type='list',
                elements='dict',
                options=endpoint_spec,
                default=[]
            )
        )

        self.resource_group = None
        self.name = None
        self.state = None
        self.tags = None
        self.location = None
        self.profile_status = None
        self.traffic_routing_method = None
        self.dns_config = None
        self.monitor_config = None
        self.endpoints = None

        self.results = dict(
            changed=False
        )

        super(AzureRMTrafficManager, self).__init__(derived_arg_spec=self.module_arg_spec, supports_check_mode=True)

    def exec_module(self, **kwargs):

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

        to_be_updated = False

        if not self.dns_config:
            self.dns_config = dict(
                relative_name=self.name,
                ttl=60
            )

        if not self.location:
            self.location = 'global'

        response = self.get_traffic_manager_profile()

        if self.state == 'present':
            if not response:
                to_be_updated = True
            else:
                self.results = response
                self.log('Results : {0}'.format(response))
                update_tags, response['tags'] = self.update_tags(response['tags'])

                if update_tags:
                    to_be_updated = True

                to_be_updated = to_be_updated or self.check_update(response)

            if to_be_updated:
                self.log("Need to Create / Update the Traffic Manager profile")

                if not self.check_mode:
                    self.results = self.ceate_update_traffic_manager_profile()
                    self.log("Creation / Update done.")

                self.results['changed'] = True
                return self.results

        elif self.state == 'absent' and response:
            self.log("Need to delete the Traffic Manager profile")
            self.results['changed'] = True

            if self.check_mode:
                return self.results

            self.delete_traffic_manager_profile()

            self.log("Traffic Manager profile deleted")

        return self.results

    def get_traffic_manager_profile(self):
        '''
        Gets the properties of the specified Traffic Manager profile

        :return: deserialized Traffic Manager profile dict
        '''
        self.log("Checking if Traffic Manager profile {0} is present".format(self.name))
        try:
            response = self.traffic_manager_management_client.profiles.get(self.resource_group, self.name)
            self.log("Response : {0}".format(response))
            self.log("Traffic Manager profile : {0} found".format(response.name))
            return traffic_manager_profile_to_dict(response)
        except ResourceNotFoundError:
            self.log('Did not find the Traffic Manager profile.')
            return False

    def delete_traffic_manager_profile(self):
        '''
        Deletes the specified Traffic Manager profile in the specified subscription and resource group.
        :return: True
        '''

        self.log("Deleting the Traffic Manager profile {0}".format(self.name))
        try:
            operation_result = self.traffic_manager_management_client.profiles.delete(self.resource_group, self.name)
            return True
        except Exception as e:
            self.log('Error attempting to delete the Traffic Manager profile.')
            self.fail("Error deleting the Traffic Manager profile: {0}".format(e.message))
            return False

    def ceate_update_traffic_manager_profile(self):
        '''
        Creates or updates a Traffic Manager profile.

        :return: deserialized Traffic Manager profile state dictionary
        '''
        self.log("Creating / Updating the Traffic Manager profile {0}".format(self.name))

        parameters = Profile(
            tags=self.tags,
            location=self.location,
            profile_status=self.profile_status,
            traffic_routing_method=self.traffic_routing_method,
            dns_config=create_dns_config_instance(self.dns_config) if self.dns_config else None,
            monitor_config=create_monitor_config_instance(self.monitor_config) if self.monitor_config else None,
            endpoints=create_endpoints(self.endpoints)
        )
        try:
            response = self.traffic_manager_management_client.profiles.create_or_update(self.resource_group, self.name, parameters)
            return traffic_manager_profile_to_dict(response)
        except Exception as exc:
            self.log('Error attempting to create the Traffic Manager.')
            self.fail("Error creating the Traffic Manager: {0}".format(exc.message))

    def check_update(self, response):
        if response['location'] != self.location:
            self.log("Location Diff - Origin {0} / Update {1}".format(response['location'], self.location))
            return True

        if response['profile_status'] != self.profile_status:
            self.log("Profile Status Diff - Origin {0} / Update {1}".format(response['profile_status'], self.profile_status))
            return True

        if response['traffic_routing_method'] != self.traffic_routing_method:
            self.log("Traffic Routing Method Diff - Origin {0} / Update {1}".format(response['traffic_routing_method'], self.traffic_routing_method))
            return True

        if (response['dns_config']['relative_name'] != self.dns_config['relative_name'] or response['dns_config']['ttl'] != self.dns_config['ttl']):
            self.log("DNS Config Diff - Origin {0} / Update {1}".format(response['dns_config'], self.dns_config))
            return True

        for k, v in self.monitor_config.items():
            if v:
                if str(v).lower() != str(response['monitor_config'][k]).lower():
                    self.log("Monitor Config Diff - Origin {0} / Update {1}".format(response['monitor_config'], self.monitor_config))
                    return True

        if len(response['endpoints']) != len(self.endpoints):
            self.log("Endpoints Diff - Origin {0} / Update {1}".format(response['endpoints'], self.endpoints))
            return True
        else:
            for e1, e2 in zip(sorted(self.endpoints, key=lambda k: k['name']), sorted(response['endpoints'], key=lambda k: k['name'])):
                for k, v in e1.items():
                    if v:
                        if str(v).lower() != str(e2[k]).lower():
                            self.log("Endpoints Diff - Origin {0} / Update {1}".format(response['endpoints'], self.endpoints))
                            return True
        return False


def main():
    """Main execution"""
    AzureRMTrafficManager()


if __name__ == '__main__':
    main()
