#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2020 Citrix Systems, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

from __future__ import absolute_import, division, print_function

__metaclass__ = type

ANSIBLE_METADATA = {'metadata_version': '1.1',
                    'status': ['preview'],
                    'supported_by': 'community'}

DOCUMENTATION = '''
---
module: citrix_adc_cs_policy
short_description: Manage content switching policy
description:
    - Manage content switching policy.
    - "This module is intended to run either on the ansible  control node or a bastion (jumpserver) with access to the actual Citrix ADC instance."

version_added: "1.0.0"

author: George Nikolopoulos (@giorgos-nikolopoulos)

options:

    policyname:
        type: str
        description:
            - >-
                Name for the content switching policy. Must begin with an ASCII alphanumeric or underscore C(_)
                character, and must contain only ASCII alphanumeric, underscore, hash C(#), period C(.), space C( ), colon
                C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. Cannot be changed after a policy is
                created.
            - "The following requirement applies only to the Citrix ADC CLI:"
            - >-
                If the name includes one or more spaces, enclose the name in double or single quotation marks (for
                example, my policy or my policy).
            - "Minimum length = 1"

    url:
        type: str
        description:
            - >-
                URL string that is matched with the URL of a request. Can contain a wildcard character. Specify the
                string value in the following format: C([[prefix] [*]] [.suffix]).
            - "Minimum length = 1"
            - "Maximum length = 208"

    rule:
        type: str
        description:
            - >-
                Expression, or name of a named expression, against which traffic is evaluated. Written in the classic
                or default syntax.
            - "Note:"
            - >-
                Maximum length of a string literal in the expression is 255 characters. A longer string can be split
                into smaller strings of up to 255 characters each, and the smaller strings concatenated with the +
                operator. For example, you can create a 500-character string as follows: '"<string of 255
                characters>" + "<string of 245 characters>"'

    domain:
        description:
            - "The domain name. The string value can range to 63 characters."
            - "Minimum length = 1"
        type: str

    action:
        type: str
        description:
            - >-
                Content switching action that names the target load balancing virtual server to which the traffic is
                switched.

extends_documentation_fragment: citrix.adc.citrixadc
requirements:
    - nitro python sdk
'''

EXAMPLES = '''
- name: Create url cs policy
  delegate_to: localhost
  citrix_adc_cs_policy:
    nsip: 172.18.0.2
    nitro_user: nsroot
    nitro_pass: nsroot
    validate_certs: no

    state: present

    policyname: policy_1
    url: /example/
'''

RETURN = '''
loglines:
    description: list of logged messages by the module
    returned: always
    type: list
    sample: ['message 1', 'message 2']

msg:
    description: Message detailing the failure reason
    returned: failure
    type: str
    sample: "Could not load nitro python sdk"

diff:
    description: List of differences between the actual configured object and the configuration specified in the module
    returned: failure
    type: dict
    sample: { 'url': 'difference. ours: (str) example1 other: (str) /example1' }
'''

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.citrix.adc.plugins.module_utils.citrix_adc import (
    ConfigProxy,
    get_nitro_client,
    netscaler_common_arguments,
    log,
    loglines,
    ensure_feature_is_enabled
)

try:
    from nssrc.com.citrix.netscaler.nitro.resource.config.cs.cspolicy import cspolicy
    from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception
    PYTHON_SDK_IMPORTED = True
except ImportError as e:
    PYTHON_SDK_IMPORTED = False


def policy_exists(client, module):
    log('Checking if policy exists')
    try:
        cspolicy.get(client, module.params['policyname'])
        return True
    except Exception:
        return False


def policy_identical(client, module, cspolicy_proxy):
    log('Checking if defined policy is identical to configured')
    try:
        policy_list = cspolicy.get(client, module.params['policyname'])
    except Exception:
        return False

    diff_dict = cspolicy_proxy.diff_object(policy_list)
    if 'ip' in diff_dict:
        del diff_dict['ip']
    if len(diff_dict) == 0:
        return True
    else:
        return False


