#!/usr/bin/python
#
# 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/>.
#
from __future__ import absolute_import, division, print_function

__metaclass__ = type
DOCUMENTATION = """
module: ios_ntp
extends_documentation_fragment:
- cisco.ios.ios
short_description: Manages core NTP configuration.
description:
- Manages core NTP configuration.
version_added: 1.0.0
deprecated:
  alternative: ios_ntp_global
  why: Updated module released with more functionality.
  removed_at_date: '2024-01-01'
author:
- Federico Olivieri (@Federico87)
- Joanie Sylvain (@JoanieAda)
options:
  server:
    description:
    - Network address of NTP server.
    type: str
  source_int:
    description:
    - Source interface for NTP packets.
    type: str
  acl:
    description:
    - ACL for peer/server access restricition.
    type: str
  logging:
    description:
    - Enable NTP logs. Data type boolean.
    type: bool
    default: false
  auth:
    description:
    - Enable NTP authentication. Data type boolean.
    type: bool
    default: false
  auth_key:
    description:
    - md5 NTP authentication key of tye 7.
    type: str
  key_id:
    description:
    - auth_key id. Data type string
    type: str
  state:
    description:
    - Manage the state of the resource.
    default: present
    choices:
    - present
    - absent
    type: str
  vrf:
    description:
    - VRF configuration for NTP servers
    type: str
"""
EXAMPLES = """
# Set new NTP server and source interface
- cisco.ios.ios_ntp:
    server: 10.0.255.10
    source_int: Loopback0
    logging: false
    state: present
# Remove NTP ACL and logging
- cisco.ios.ios_ntp:
    acl: NTP_ACL
    logging: true
    state: absent
# Set NTP authentication
- cisco.ios.ios_ntp:
    key_id: 10
    auth_key: 15435A030726242723273C21181319000A
    auth: true
    state: present
# Set new NTP configuration
- cisco.ios.ios_ntp:
    server: 10.0.255.10
    source_int: Loopback0
    acl: NTP_ACL
    logging: true
    vrf: mgmt
    key_id: 10
    auth_key: 15435A030726242723273C21181319000A
    auth: true
    state: present
"""
RETURN = """
commands:
    description: command sent to the device
    returned: always
    type: list
    sample: ["no ntp server 10.0.255.10", "no ntp source Loopback0"]
"""
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.ios.plugins.module_utils.network.ios.ios import (
    get_config,
    load_config,
)
from ansible_collections.cisco.ios.plugins.module_utils.network.ios.ios import (
    ios_argument_spec,
)


def parse_server(line, dest):
    if dest == "server":
        match = re.search(
            "(ntp\\sserver\\s)(vrf\\s\\w+\\s)?(\\d+\\.\\d+\\.\\d+\\.\\d+)",
            line,
            re.M,
        )

        if match.group(2) and match.group(3):
            vrf = match.group(2)
            server = match.group(3)

            return vrf, server

        if match.group(3):
            vrf = None
            server = match.group(3)

            return vrf, server


def parse_source_int(line, dest):
    if dest == "source":
        match = re.search("(ntp\\ssource\\s)(\\S+)", line, re.M)
        if match:
            source = match.group(2)
            return source


def parse_acl(line, dest):
    if dest == "access-group":
        match = re.search(
            "ntp\\saccess-group\\s(?:peer|serve)(?:\\s+)(\\S+)", line, re.M
        )
        if match:
            acl = match.group(1)
            return acl


def parse_logging(line, dest):
    if dest == "logging":
        logging = dest
        return logging


def parse_auth_key(line, dest):
    if dest == "authentication-key":
        match = re.search(
            "(ntp\\sauthentication-key\\s\\d+\\smd5\\s)(\\w+)", line, re.M
        )
        if match:
            auth_key = match.group(2)
            return auth_key


def parse_key_id(line, dest):
    if dest == "trusted-key":
        match = re.search("(ntp\\strusted-key\\s)(\\d+)", line, re.M)
        if match:
            auth_key = match.group(2)
            return auth_key


def parse_auth(dest):
    if dest == "authenticate":
        return dest


