#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2016 F5 Networks Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.

ANSIBLE_METADATA = {'status': ['preview'],
                    'supported_by': 'community',
                    'version': '1.0'}

DOCUMENTATION = '''
---
module: bigip_gtm_facts
short_description: Collect facts from F5 BIG-IP GTM devices.
description:
  - Collect facts from F5 BIG-IP GTM devices.
version_added: "2.3"
options:
  include:
    description:
      - Fact category to collect
    required: true
    choices:
      - pool
      - wide_ip
      - virtual_server
  filter:
    description:
      - Perform regex filter of response. Filtering is done on the name of
        the resource. Valid filters are anything that can be provided to
        Python's C(re) module.
    required: false
    default: None
notes:
   - Requires the f5-sdk Python package on the host. This is as easy as
     pip install f5-sdk
extends_documentation_fragment: f5
requirements:
  - f5-sdk
author:
  - Tim Rupp (@caphrim007)
'''

EXAMPLES = '''
- name: Get pool facts
  bigip_gtm_facts:
      server: "lb.mydomain.com"
      user: "admin"
      password: "secret"
      include: "pool"
      filter: "my_pool"
  delegate_to: localhost
'''

RETURN = '''
wide_ip:
    description:
        Contains the lb method for the wide ip and the pools
        that are within the wide ip.
    returned: changed
    type: dict
    sample:
        wide_ip:
            - enabled: "True"
              failure_rcode: "noerror"
              failure_rcode_response: "disabled"
              failure_rcode_ttl: "0"
              full_path: "/Common/foo.ok.com"
              last_resort_pool: ""
              minimal_response: "enabled"
              name: "foo.ok.com"
              partition: "Common"
              persist_cidr_ipv4: "32"
              persist_cidr_ipv6: "128"
              persistence: "disabled"
              pool_lb_mode: "round-robin"
              pools:
                  - name: "d3qw"
                    order: "0"
                    partition: "Common"
                    ratio: "1"
              ttl_persistence: "3600"
              type: "naptr"
pool:
    description: Contains the pool object status and enabled status.
    returned: changed
    type: dict
    sample:
        pool:
            - alternate_mode: "round-robin"
              dynamic_ratio: "disabled"
              enabled: "True"
              fallback_mode: "return-to-dns"
              full_path: "/Common/d3qw"
              load_balancing_mode: "round-robin"
              manual_resume: "disabled"
              max_answers_returned: "1"
              members:
                  - disabled: "True"
                    flags: "a"
                    full_path: "ok3.com"
                    member_order: "0"
                    name: "ok3.com"
                    order: "10"
                    preference: "10"
                    ratio: "1"
                    service: "80"
              name: "d3qw"
              partition: "Common"
              qos_hit_ratio: "5"
              qos_hops: "0"
              qos_kilobytes_second: "3"
              qos_lcs: "30"
              qos_packet_rate: "1"
              qos_rtt: "50"
              qos_topology: "0"
              qos_vs_capacity: "0"
              qos_vs_score: "0"
              ttl: "30"
              type: "naptr"
              verify_member_availability: "disabled"
virtual_server:
    description:
        Contains the virtual server enabled and availability
        status, and address
    returned: changed
    type: dict
    sample:
        virtual_server:
            - addresses:
                  - device_name: "/Common/qweqwe"
                    name: "10.10.10.10"
                    translation: "none"
              datacenter: "/Common/xfxgh"
              enabled: "True"
              expose_route_domains: "no"
              full_path: "/Common/qweqwe"
              iq_allow_path: "yes"
              iq_allow_service_check: "yes"
              iq_allow_snmp: "yes"
              limit_cpu_usage: "0"
              limit_cpu_usage_status: "disabled"
              limit_max_bps: "0"
              limit_max_bps_status: "disabled"
              limit_max_connections: "0"
              limit_max_connections_status: "disabled"
              limit_max_pps: "0"
              limit_max_pps_status: "disabled"
              limit_mem_avail: "0"
              limit_mem_avail_status: "disabled"
              link_discovery: "disabled"
              monitor: "/Common/bigip "
              name: "qweqwe"
              partition: "Common"
              product: "single-bigip"
              virtual_server_discovery: "disabled"
              virtual_servers:
                  - destination: "10.10.10.10:0"
                    enabled: "True"
                    full_path: "jsdfhsd"
                    limit_max_bps: "0"
                    limit_max_bps_status: "disabled"
                    limit_max_connections: "0"
                    limit_max_connections_status: "disabled"
                    limit_max_pps: "0"
                    limit_max_pps_status: "disabled"
                    name: "jsdfhsd"
                    translation_address: "none"
                    translation_port: "0"
'''

