#!/usr/bin/python
#
# Copyright (C) 2019 Huawei
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations

###############################################################################
# Documentation
###############################################################################

DOCUMENTATION = r"""
module: hwc_vpc_security_group
description:
  - VPC security group management.
short_description: Creates a resource of VPC/SecurityGroup in Huawei Cloud
notes:
  - If O(id) option is provided, it takes precedence over O(name), O(enterprise_project_id), and O(vpc_id) for security group
    selection.
  - O(name), O(enterprise_project_id) and O(vpc_id) are used for security group selection. If more than one security group
    with this options exists, execution is aborted.
  - No parameter support updating. If one of option is changed, the module creates a new resource.
version_added: '0.2.0'
author: Huawei Inc. (@huaweicloud)
requirements:
  - keystoneauth1 >= 3.6.0
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  state:
    description:
      - Whether the given object should exist in Huawei Cloud.
    type: str
    choices: ['present', 'absent']
    default: 'present'
  name:
    description:
      - Specifies the security group name. The value is a string of 1 to 64 characters that can contain letters, digits, underscores
        (V(_)), hyphens (V(-)), and periods (V(.)).
    type: str
    required: true
  enterprise_project_id:
    description:
      - Specifies the enterprise project ID. When creating a security group, associate the enterprise project ID with the
        security group.s.
    type: str
  vpc_id:
    description:
      - Specifies the resource ID of the VPC to which the security group belongs.
    type: str
extends_documentation_fragment:
  - community.general._hwc
  - community.general._attributes
"""

EXAMPLES = r"""
# create a security group
- name: Create a security group
  community.general.hwc_vpc_security_group:
    name: "ansible_network_security_group_test"
"""

RETURN = r"""
name:
  description:
    - Specifies the security group name. The value is a string of 1 to 64 characters that can contain letters, digits, underscores
      (V(_)), hyphens (V(-)), and periods (V(.)).
  type: str
  returned: success
enterprise_project_id:
  description:
    - Specifies the enterprise project ID. When creating a security group, associate the enterprise project ID with the security
      group.
  type: str
  returned: success
vpc_id:
  description:
    - Specifies the resource ID of the VPC to which the security group belongs.
  type: str
  returned: success
rules:
  description:
    - Specifies the security group rule, which ensures that resources in the security group can communicate with one another.
  type: list
  elements: dict
  returned: success
  contains:
    description:
      description:
        - Provides supplementary information about the security group rule.
      type: str
      returned: success
    direction:
      description:
        - Specifies the direction of access control. The value can be egress or ingress.
      type: str
      returned: success
    ethertype:
      description:
        - Specifies the IP protocol version. The value can be IPv4 or IPv6.
      type: str
      returned: success
    id:
      description:
        - Specifies the security group rule ID.
      type: str
      returned: success
    port_range_max:
      description:
        - Specifies the end port number. The value ranges from 1 to 65535. If the protocol is not icmp, the value cannot be
          smaller than the port_range_min value. An empty value indicates all ports.
      type: int
      returned: success
    port_range_min:
      description:
        - Specifies the start port number. The value ranges from 1 to 65535. The value cannot be greater than the port_range_max
          value. An empty value indicates all ports.
      type: int
      returned: success
    protocol:
      description:
        - Specifies the protocol type. The value can be icmp, tcp, udp, or others. If the parameter is left blank, the security
          group supports all protocols.
      type: str
      returned: success
    remote_address_group_id:
      description:
        - Specifies the ID of remote IP address group.
      type: str
      returned: success
    remote_group_id:
      description:
        - Specifies the ID of the peer security group.
      type: str
      returned: success
    remote_ip_prefix:
      description:
        - Specifies the remote IP address. If the access control direction is set to egress, the parameter specifies the source
          IP address. If the access control direction is set to ingress, the parameter specifies the destination IP address.
      type: str
      returned: success
"""

from ansible_collections.community.general.plugins.module_utils._hwc_utils import (
    Config,
    HwcClientException,
    HwcModule,
    are_different_dicts,
    build_path,
    get_region,
    is_empty_value,
    navigate_value,
)


def build_module():
    return HwcModule(
        argument_spec=dict(
            state=dict(default="present", choices=["present", "absent"], type="str"),
            name=dict(type="str", required=True),
            enterprise_project_id=dict(type="str"),
            vpc_id=dict(type="str"),
        ),
        supports_check_mode=True,
    )


def main():
    """Main function"""

    module = build_module()
    config = Config(module, "vpc")

    try:
        resource = None
        if module.params.get("id"):
            resource = read_resource(config)
            if module.params["state"] == "present":
                check_resource_option(resource, module)
        else:
            v = search_resource(config)
            if len(v) > 1:
                raise Exception(f"Found more than one resource({', '.join([navigate_value(i, ['id']) for i in v])})")

            if len(v) == 1:
                resource = update_properties(module, {"read": v[0]}, None)
                module.params["id"] = navigate_value(resource, ["id"])

        result = {}
        changed = False
        if module.params["state"] == "present":
            if resource is None:
                if not module.check_mode:
                    resource = create(config)
                changed = True

            result = resource
        else:
            if resource:
                if not module.check_mode:
                    delete(config)
                changed = True

    except Exception as ex:
        module.fail_json(msg=str(ex))

    else:
        result["changed"] = changed
        module.exit_json(**result)


