#!/usr/bin/python
# Copyright: Ansible Project
# 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: icx_logging
author: "Ruckus Wireless (@Commscope)"
short_description:  Manage logging on Ruckus ICX 7000 series switches
description:
  - This module provides declarative management of logging
    on Ruckus ICX 7000 series switches.
notes:
  - Tested against ICX 10.1.
  - For information on using ICX platform, see L(the ICX OS Platform Options guide,../network/user_guide/platform_icx.html).
options:
  dest:
    description:
      - Destination of the logs.
    choices: ['on', 'host', 'console', 'buffered', 'persistence', 'rfc5424']
    type: str
  name:
    description:
      - ipv4 address/ipv6 address/name of  syslog server.
    type: str
  udp_port:
    description:
      - UDP port of destination host(syslog server).
    type: str
  facility:
    description:
      - Specifies log facility to log messages from the device.
    choices: ['auth','cron','daemon','kern','local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', 'user',
              'lpr','mail','news','syslog','sys9','sys10','sys11','sys12','sys13','sys14','user','uucp']
    type: str
  level:
    description:
      - Specifies the message level.
    type: list
    choices: ['alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational',
                'notifications', 'warnings']
  aggregate:
    description:
      - List of logging definitions.
    type: list
    suboptions:
      dest:
        description:
          - Destination of the logs.
        choices: ['on', 'host', 'console', 'buffered', 'persistence', 'rfc5424']
        type: str
      name:
        description:
          - ipv4 address/ipv6 address/name of  syslog server.
        type: str
      udp_port:
        description:
          - UDP port of destination host(syslog server).
        type: str
      facility:
        description:
          - Specifies log facility to log messages from the device.
        choices: ['auth','cron','daemon','kern','local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7', 'user',
                  'lpr','mail','news','syslog','sys9','sys10','sys11','sys12','sys13','sys14','user','uucp']
        type: str
      level:
        description:
          - Specifies the message level.
        type: list
        choices: ['alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational',
                    'notifications', 'warnings']
      state:
        description:
          - State of the logging configuration.
        choices: ['present', 'absent']
        type: str
      check_running_config:
        description:
          - Check running configuration. This can be set as environment variable.
           Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter.
        type: bool
  state:
    description:
      - State of the logging configuration.
    default: present
    choices: ['present', 'absent']
    type: str
  check_running_config:
    description:
      - Check running configuration. This can be set as environment variable.
       Module will use environment variable value(default:True), unless it is overridden, by specifying it as module parameter.
    type: bool
    default: true