try:
    from distutils.version import LooseVersion
    from f5.bigip.contexts import TransactionContextManager
    from f5.bigip import ManagementRoot
    from icontrol.session import iControlUnexpectedHTTPError

    HAS_F5SDK = True
except ImportError:
    HAS_F5SDK = False

import re


class BigIpGtmFactsCommon(object):
    def __init__(self):
        self.api = None
        self.attributes_to_remove = [
            'kind', 'generation', 'selfLink', '_meta_data',
            'membersReference', 'datacenterReference',
            'virtualServersReference', 'nameReference'
        ]
        self.gtm_types = dict(
            a_s='a',
            aaaas='aaaa',
            cnames='cname',
            mxs='mx',
            naptrs='naptr',
            srvs='srv'
        )
        self.request_params = dict(
            params='expandSubcollections=true'
        )

    def is_version_less_than_12(self):
        version = self.api.tmos_version
        if LooseVersion(version) < LooseVersion('12.0.0'):
            return True
        else:
            return False

    def format_string_facts(self, parameters):
        result = dict()
        for attribute in self.attributes_to_remove:
            parameters.pop(attribute, None)
        for key, val in parameters.items():
            result[key] = str(val)
        return result

    def filter_matches_name(self, name):
        if not self.params['filter']:
            return True
        matches = re.match(self.params['filter'], str(name))
        if matches:
            return True
        else:
            return False

    def get_facts_from_collection(self, collection, collection_type=None):
        results = []
        for item in collection:
            if not self.filter_matches_name(item.name):
                continue
            facts = self.format_facts(item, collection_type)
            results.append(facts)
        return results

    def connect_to_bigip(self, **kwargs):
        return ManagementRoot(kwargs['server'],
                              kwargs['user'],
                              kwargs['password'],
                              port=kwargs['server_port'])


class BigIpGtmFactsPools(BigIpGtmFactsCommon):
    def __init__(self, *args, **kwargs):
        super(BigIpGtmFactsPools, self).__init__()
        self.params = kwargs

    def get_facts(self):
        self.api = self.connect_to_bigip(**self.params)
        return self.get_facts_from_device()

    def get_facts_from_device(self):
        try:
            if self.is_version_less_than_12():
                return self.get_facts_without_types()
            else:
                return self.get_facts_with_types()
        except iControlUnexpectedHTTPError as e:
            raise F5ModuleError(str(e))

    def get_facts_with_types(self):
        result = []
        for key, type in self.gtm_types.items():
            facts = self.get_all_facts_by_type(key, type)
            if facts:
                result.append(facts)
        return result

    def get_facts_without_types(self):
        pools = self.api.tm.gtm.pools.get_collection(**self.request_params)
        return self.get_facts_from_collection(pools)

    def get_all_facts_by_type(self, key, type):
        collection = getattr(self.api.tm.gtm.pools, key)
        pools = collection.get_collection(**self.request_params)
        return self.get_facts_from_collection(pools, type)

    def format_facts(self, pool, collection_type):
        result = dict()
        pool_dict = pool.to_dict()
        result.update(self.format_string_facts(pool_dict))
        result.update(self.format_member_facts(pool))
        if collection_type:
            result['type'] = collection_type
        return camel_dict_to_snake_dict(result)

    def format_member_facts(self, pool):
        result = []
        if not 'items' in pool.membersReference:
            return dict(members=[])
        for member in pool.membersReference['items']:
            member_facts = self.format_string_facts(member)
            result.append(member_facts)
        return dict(members=result)


class BigIpGtmFactsWideIps(BigIpGtmFactsCommon):
    def __init__(self, *args, **kwargs):
        super(BigIpGtmFactsWideIps, self).__init__()
        self.params = kwargs

    def get_facts(self):
        self.api = self.connect_to_bigip(**self.params)
        return self.get_facts_from_device()

    def get_facts_from_device(self):
        try:
            if self.is_version_less_than_12():
                return self.get_facts_without_types()
            else:
                return self.get_facts_with_types()
        except iControlUnexpectedHTTPError as e:
            raise F5ModuleError(str(e))

    def get_facts_with_types(self):
        result = []
        for key, type in self.gtm_types.items():
            facts = self.get_all_facts_by_type(key, type)
            if facts:
                result.append(facts)
        return result

    def get_facts_without_types(self):
        wideips = self.api.tm.gtm.wideips.get_collection(
            **self.request_params
        )
        return self.get_facts_from_collection(wideips)

    def get_all_facts_by_type(self, key, type):
        collection = getattr(self.api.tm.gtm.wideips, key)
        wideips = collection.get_collection(**self.request_params)
        return self.get_facts_from_collection(wideips, type)

    def format_facts(self, wideip, collection_type):
        result = dict()
        wideip_dict = wideip.to_dict()
        result.update(self.format_string_facts(wideip_dict))
        result.update(self.format_pool_facts(wideip))
        if collection_type:
            result['type'] = collection_type
        return camel_dict_to_snake_dict(result)

    def format_pool_facts(self, wideip):
        result = []
        if not hasattr(wideip, 'pools'):
            return dict(pools=[])
        for pool in wideip.pools:
            pool_facts = self.format_string_facts(pool)
            result.append(pool_facts)
        return dict(pools=result)