def user_input_parameters(module):
    return {
        "enterprise_project_id": module.params.get("enterprise_project_id"),
        "name": module.params.get("name"),
        "vpc_id": module.params.get("vpc_id"),
        "id": module.params.get("id"),
    }


def check_resource_option(resource, module):
    opts = user_input_parameters(module)

    resource = {
        "enterprise_project_id": resource.get("enterprise_project_id"),
        "name": resource.get("name"),
        "vpc_id": resource.get("vpc_id"),
        "id": resource.get("id"),
    }

    if are_different_dicts(resource, opts):
        raise Exception(
            f"Cannot change option from ({resource}) to ({opts}) for an existing security group({module.params['id']})."
        )


def create(config):
    module = config.module
    client = config.client(get_region(module), "vpc", "project")
    opts = user_input_parameters(module)

    params = build_create_parameters(opts)
    r = send_create_request(module, params, client)
    module.params["id"] = navigate_value(r, ["security_group", "id"])

    result = update_properties(module, {"read": fill_read_resp_body(r)}, None)
    return result


def delete(config):
    module = config.module
    client = config.client(get_region(module), "vpc", "project")

    send_delete_request(module, None, client)


def read_resource(config, exclude_output=False):
    module = config.module
    client = config.client(get_region(module), "vpc", "project")

    res = {}

    r = send_read_request(module, client)
    res["read"] = fill_read_resp_body(r)

    return update_properties(module, res, None, exclude_output)


def _build_query_link(opts):
    query_params = []

    v = navigate_value(opts, ["enterprise_project_id"])
    if v:
        query_params.append(f"enterprise_project_id={v}")

    v = navigate_value(opts, ["vpc_id"])
    if v:
        query_params.append(f"vpc_id={v}")

    query_link = "?marker={marker}&limit=10"
    if query_params:
        query_link += f"&{'&'.join(query_params)}"

    return query_link


def search_resource(config):
    module = config.module
    client = config.client(get_region(module), "vpc", "project")
    opts = user_input_parameters(module)
    identity_obj = _build_identity_object(opts)
    query_link = _build_query_link(opts)
    link = f"security-groups{query_link}"

    result = []
    p = {"marker": ""}
    while True:
        url = link.format(**p)
        r = send_list_request(module, client, url)
        if not r:
            break

        for item in r:
            item = fill_list_resp_body(item)
            if not are_different_dicts(identity_obj, item):
                result.append(item)

        if len(result) > 1:
            break

        p["marker"] = r[-1].get("id")

    return result


def build_create_parameters(opts):
    params = dict()

    v = navigate_value(opts, ["enterprise_project_id"], None)
    if not is_empty_value(v):
        params["enterprise_project_id"] = v

    v = navigate_value(opts, ["name"], None)
    if not is_empty_value(v):
        params["name"] = v

    v = navigate_value(opts, ["vpc_id"], None)
    if not is_empty_value(v):
        params["vpc_id"] = v

    if not params:
        return params

    params = {"security_group": params}

    return params


def send_create_request(module, params, client):
    url = "security-groups"
    try:
        r = client.post(url, params)
    except HwcClientException as ex:
        msg = f"module(hwc_vpc_security_group): error running api(create), error: {ex}"
        module.fail_json(msg=msg)

    return r


def send_delete_request(module, params, client):
    url = build_path(module, "security-groups/{id}")

    try:
        r = client.delete(url, params)
    except HwcClientException as ex:
        msg = f"module(hwc_vpc_security_group): error running api(delete), error: {ex}"
        module.fail_json(msg=msg)

    return r


def send_read_request(module, client):
    url = build_path(module, "security-groups/{id}")

    r = None
    try:
        r = client.get(url)
    except HwcClientException as ex:
        msg = f"module(hwc_vpc_security_group): error running api(read), error: {ex}"
        module.fail_json(msg=msg)

    return navigate_value(r, ["security_group"], None)


def fill_read_resp_body(body):
    result = dict()

    result["enterprise_project_id"] = body.get("enterprise_project_id")

    result["id"] = body.get("id")

    result["name"] = body.get("name")

    v = fill_read_resp_security_group_rules(body.get("security_group_rules"))
    result["security_group_rules"] = v

    result["vpc_id"] = body.get("vpc_id")

    return result


def fill_read_resp_security_group_rules(value):
    if not value:
        return None

    result = []
    for item in value:
        val = dict()

        val["description"] = item.get("description")

        val["direction"] = item.get("direction")

        val["ethertype"] = item.get("ethertype")

        val["id"] = item.get("id")

        val["port_range_max"] = item.get("port_range_max")

        val["port_range_min"] = item.get("port_range_min")

        val["protocol"] = item.get("protocol")

        val["remote_address_group_id"] = item.get("remote_address_group_id")

        val["remote_group_id"] = item.get("remote_group_id")

        val["remote_ip_prefix"] = item.get("remote_ip_prefix")

        val["security_group_id"] = item.get("security_group_id")

        result.append(val)

    return result