def diff_list(client, module, cspolicy_proxy):
    policy_list = cspolicy.get(client, module.params['policyname'])
    return cspolicy_proxy.diff_object(policy_list)


def main():

    module_specific_arguments = dict(
        policyname=dict(type='str'),
        url=dict(type='str'),
        rule=dict(type='str'),
        domain=dict(type='str'),
        action=dict(type='str'),
        mas_proxy_call=dict(
            default=False,
            type='bool'
        ),
    )

    hand_inserted_arguments = dict(
    )

    argument_spec = dict()

    argument_spec.update(netscaler_common_arguments)
    argument_spec.update(module_specific_arguments)
    argument_spec.update(hand_inserted_arguments)

    module = AnsibleModule(
        argument_spec=argument_spec,
        supports_check_mode=True,
    )
    module_result = dict(
        changed=False,
        failed=False,
        loglines=loglines,
    )

    # Fail the module if imports failed
    if not PYTHON_SDK_IMPORTED:
        module.fail_json(msg='Could not load nitro python sdk')

    # Fallthrough to rest of execution
    client = get_nitro_client(module)

    if not module.params['mas_proxy_call']:
        try:
            client.login()
        except nitro_exception as e:
            msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message)
            module.fail_json(msg=msg)
        except Exception as e:
            if str(type(e)) == "<class 'requests.exceptions.ConnectionError'>":
                module.fail_json(msg='Connection error %s' % str(e))
            elif str(type(e)) == "<class 'requests.exceptions.SSLError'>":
                module.fail_json(msg='SSL Error %s' % str(e))
            else:
                module.fail_json(msg='Unexpected error during login %s' % str(e))

    readwrite_attrs = [
        'policyname',
        'url',
        'rule',
        'domain',
        'action',
    ]
    readonly_attrs = [
        'vstype',
        'hits',
        'bindhits',
        'labelname',
        'labeltype',
        'priority',
        'activepolicy',
        'cspolicytype',
    ]

    transforms = {
    }

    # Instantiate config proxy
    cspolicy_proxy = ConfigProxy(
        actual=cspolicy(),
        client=client,
        attribute_values_dict=module.params,
        readwrite_attrs=readwrite_attrs,
        readonly_attrs=readonly_attrs,
        transforms=transforms,
    )

    try:
        ensure_feature_is_enabled(client, 'CS')

        # Apply appropriate state
        if module.params['state'] == 'present':
            log('Sanity checks for state present')
            if not policy_exists(client, module):
                if not module.check_mode:
                    cspolicy_proxy.add()
                    if module.params['save_config']:
                        client.save_config()
                module_result['changed'] = True
            elif not policy_identical(client, module, cspolicy_proxy):
                if not module.check_mode:
                    cspolicy_proxy.update()
                    if module.params['save_config']:
                        client.save_config()
                module_result['changed'] = True
            else:
                module_result['changed'] = False

            # Sanity check for state
            if not module.check_mode:
                log('Sanity checks for state present')
                if not policy_exists(client, module):
                    module.fail_json(msg='Policy does not exist', **module_result)
                if not policy_identical(client, module, cspolicy_proxy):
                    module.fail_json(msg='Policy differs from configured',
                                     diff=diff_list(client, module, cspolicy_proxy), **module_result)

        elif module.params['state'] == 'absent':
            log('Applying actions for state absent')
            if policy_exists(client, module):
                if not module.check_mode:
                    cspolicy_proxy.delete()
                    if module.params['save_config']:
                        client.save_config()
                module_result['changed'] = True
            else:
                module_result['changed'] = False

            # Sanity check for state
            if not module.check_mode:
                log('Sanity checks for state absent')
                if policy_exists(client, module):
                    module.fail_json(msg='Policy still exists', **module_result)

    except nitro_exception as e:
        msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message)
        module.fail_json(msg=msg, **module_result)

    if not module.params['mas_proxy_call']:
        client.logout()

    module.exit_json(**module_result)


if __name__ == "__main__":
    main()
