#!/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: (deprecated, removed after 2024-01-01) 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
- name: Example ntp play
  cisco.ios.ios_ntp:
    server: 10.0.255.10
    source_int: Loopback0
    logging: false
    state: present

# Remove NTP ACL and logging
- name: Example ntp play absent
  cisco.ios.ios_ntp:
    acl: NTP_ACL
    logging: true
    state: absent

# Set NTP authentication
- name: Example ntp play auth
  cisco.ios.ios_ntp:
    key_id: 10
    auth_key: 15435A030726242723273C21181319000A
    auth: true
    state: present

# Set new NTP configuration
- name: Example ntp play auth
  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,
)


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

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

        if match and match.group(3):
            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(),
    )
    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()