def update_properties(module, response, array_index, exclude_output=False):
    r = user_input_parameters(module)

    v = navigate_value(response, ["read", "enterprise_project_id"], array_index)
    r["enterprise_project_id"] = v

    v = navigate_value(response, ["read", "name"], array_index)
    r["name"] = v

    if not exclude_output:
        v = r.get("rules")
        v = flatten_rules(response, array_index, v, exclude_output)
        r["rules"] = v

    v = navigate_value(response, ["read", "vpc_id"], array_index)
    r["vpc_id"] = v

    v = navigate_value(response, ["read", "id"], array_index)
    r["id"] = v

    return r


def flatten_rules(d, array_index, current_value, exclude_output):
    n = 0
    result = current_value
    has_init_value = True
    if result:
        n = len(result)
    else:
        has_init_value = False
        result = []
        v = navigate_value(d, ["read", "security_group_rules"], array_index)
        if not v:
            return current_value
        n = len(v)

    new_array_index = dict()
    if array_index:
        new_array_index.update(array_index)

    for i in range(n):
        new_array_index["read.security_group_rules"] = i

        val = dict()
        if len(result) >= (i + 1) and result[i]:
            val = result[i]

        if not exclude_output:
            v = navigate_value(d, ["read", "security_group_rules", "description"], new_array_index)
            val["description"] = v

        if not exclude_output:
            v = navigate_value(d, ["read", "security_group_rules", "direction"], new_array_index)
            val["direction"] = v

        if not exclude_output:
            v = navigate_value(d, ["read", "security_group_rules", "ethertype"], new_array_index)
            val["ethertype"] = v

        if not exclude_output:
            v = navigate_value(d, ["read", "security_group_rules", "id"], new_array_index)
            val["id"] = v

        if not exclude_output:
            v = navigate_value(d, ["read", "security_group_rules", "port_range_max"], new_array_index)
            val["port_range_max"] = v

        if not exclude_output:
            v = navigate_value(d, ["read", "security_group_rules", "port_range_min"], new_array_index)
            val["port_range_min"] = v

        if not exclude_output:
            v = navigate_value(d, ["read", "security_group_rules", "protocol"], new_array_index)
            val["protocol"] = v

        if not exclude_output:
            v = navigate_value(d, ["read", "security_group_rules", "remote_address_group_id"], new_array_index)
            val["remote_address_group_id"] = v

        if not exclude_output:
            v = navigate_value(d, ["read", "security_group_rules", "remote_group_id"], new_array_index)
            val["remote_group_id"] = v

        if not exclude_output:
            v = navigate_value(d, ["read", "security_group_rules", "remote_ip_prefix"], new_array_index)
            val["remote_ip_prefix"] = v

        if len(result) >= (i + 1):
            result[i] = val
        else:
            for v in val.values():
                if v is not None:
                    result.append(val)
                    break

    return result if (has_init_value or result) else current_value


def send_list_request(module, client, url):
    r = None
    try:
        r = client.get(url)
    except HwcClientException as ex:
        msg = f"module(hwc_vpc_security_group): error running api(list), error: {ex}"
        module.fail_json(msg=msg)

    return navigate_value(r, ["security_groups"], None)


def _build_identity_object(all_opts):
    result = dict()

    v = navigate_value(all_opts, ["enterprise_project_id"], None)
    result["enterprise_project_id"] = v

    result["id"] = None

    v = navigate_value(all_opts, ["name"], None)
    result["name"] = v

    result["security_group_rules"] = None

    v = navigate_value(all_opts, ["vpc_id"], None)
    result["vpc_id"] = v

    return result


def fill_list_resp_body(body):
    result = dict()

    result["enterprise_project_id"] = body.get("enterprise_project_id")

    result["id"] = body.get("id")

    result["name"] = body.get("name")

    v = fill_list_resp_security_group_rules(body.get("security_group_rules"))
    result["security_group_rules"] = v

    result["vpc_id"] = body.get("vpc_id")

    return result


def fill_list_resp_security_group_rules(value):
    if not value:
        return None

    result = []
    for item in value:
        val = dict()

        val["description"] = item.get("description")

        val["direction"] = item.get("direction")

        val["ethertype"] = item.get("ethertype")

        val["id"] = item.get("id")

        val["port_range_max"] = item.get("port_range_max")

        val["port_range_min"] = item.get("port_range_min")

        val["protocol"] = item.get("protocol")

        val["remote_address_group_id"] = item.get("remote_address_group_id")

        val["remote_group_id"] = item.get("remote_group_id")

        val["remote_ip_prefix"] = item.get("remote_ip_prefix")

        val["security_group_id"] = item.get("security_group_id")

        result.append(val)

    return result


if __name__ == "__main__":
    main()