class BigIpGtmFactsVirtualServers(BigIpGtmFactsCommon):
    def __init__(self, *args, **kwargs):
        super(BigIpGtmFactsVirtualServers, self).__init__()
        self.params = kwargs

    def get_facts(self):
        try:
            self.api = self.connect_to_bigip(**self.params)
            return self.get_facts_from_device()
        except iControlUnexpectedHTTPError as e:
            raise F5ModuleError(str(e))

    def get_facts_from_device(self):
        servers = self.api.tm.gtm.servers.get_collection(
            **self.request_params
        )
        return self.get_facts_from_collection(servers)

    def format_facts(self, server, collection_type=None):
        result = dict()
        server_dict = server.to_dict()
        result.update(self.format_string_facts(server_dict))
        result.update(self.format_address_facts(server))
        result.update(self.format_virtual_server_facts(server))
        return camel_dict_to_snake_dict(result)

    def format_address_facts(self, server):
        result = []
        if not hasattr(server, 'addresses'):
            return dict(addresses=[])
        for address in server.addresses:
            address_facts = self.format_string_facts(address)
            result.append(address_facts)
        return dict(addresses=result)

    def format_virtual_server_facts(self, server):
        result = []
        if not 'items' in server.virtualServersReference:
            return dict(virtual_servers=[])
        for server in server.virtualServersReference['items']:
            server_facts = self.format_string_facts(server)
            result.append(server_facts)
        return dict(virtual_servers=result)

class BigIpGtmFactsManager(object):
    def __init__(self, *args, **kwargs):
        self.params = kwargs
        self.api = None

    def get_facts(self):
        result = dict()
        facts = dict()

        if 'pool' in self.params['include']:
            facts['pool'] = self.get_pool_facts()
        if 'wide_ip' in self.params['include']:
            facts['wide_ip'] = self.get_wide_ip_facts()
        if 'virtual_server' in self.params['include']:
            facts['virtual_server'] = self.get_virtual_server_facts()

        result.update(**facts)
        result.update(dict(changed=True))
        return result

    def get_pool_facts(self):
        pools = BigIpGtmFactsPools(**self.params)
        return pools.get_facts()

    def get_wide_ip_facts(self):
        wide_ips = BigIpGtmFactsWideIps(**self.params)
        return wide_ips.get_facts()

    def get_virtual_server_facts(self):
        wide_ips = BigIpGtmFactsVirtualServers(**self.params)
        return wide_ips.get_facts()


class BigIpGtmFactsModuleConfig(object):
    def __init__(self):
        self.argument_spec = dict()
        self.meta_args = dict()
        self.supports_check_mode = False
        self.valid_includes = ['pool', 'wide_ip', 'virtual_server']
        self.initialize_meta_args()
        self.initialize_argument_spec()

    def initialize_meta_args(self):
        args = dict(
            include=dict(type='list', required=True),
            filter=dict(type='str', required=False)
        )
        self.meta_args = args

    def initialize_argument_spec(self):
        self.argument_spec = f5_argument_spec()
        self.argument_spec.update(self.meta_args)

    def create(self):
        return AnsibleModule(
            argument_spec=self.argument_spec,
            supports_check_mode=self.supports_check_mode
        )


def main():
    if not HAS_F5SDK:
        raise F5ModuleError("The python f5-sdk module is required")

    config = BigIpGtmFactsModuleConfig()
    module = config.create()

    try:
        obj = BigIpGtmFactsManager(
            check_mode=module.check_mode, **module.params
        )
        result = obj.get_facts()

        module.exit_json(**result)
    except F5ModuleError as e:
        module.fail_json(msg=str(e))

from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import camel_dict_to_snake_dict
from ansible.module_utils.f5 import *

if __name__ == '__main__':
    main()
