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

# (c) 2019, Ansible by Red Hat, inc
# 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: asa_og
author:
- Federico Olivieri (@Federico87)
short_description: (deprecated, removed after 2022-06-01) Manage object groups on
  a Cisco ASA
description:
- This module allows you to create and update object-group network/service on Cisco
  ASA device.
version_added: 1.0.0
deprecated:
  alternative: asa_ogs
  why: Newer and updated modules released with more functionality in Ansible 2.10
  removed_at_date: '2022-06-01'
options:
  name:
    description:
    - Name of the object group.
    required: true
  group_type:
    description:
    - The object group type.
    choices:
    - network-object
    - service-object
    - port-object
    required: true
  protocol:
    description:
    - The protocol for object-group service with port-object.
    choices:
    - udp
    - tcp
    - tcp-udp
  host_ip:
    description:
    - The host IP address for object-group network.
    type: list
  description:
    description:
    - The description for the object-group.
  group_object:
    description:
    - The group-object for network object-group.
    type: list
  ip_mask:
    description:
    - The IP address and mask for network object-group.
    type: list
  port_range:
    description:
    - The port range for port-object.
  port_eq:
    description:
    - The single port for port-object.
  service_cfg:
    description:
    - The service-object configuration protocol, direction, range or port.
  state:
    description:
    - Manage the state of the resource.
    default: present
    choices:
    - present
    - absent
    - replace

"""

EXAMPLES = """
- name: configure network object-group
  cisco.asa.asa_og:
    name: ansible_test_0
    group_type: network-object
    state: present
    description: ansible_test object-group description
    host_ip:
    - 8.8.8.8
    - 8.8.4.4
    ip_mask:
    - 10.0.0.0 255.255.255.0
    - 192.168.0.0 255.255.0.0
    group_object:
    - awx_lon
    - awx_ams

- name: configure port-object object-group
  cisco.asa.asa_og:
    name: ansible_test_1
    group_type: port-object
    state: replace
    description: ansible_test object-group description
    protocol: tcp-udp
    port_eq:
    - 1025
    - kerberos
    port_range:
    - 1025 5201
    - 0 1024

- name: configure service-object object-group
  cisco.asa.asa_og:
    name: ansible_test_2
    group_type: service-object
    state: absent
    description: ansible_test object-group description
    service_cfg:
    - tcp destination eq 8080
    - tcp destination eq www
"""

RETURN = """
commands:
  description: command sent to the device
  returned: always
  type: list
  sample: [
    "object-group network ansible_test_0",
    "description ansible_test object-group description",
    "network-object host 8.8.8.8",
    "network-object host 8.8.4.4",
    "network-object 10.0.0.0 255.255.255.0",
    "network-object 192.168.0.0 255.255.0.0",
    "network-object 192.168.0.0 255.255.0.0",
    "group-object awx_lon",
    "group-object awx_ams",
    ]