def map_config_to_obj(module):

    obj_dict = dict()
    obj = list()
    server_list = list()
    config = get_config(module, flags=["| include ntp"])

    for line in config.splitlines():

        match = re.search("ntp\\s(\\S+)", line, re.M)

        if match:
            dest = match.group(1)
            server = parse_server(line, dest)
            source_int = parse_source_int(line, dest)
            acl = parse_acl(line, dest)
            logging = parse_logging(line, dest)
            auth = parse_auth(dest)
            auth_key = parse_auth_key(line, dest)
            key_id = parse_key_id(line, dest)

            if server:
                if server[0] is None:
                    server_list.append((server[0], server[1]))
                else:
                    server_list.append((server[0].split()[1], server[1]))

            if source_int:
                obj_dict["source_int"] = source_int

            if acl:
                obj_dict["acl"] = acl

            if logging:
                obj_dict["logging"] = True

            if auth:
                obj_dict["auth"] = True

            if auth_key:
                obj_dict["auth_key"] = auth_key

            if key_id:
                obj_dict["key_id"] = key_id

    obj_dict["server"] = server_list

    obj.append(obj_dict)

    return obj


def map_params_to_obj(module):
    obj = list()

    obj.append(
        {
            "state": module.params["state"],
            "server": module.params["server"],
            "source_int": module.params["source_int"],
            "logging": module.params["logging"],
            "acl": module.params["acl"],
            "auth": module.params["auth"],
            "auth_key": module.params["auth_key"],
            "key_id": module.params["key_id"],
            "vrf": module.params["vrf"],
        }
    )

    return obj


def map_obj_to_commands(want, have, module):
    commands = list()

    server_have = have[0].get("server", None)
    source_int_have = have[0].get("source_int", None)
    acl_have = have[0].get("acl", None)
    logging_have = have[0].get("logging", None)
    auth_have = have[0].get("auth", None)
    auth_key_have = have[0].get("auth_key", None)
    key_id_have = have[0].get("key_id", None)

    for w in want:

        server = w["server"]
        source_int = w["source_int"]
        acl = w["acl"]
        logging = w["logging"]
        state = w["state"]
        auth = w["auth"]
        auth_key = w["auth_key"]
        key_id = w["key_id"]
        vrf = w["vrf"]
        if vrf == "":
            vrf = None

        if state == "absent":

            if server_have and (vrf, server) in server_have:
                if vrf is not None:
                    commands.append(
                        "no ntp server vrf {0} {1}".format(vrf, server)
                    )
                else:
                    commands.append("no ntp server {0}".format(server))

            if source_int and source_int_have:
                commands.append("no ntp source {0}".format(source_int))

            if acl and acl_have:
                commands.append("no ntp access-group peer {0}".format(acl))

            if logging is True and logging_have:
                commands.append("no ntp logging")

            if auth is True and auth_have:
                commands.append("no ntp authenticate")

            if key_id and key_id_have:
                commands.append("no ntp trusted-key {0}".format(key_id))

            if auth_key and auth_key_have:
                if key_id and key_id_have:
                    commands.append(
                        "no ntp authentication-key {0} md5 {1} 7".format(
                            key_id, auth_key
                        )
                    )
        elif state == "present":

            if server is not None and (vrf, server) not in server_have:
                if vrf is not None:
                    commands.append(
                        "ntp server vrf {0} {1}".format(vrf, server)
                    )
                else:
                    commands.append("ntp server {0}".format(server))

            if source_int is not None and source_int != source_int_have:
                commands.append("ntp source {0}".format(source_int))

            if acl is not None and acl != acl_have:
                commands.append("ntp access-group peer {0}".format(acl))

            if (
                logging is not None
                and logging != logging_have
                and logging is not False
            ):
                commands.append("ntp logging")

            if auth is not None and auth != auth_have and auth is not False:
                commands.append("ntp authenticate")

            if key_id is not None and key_id != key_id_have:
                commands.append("ntp trusted-key {0}".format(key_id))

            if auth_key is not None and auth_key != auth_key_have:
                if key_id is not None:
                    commands.append(
                        "ntp authentication-key {0} md5 {1} 7".format(
                            key_id, auth_key
                        )
                    )
    return commands


def main():

    argument_spec = dict(
        server=dict(),
        source_int=dict(),
        acl=dict(),
        logging=dict(type="bool", default=False),
        auth=dict(type="bool", default=False),
        auth_key=dict(no_log=True),
        key_id=dict(),
        state=dict(choices=["absent", "present"], default="present"),
        vrf=dict(),
    )

    argument_spec.update(ios_argument_spec)

    module = AnsibleModule(
        argument_spec=argument_spec, supports_check_mode=True
    )

    result = {"changed": False}

    warnings = list()

    if warnings:
        result["warnings"] = warnings

    want = map_params_to_obj(module)
    have = map_config_to_obj(module)
    commands = map_obj_to_commands(want, have, module)

    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()