'''

EXAMPLES = """
- name: Configure host logging.
  community.network.icx_logging:
    dest: host
    name: 172.16.0.1
    udp_port: 5555
- name: Remove host logging configuration.
  community.network.icx_logging:
    dest: host
    name: 172.16.0.1
    udp_port: 5555
    state: absent
- name: Disables the real-time display of syslog messages.
  community.network.icx_logging:
    dest: console
    state: absent
- name: Enables local syslog logging.
  community.network.icx_logging:
    dest : on
    state: present
- name: Configure buffer level
  community.network.icx_logging:
    dest: buffered
    level: critical
- name: Configure logging using aggregate
  community.network.icx_logging:
    aggregate:
      - { dest: buffered, level: ['notifications','errors'] }
- name: Remove logging using aggregate
  community.network.icx_logging:
    aggregate:
      - { dest: console }
      - { dest: host, name: 172.16.0.1, udp_port: 5555 }
    state: absent
"""

RETURN = """
commands:
  description: The list of configuration mode commands to send to the device
  returned: always
  type: list
  sample:
    - logging host 172.16.0.1
    - logging console
"""

import re
from copy import deepcopy
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.connection import Connection, ConnectionError, exec_command
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import remove_default_spec, validate_ip_v6_address
from ansible_collections.community.network.plugins.module_utils.network.icx.icx import get_config, load_config


def search_obj_in_list(name, lst):
    for o in lst:
        if o['name'] == name:
            return o


def diff_in_list(want, have):
    adds = set()
    removes = set()
    for w in want:
        if w['dest'] == 'buffered':
            for h in have:
                if h['dest'] == 'buffered':
                    adds = w['level'] - h['level']
                    removes = h['level'] - w['level']
                    return adds, removes
    return adds, removes


def map_obj_to_commands(updates):
    dest_group = ('host', 'console', 'persistence', 'enable')
    commands = list()
    want, have = updates

    for w in want:
        dest = w['dest']
        name = w['name']
        level = w['level']
        state = w['state']
        udp_port = w['udp_port']
        facility = w['facility']
        del w['state']
        del w['facility']

        facility_name = ''
        facility_level = ''
        if name is not None and validate_ip_v6_address(name):
            name = 'ipv6 ' + name

        if facility:
            for item in have:
                if item['dest'] == 'facility':
                    facility_name = item['dest']
                    facility_level = item['facility']

        if state == 'absent':
            if have == []:
                if facility:
                    commands.append('no logging facility')

                if dest == 'buffered':
                    for item in have:
                        if item['dest'] == 'buffered':
                            want_level = level
                            have_level = item['level']
                            for item in want_level:
                                commands.append('no logging buffered {0}'.format(item))

                if dest == 'host':
                    if name and udp_port:
                        commands.append('no logging host {0} udp-port {1}'.format(name, udp_port))
                    elif name:
                        commands.append('no logging host {0}'.format(name))
                else:
                    if dest == 'rfc5424':
                        commands.append('no logging enable {0}'.format(dest))
                    else:
                        if dest != 'buffered':
                            commands.append('no logging {0}'.format(dest))

            if facility:
                if facility_name == 'facility' and facility_level != 'user':
                    commands.append('no logging facility')

            if dest == 'buffered':
                for item in have:
                    if item['dest'] == 'buffered':
                        want_level = level
                        have_level = item['level']
                        for item in want_level:
                            if item in have_level:
                                commands.append('no logging buffered {0}'.format(item))

            if w in have:
                if dest == 'host':
                    if name and udp_port:
                        commands.append('no logging host {0} udp-port {1}'.format(name, udp_port))
                    elif name:
                        commands.append('no logging host {0}'.format(name))
                else:
                    if dest == 'rfc5424':
                        commands.append('no logging enable {0}'.format(dest))
                    else:
                        if dest != 'buffered':
                            commands.append('no logging {0}'.format(dest))

        if state == 'present':
            if facility:
                if facility != facility_level:
                    commands.append('logging facility {0}'.format(facility))
            if w not in have:
                if dest == 'host':
                    if name and udp_port:
                        commands.append('logging host {0} udp-port {1}'.format(name, udp_port))
                    elif name:
                        commands.append('logging host {0}'.format(name))
                elif dest == 'buffered':
                    adds, removes = diff_in_list(want, have)
                    for item in adds:
                        commands.append('logging buffered {0}'.format(item))
                    for item in removes:
                        commands.append('no logging buffered {0}'.format(item))
                elif dest == 'rfc5424':
                    commands.append('logging enable {0}'.format(dest))
                else:
                    commands.append('logging {0}'.format(dest))

    return commands


def parse_port(line, dest):
    port = None
    if dest == 'host':
        match = re.search(r'logging host \S+\s+udp-port\s+(\d+)', line, re.M)
        if match:
            port = match.group(1)
        else:
            match_port = re.search(r'logging host ipv6 \S+\s+udp-port\s+(\d+)', line, re.M)
            if match_port:
                port = match_port.group(1)
    return port


def parse_name(line, dest):
    name = None
    if dest == 'host':
        match = re.search(r'logging host (\S+)', line, re.M)
        if match:
            if match.group(1) == 'ipv6':
                ipv6_add = re.search(r'logging host ipv6 (\S+)', line, re.M)
                name = ipv6_add.group(1)
            else:
                name = match.group(1)

    return name


def parse_address(line, dest):
    if dest == 'host':
        match = re.search(r'^logging host ipv6 (\S+)', line.strip(), re.M)
        if match:
            return True
    return False


def map_config_to_obj(module):
    obj = []
    facility = ''
    addr6 = False
    dest_group = ('host', 'console', 'buffered', 'persistence', 'enable')
    dest_level = ('alerts', 'critical', 'debugging', 'emergencies', 'errors', 'informational', 'notifications', 'warnings')
    buff_level = list()
    if module.params['check_running_config'] is False:
        return []
    data = get_config(module, flags=['| include logging'])
    facility_match = re.search(r'^logging facility (\S+)', data, re.M)
    if facility_match:
        facility = facility_match.group(1)
        obj.append({
            'dest': 'facility',
            'facility': facility
        })
    else:
        obj.append({
            'dest': 'facility',
            'facility': 'user'
        })
    for line in data.split('\n'):
        match = re.search(r'^logging (\S+)', line.strip(), re.M)
        if match:

            if match.group(1) in dest_group:
                dest = match.group(1)
                if dest == 'host':
                    obj.append({
                        'dest': dest,
                        'name': parse_name(line.strip(), dest),
                        'udp_port': parse_port(line, dest),
                        'level': None,
                        'addr6': parse_address(line, dest)

                    })
                elif dest == 'buffered':
                    obj.append({
                        'dest': dest,
                        'level': None,
                        'name': None,
                        'udp_port': None,
                        'addr6': False
                    })
                else:
                    if dest == 'enable':
                        dest = 'rfc5424'
                    obj.append({
                        'dest': dest,
                        'level': None,
                        'name': None,
                        'udp_port': None,
                        'addr6': False
                    })
        else:

            ip_match = re.search(r'^no logging buffered (\S+)', line, re.M)
            if ip_match:
                dest = 'buffered'
                buff_level.append(ip_match.group(1))
    if 'no logging on' not in data:
        obj.append({
            'dest': 'on',
            'level': None,
            'name': None,
            'udp_port': None,
            'addr6': False

        })
    levels = set()
    for items in dest_level:
        if items not in buff_level:
            levels.add(items)
    obj.append({
        'dest': 'buffered',
        'level': levels,
        'name': None,
        'udp_port': None,
        'addr6': False

    })
    return obj


def count_terms(check, param=None):
    count = 0
    for term in check:
        if param[term] is not None:
            count += 1
    return count


def check_required_if(module, spec, param):
    for sp in spec:
        missing = []
        max_missing_count = 0
        is_one_of = False
        if len(sp) == 4:
            key, val, requirements, is_one_of = sp
        else:
            key, val, requirements = sp

        if is_one_of:
            max_missing_count = len(requirements)
            term = 'any'
        else:
            term = 'all'

        if key in param and param[key] == val:
            for check in requirements:
                count = count_terms((check,), param)
                if count == 0:
                    missing.append(check)
        if len(missing) and len(missing) >= max_missing_count:
            msg = "%s is %s but %s of the following are missing: %s" % (key, val, term, ', '.join(missing))
            module.fail_json(msg=msg)


def map_params_to_obj(module, required_if=None):
    obj = []
    addr6 = False
    aggregate = module.params.get('aggregate')

    if aggregate:
        for item in aggregate:
            if item['name'] is not None and validate_ip_v6_address(item['name']):
                addr6 = True
            for key in item:
                if item.get(key) is None:
                    item[key] = module.params[key]

            check_required_if(module, required_if, item)
            item.update({'addr6': addr6})

            d = item.copy()
            d['level'] = set(d['level']) if d['level'] is not None else None
            if d['dest'] != 'host':
                d['name'] = None
                d['udp_port'] = None

            if d['dest'] != 'buffered':
                d['level'] = None
            del d['check_running_config']
            obj.append(d)

    else:
        if module.params['name'] is not None and validate_ip_v6_address(module.params['name']):
            addr6 = True
        if module.params['dest'] != 'host':
            module.params['name'] = None
            module.params['udp_port'] = None

        if module.params['dest'] != 'buffered':
            module.params['level'] = None

        obj.append({
            'dest': module.params['dest'],
            'name': module.params['name'],
            'udp_port': module.params['udp_port'],
            'level': set(module.params['level']) if module.params['level'] else None,
            'facility': module.params['facility'],
            'state': module.params['state'],
            'addr6': addr6
        })
    return obj


def main():
    """ main entry point for module execution
    """
    element_spec = dict(
        dest=dict(
            type='str',
            choices=[
                'on',
                'host',
                'console',
                'buffered',
                'persistence',
                'rfc5424']),
        name=dict(
            type='str'),
        udp_port=dict(),
        level=dict(
            type='list',
            choices=[
                'alerts',
                'critical',
                'debugging',
                'emergencies',
                'errors',
                'informational',
                'notifications',
                'warnings']),
        facility=dict(
            type='str',
            choices=[
                'auth',
                'cron',
                'daemon',
                'kern',
                'local0',
                'local1',
                'local2',
                'local3',
                'local4',
                'local5',
                'local6',
                'local7',
                'user',
                'lpr',
                'mail',
                'news',
                'syslog',
                'sys9',
                'sys10',
                'sys11',
                'sys12',
                'sys13',
                'sys14',
                'user',
                'uucp']),
        state=dict(
            default='present',
            choices=[
                'present',
                'absent']),
        check_running_config=dict(default=True, type='bool', fallback=(env_fallback, ['ANSIBLE_CHECK_ICX_RUNNING_CONFIG'])))

    aggregate_spec = deepcopy(element_spec)

    remove_default_spec(aggregate_spec)

    argument_spec = dict(
        aggregate=dict(type='list', elements='dict', options=aggregate_spec),
    )

    argument_spec.update(element_spec)
    required_if = [('dest', 'host', ['name']),
                   ('dest', 'buffered', ['level'])]
    module = AnsibleModule(argument_spec=argument_spec,
                           required_if=required_if,
                           supports_check_mode=True)

    result = {'changed': False}
    warnings = list()

    exec_command(module, 'skip')
    if warnings:
        result['warnings'] = warnings

    want = map_params_to_obj(module, required_if=required_if)
    have = map_config_to_obj(module)
    result['want'] = want
    result['have'] = have

    commands = map_obj_to_commands((want, have))
    result['commands'] = commands
    if commands:
        if not module.check_mode:
            load_config(module, commands)
        result['changed'] = True
    module.exit_json(**result)


if __name__ == '__main__':
    main()