"""
import re

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import (
    get_config,
    load_config,
)


class Parser:
    """Regex class for outputs parsing"""

    def __init__(self, config, protocol):
        """Parser __init__ method"""
        self.config = config
        self.protocol = protocol

    def parse_obj_grp_name(self):
        list_return = list()
        match = re.search(
            r"(?:object-group\s)(network\s|service\s)(\w+)\s?(tcp-udp|tcp|udp)?",
            self.config,
            re.M,
        )

        if match:
            if match.group(3):
                list_return.append(str(match.group(3)))
            else:
                list_return.append(False)

            if match.group(2):
                list_return.append(str(match.group(2)))

            if match.group(1):
                list_return.append(str(match.group(1)))

        return list_return

    def parse_description(self):
        match = re.search(r"description(:)?\s(.*)", self.config, re.M)
        if match:
            description = match.group(2)

            return description

    def parse_host(self):
        list_return = list()
        match = re.findall(r"(host\s)(\d+\.\d+\.\d+\.\d+)", self.config, re.M)

        if match:
            for i in match:
                if i[1]:
                    list_return.append(str(i[1]))

        return list_return

    def parse_group_object(self):
        list_return = list()
        match = re.findall(r"(group-object\s)(.*)", self.config, re.M)

        if match:
            for i in match:
                if i[1]:
                    list_return.append(str(i[1]))

        return list_return

    def parse_address(self):
        list_return = list()
        match = re.findall(
            r"(network-object\s)(\d+\.\d+\.\d+\.\d+\s\d+\.\d+\.\d+\.\d+)",
            self.config,
            re.M,
        )

        if match:
            for i in match:
                if i[1]:
                    list_return.append(str(i[1]))

        return list_return

    def parse_port_range(self):
        list_return = list()
        match = re.findall(r"(range\s)(.*)", self.config, re.M)

        if match:
            for i in match:
                if i[1]:
                    list_return.append(str(i[1]))

        return list_return

    def parse_port_eq(self):
        list_return = list()
        match = re.findall(r"(eq\s)(.*)", self.config, re.M)

        if match:
            for i in match:
                if i[1]:
                    list_return.append(str(i[1]))

        return list_return

    def parse_service_cfg(self):
        list_return = list()
        match = re.findall(r"(service-object\s)(.*)", self.config, re.M)

        if match:
            for i in match:
                if i[1]:
                    list_return.append(str(i[1]))

        return list_return


def map_config_to_obj(module):

    obj = list()
    obj_dict = dict()

    group_name = module.params["name"]
    protocol = module.params["protocol"]

    sh_run_group_name = get_config(
        module, flags=["object-group | include {0}".format(group_name)]
    )
    run_group_name = Parser(sh_run_group_name, protocol).parse_obj_grp_name()

    obj_dict["have_name"] = run_group_name

    if run_group_name:
        if run_group_name[0] is not False:
            obj_dict["have_group_type"] = "port-object"
            obj_dict["have_protocol"] = run_group_name[0]
        elif "network" in run_group_name[2]:
            obj_dict["have_group_type"] = "network-object"
        elif "service" in run_group_name[2] and run_group_name[0] is False:
            obj_dict["have_group_type"] = "service-object"
        else:
            obj_dict["have_group_type"] = None

    sh_run_group_type = get_config(
        module, flags=["object-group id {0}".format(group_name)]
    )

    have_description = Parser(sh_run_group_type, protocol).parse_description()
    obj_dict["have_description"] = have_description

    have_host_ip = Parser(sh_run_group_type, protocol).parse_host()
    obj_dict["have_host_ip"] = have_host_ip

    have_group_object = Parser(
        sh_run_group_type, protocol
    ).parse_group_object()
    obj_dict["have_group_object"] = have_group_object

    have_ip_mask = Parser(sh_run_group_type, protocol).parse_address()
    obj_dict["have_ip_mask"] = have_ip_mask

    have_port_range = Parser(sh_run_group_type, protocol).parse_port_range()
    obj_dict["have_port_range"] = have_port_range

    have_port_eq = Parser(sh_run_group_type, protocol).parse_port_eq()
    obj_dict["have_port_eq"] = have_port_eq

    have_service_cfg = Parser(sh_run_group_type, protocol).parse_service_cfg()

    if have_service_cfg:
        have_lines = list()
        for i in have_service_cfg:
            have_lines.append(i.rstrip(" "))
        obj_dict["have_service_cfg"] = have_lines
    elif have_service_cfg is None:
        obj_dict["have_service_cfg"] = have_service_cfg

    obj.append(obj_dict)

    return obj


def replace(want_dict, have):

    commands = list()
    add_lines = list()
    remove_lines = list()

    have_group_type = have[0].get("have_group_type")
    have_description = have[0].get("have_description")
    have_host_ip = have[0].get("have_host_ip")
    have_group_object = have[0].get("have_group_object")
    have_ip_mask = have[0].get("have_ip_mask")
    have_protocol = have[0].get("have_protocol")
    have_port_range = have[0].get("have_port_range")
    have_port_eq = have[0].get("have_port_eq")
    have_service_cfg = have[0].get("have_service_cfg")

    name = want_dict["name"]
    group_type = want_dict["group_type"]
    protocol = want_dict["protocol"]
    description = want_dict["description"]
    host = want_dict["host_ip"]
    group_object = want_dict["group_object"]
    address = want_dict["ip_mask"]
    port_range = want_dict["port_range"]
    port_eq = want_dict["port_eq"]
    service_cfg = want_dict["service_cfg"]

    if "network-object" in group_type:

        if have_group_type is None:
            commands.append("object-group network {0}".format(name))

            if host:
                for i in host:
                    commands.append("network-object host " + i)
            if description:
                if have_description is None:
                    commands.append("description {0}".format(description))
            if group_object:
                for i in group_object:
                    if i not in have_group_object:
                        commands.append("group-object " + i)
            if address:
                for i in address:
                    commands.append("network-object " + i)

        elif "network" in have_group_type:

            if host:
                if sorted(host) != sorted(have_host_ip):
                    for i in host:
                        if i not in have_host_ip:
                            if (
                                "object-group network {0}".format(name)
                                not in commands
                            ):
                                commands.append(
                                    "object-group network {0}".format(name)
                                )
                            add_lines.append("network-object host " + i)
                    for i in have_host_ip:
                        if i not in host:
                            if (
                                "object-group network {0}".format(name)
                                not in commands
                            ):
                                commands.append(
                                    "object-group network {0}".format(name)
                                )
                            remove_lines.append("no network-object host " + i)

            if description:
                if description != have_description:
                    if "object-group network {0}".format(name) not in commands:
                        commands.append(
                            "object-group network {0}".format(name)
                        )
                    add_lines.append("description {0}".format(description))

            if group_object:
                if sorted(group_object) != sorted(have_group_object):
                    for i in group_object:
                        if i not in have_group_object:
                            if (
                                "object-group network {0}".format(name)
                                not in commands
                            ):
                                commands.append(
                                    "object-group network {0}".format(name)
                                )
                            add_lines.append("group-object " + i)
                    for i in have_group_object:
                        if i not in group_object:
                            if (
                                "object-group network {0}".format(name)
                                not in commands
                            ):
                                commands.append(
                                    "object-group network {0}".format(name)
                                )
                            remove_lines.append("no group-object " + i)
            if address:
                if sorted(address) != sorted(have_ip_mask):
                    for i in address:
                        if i not in have_ip_mask:
                            if (
                                "object-group network {0}".format(name)
                                not in commands
                            ):
                                commands.append(
                                    "object-group network {0}".format(name)
                                )
                            add_lines.append("network-object " + i)
                    for i in have_ip_mask:
                        if i not in address:
                            if (
                                "object-group network {0}".format(name)
                                not in commands
                            ):
                                commands.append(
                                    "object-group network {0}".format(name)
                                )
                            remove_lines.append("no network-object " + i)

    elif "port-object" in group_type:

        if have_group_type is None and have_protocol != protocol:
            commands.append(
                "object-group service {0} {1}".format(name, protocol)
            )

            if port_range:
                for i in port_range:
                    commands.append("port-object range " + i)
            if port_eq:
                for i in port_eq:
                    commands.append("port-object eq " + i)
            if description:
                commands.append("description {0}".format(description))

        elif "port" in have_group_type and have_protocol == protocol:

            if port_range:
                if sorted(port_range) != sorted(have_port_range):
                    for i in port_range:
                        if i not in have_port_range:
                            if (
                                "object-group service {0} {1}".format(
                                    name, protocol
                                )
                                not in commands
                            ):
                                commands.append(
                                    "object-group service {0} {1}".format(
                                        name, protocol
                                    )
                                )
                            add_lines.append("port-object range " + i)
                    for i in have_port_range:
                        if i not in port_range:
                            if (
                                "object-group service {0} {1}".format(
                                    name, protocol
                                )
                                not in commands
                            ):
                                commands.append(
                                    "object-group service {0} {1}".format(
                                        name, protocol
                                    )
                                )
                            remove_lines.append("no port-object range " + i)
            if port_eq:
                if sorted(port_eq) != sorted(have_port_eq):
                    for i in port_eq:
                        if i not in have_port_eq:
                            if (
                                "object-group service {0} {1}".format(
                                    name, protocol
                                )
                                not in commands
                            ):
                                commands.append(
                                    "object-group service {0} {1}".format(
                                        name, protocol
                                    )
                                )
                            add_lines.append("port-object eq " + i)
                    for i in have_port_eq:
                        if i not in port_eq:
                            if (
                                "object-group service {0} {1}".format(
                                    name, protocol
                                )
                                not in commands
                            ):
                                commands.append(
                                    "object-group service {0} {1}".format(
                                        name, protocol
                                    )
                                )
                            remove_lines.append("no port-object eq " + i)
            if description:
                if description != have_description:
                    if (
                        "object-group service {0} {1}".format(name, protocol)
                        not in commands
                    ):
                        commands.append(
                            "object-group service {0} {1}".format(
                                name, protocol
                            )
                        )
                    commands.append("description {0}".format(description))

    elif "service-object" in group_type:

        if have_group_type is None:
            commands.append("object-group service {0}".format(name))

            if description:
                if have_description is None:
                    commands.append("description {0}".format(description))
            if service_cfg:
                for i in service_cfg:
                    commands.append("service-object " + i)

        elif "service" in have_group_type:
            if description:
                if description != have_description:
                    if "object-group service {0}".format(name) not in commands:
                        commands.append(
                            "object-group service {0}".format(name)
                        )
                    commands.append("description {0}".format(description))
            if service_cfg:
                for i in service_cfg:
                    if i not in have_service_cfg:
                        if (
                            "object-group service {0}".format(name)
                            not in commands
                        ):
                            commands.append(
                                "object-group service {0}".format(name)
                            )
                        add_lines.append("service " + i)
                for i in have_service_cfg:
                    if i not in service_cfg:
                        if (
                            "object-group service {0}".format(name)
                            not in commands
                        ):
                            commands.append(
                                "object-group service {0}".format(name)
                            )
                        remove_lines.append("no service " + i)

    set_add_lines = set(add_lines)
    set_remove_lines = set(remove_lines)

    for i in list(set_add_lines) + list(set_remove_lines):
        commands.append(i)

    return commands


def present(want_dict, have):

    commands = list()

    have_group_type = have[0].get("have_group_type")
    have_description = have[0].get("have_description")
    have_host_ip = have[0].get("have_host_ip")
    have_group_object = have[0].get("have_group_object")
    have_ip_mask = have[0].get("have_ip_mask")
    have_protocol = have[0].get("have_protocol")
    have_port_range = have[0].get("have_port_range")
    have_port_eq = have[0].get("have_port_eq")
    have_service_cfg = have[0].get("have_service_cfg")

    name = want_dict["name"]
    group_type = want_dict["group_type"]
    protocol = want_dict["protocol"]
    description = want_dict["description"]
    host = want_dict["host_ip"]
    group_object = want_dict["group_object"]
    address = want_dict["ip_mask"]
    port_range = want_dict["port_range"]
    port_eq = want_dict["port_eq"]
    service_cfg = want_dict["service_cfg"]

    if "network-object" in group_type:

        if have_group_type is None:
            commands.append("object-group network {0}".format(name))

            if host:
                for i in host:
                    commands.append("network-object host " + i)
            if description:
                if have_description is None:
                    commands.append("description {0}".format(description))
            if group_object:
                for i in group_object:
                    commands.append("group-object " + i)
            if address:
                for i in address:
                    commands.append("network-object " + i)

        elif "network" in have_group_type:

            if host:
                for i in host:
                    if i not in have_host_ip:
                        if (
                            "object-group network {0}".format(name)
                            not in commands
                        ):
                            commands.append(
                                "object-group network {0}".format(name)
                            )
                        commands.append("network-object host " + i)
            if description:
                if description != have_description:
                    if "object-group network {0}".format(name) not in commands:
                        commands.append(
                            "object-group network {0}".format(name)
                        )
                    commands.append("description {0}".format(description))
            if group_object:
                for i in group_object:
                    if i not in have_group_object:
                        if (
                            "object-group network {0}".format(name)
                            not in commands
                        ):
                            commands.append(
                                "object-group network {0}".format(name)
                            )
                        commands.append("group-object " + i)
            if address:
                for i in address:
                    if i not in have_ip_mask:
                        if (
                            "object-group network {0}".format(name)
                            not in commands
                        ):
                            commands.append(
                                "object-group network {0}".format(name)
                            )
                        commands.append("network-object " + i)

    elif "port-object" in group_type:

        if have_group_type is None and have_protocol != protocol:
            commands.append(
                "object-group service {0} {1}".format(name, protocol)
            )

            if port_range:
                for i in port_range:
                    commands.append("port-object range " + i)
            if port_eq:
                for i in port_eq:
                    commands.append("port-object eq " + i)
            if description:
                commands.append("description {0}".format(description))

        elif "port" in have_group_type and have_protocol == protocol:

            if port_range:
                for i in port_range:
                    if i not in have_port_range:
                        if (
                            "object-group service {0} {1}".format(
                                name, protocol
                            )
                            not in commands
                        ):
                            commands.append(
                                "object-group service {0} {1}".format(
                                    name, protocol
                                )
                            )
                        commands.append("port-object range " + i)
            if port_eq:
                for i in port_eq:
                    if i not in have_port_eq:
                        if (
                            "object-group service {0} {1}".format(
                                name, protocol
                            )
                            not in commands
                        ):
                            commands.append(
                                "object-group service {0} {1}".format(
                                    name, protocol
                                )
                            )
                        commands.append("port-object eq " + i)
            if description:
                if description != have_description:
                    if (
                        "object-group service {0} {1}".format(name, protocol)
                        not in commands
                    ):
                        commands.append(
                            "object-group service {0} {1}".format(
                                name, protocol
                            )
                        )
                    commands.append("description {0}".format(description))

    elif "service-object" in group_type:

        if have_group_type is None:
            commands.append("object-group service {0}".format(name))

            if description:
                if have_description is None:
                    commands.append("description {0}".format(description))
            if service_cfg:
                for i in service_cfg:
                    commands.append("service-object " + i)

        elif "service" in have_group_type:

            if description:
                if description != have_description:
                    if "object-group service {0}".format(name) not in commands:
                        commands.append(
                            "object-group service {0}".format(name)
                        )
                    commands.append("description {0}".format(description))
            if service_cfg:
                for i in service_cfg:
                    if i not in have_service_cfg:
                        if (
                            "object-group service {0}".format(name)
                            not in commands
                        ):
                            commands.append(
                                "object-group service {0}".format(name)
                            )
                        commands.append("service " + i)

    return commands


def absent(want_dict, have):

    commands = list()

    have_group_type = have[0].get("have_group_type")
    have_description = have[0].get("have_description")
    have_host_ip = have[0].get("have_host_ip")
    have_group_object = have[0].get("have_group_object")
    have_ip_mask = have[0].get("have_ip_mask")
    have_protocol = have[0].get("have_protocol")
    have_port_range = have[0].get("have_port_range")
    have_port_eq = have[0].get("have_port_eq")
    have_service_cfg = have[0].get("have_service_cfg")

    name = want_dict["name"]
    group_type = want_dict["group_type"]
    protocol = want_dict["protocol"]
    description = want_dict["description"]
    host = want_dict["host_ip"]
    group_object = want_dict["group_object"]
    address = want_dict["ip_mask"]
    port_range = want_dict["port_range"]
    port_eq = want_dict["port_eq"]
    service_cfg = want_dict["service_cfg"]

    if "network-object" in group_type:

        if have_group_type is None:
            return commands

        elif "network" in have_group_type:

            if host:
                for i in host:
                    if i in have_host_ip:
                        if (
                            "object-group network {0}".format(name)
                            not in commands
                        ):
                            commands.append(
                                "object-group network {0}".format(name)
                            )
                        commands.append("no network-object host " + i)
            if description:
                if description == have_description:
                    if "object-group network {0}".format(name) not in commands:
                        commands.append(
                            "object-group network {0}".format(name)
                        )
                    commands.append("no description {0}".format(description))
            if group_object:
                for i in group_object:
                    if i in have_group_object:
                        if (
                            "object-group network {0}".format(name)
                            not in commands
                        ):
                            commands.append(
                                "object-group network {0}".format(name)
                            )
                        commands.append("no group-object " + i)
            if address:
                for i in address:
                    if i in have_ip_mask:
                        if (
                            "object-group network {0}".format(name)
                            not in commands
                        ):
                            commands.append(
                                "object-group network {0}".format(name)
                            )
                        commands.append("no network-object " + i)

    elif "port-object" in group_type:

        if have_group_type is None and have_protocol is None:
            return commands

        elif "port" in have_group_type and have_protocol == protocol:

            if port_range:
                for i in port_range:
                    if i in have_port_range:
                        if (
                            "object-group service {0} {1}".format(
                                name, protocol
                            )
                            not in commands
                        ):
                            commands.append(
                                "object-group service {0} {1}".format(
                                    name, protocol
                                )
                            )
                        commands.append("no port-object range " + i)
            if port_eq:
                for i in port_eq:
                    if i in have_port_eq:
                        if (
                            "object-group service {0} {1}".format(
                                name, protocol
                            )
                            not in commands
                        ):
                            commands.append(
                                "object-group service {0} {1}".format(
                                    name, protocol
                                )
                            )
                        commands.append("no port-object eq " + i)
            if description:
                if description == have_description:
                    if (
                        "object-group service {0} {1}".format(name, protocol)
                        not in commands
                    ):
                        commands.append(
                            "object-group service {0} {1}".format(
                                name, protocol
                            )
                        )
                    commands.append("no description {0}".format(description))

    elif "service-object" in group_type:

        if have_group_type is None:
            return commands

        elif "service" in have_group_type:
            if description:
                if description == have_description:
                    if "object-group service {0}".format(name) not in commands:
                        commands.append(
                            "object-group service {0}".format(name)
                        )
                    commands.append("no description {0}".format(description))
            if service_cfg:
                for i in service_cfg:
                    if i in have_service_cfg:
                        if (
                            "object-group service {0}".format(name)
                            not in commands
                        ):
                            commands.append(
                                "object-group service {0}".format(name)
                            )
                        commands.append("no service " + i)

    return commands


def map_obj_to_commands(want, have, module):

    for w in want:

        want_dict = dict()

        want_dict["name"] = w["name"]
        want_dict["group_type"] = w["group_type"]
        want_dict["protocol"] = w["protocol"]
        want_dict["description"] = w["description"]
        want_dict["host_ip"] = w["host_ip"]
        want_dict["group_object"] = w["group_object"]
        want_dict["ip_mask"] = w["ip_mask"]
        want_dict["port_range"] = w["port_range"]
        want_dict["port_eq"] = w["port_eq"]
        want_dict["service_cfg"] = w["service_cfg"]
        state = w["state"]

        if state == "replace":
            return replace(want_dict, have)
        elif state == "present":
            return present(want_dict, have)
        elif state == "absent":
            return absent(want_dict, have)


def map_params_to_obj(module):

    obj = list()

    obj.append(
        {
            "name": module.params["name"],
            "group_type": module.params["group_type"],
            "protocol": module.params["protocol"],
            "state": module.params["state"],
            "description": module.params["description"],
            "host_ip": module.params["host_ip"],
            "group_object": module.params["group_object"],
            "port_range": module.params["port_range"],
            "port_eq": module.params["port_eq"],
            "service_cfg": module.params["service_cfg"],
            "ip_mask": module.params["ip_mask"],
        }
    )

    return obj


def main():

    argument_spec = dict(
        name=dict(required=True),
        group_type=dict(
            choices=["network-object", "service-object", "port-object"],
            required=True,
        ),
        protocol=dict(choices=["udp", "tcp", "tcp-udp"]),
        host_ip=dict(type="list"),
        description=dict(),
        group_object=dict(type="list"),
        ip_mask=dict(type="list"),
        port_range=dict(type="list"),
        port_eq=dict(type="list"),
        service_cfg=dict(type="list"),
        state=dict(
            choices=["present", "absent", "replace"], default="present"
        ),
    )

    required_if = [
        ("group_type", "port-object", ["protocol"]),
        ("group_type", "service-object", ["service_cfg"]),
    ]

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

    result = {"changed": False}

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

    result["commands"] = config_commans

    if config_commans:
        if not module.check_mode:
            load_config(module, config_commans)
        result["changed"] = True

    module.exit_json(**result)


if __name__ == "__main__":
    main()
