#!/usr/bin/python
#
# Copyright (C) 2018 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_network_vpc
description:
  - Represents an vpc resource.
short_description: Creates a Huawei Cloud VPC
author: Huawei Inc. (@huaweicloud)
requirements:
  - requests >= 2.18.4
  - keystoneauth1 >= 3.6.0
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  state:
    description:
      - Whether the given object should exist in VPC.
    type: str
    choices: ['present', 'absent']
    default: 'present'
  timeouts:
    description:
      - The timeouts for each operations.
    type: dict
    default: {}
    suboptions:
      create:
        description:
          - The timeout for create operation.
        type: str
        default: '15m'
      update:
        description:
          - The timeout for update operation.
        type: str
        default: '15m'
      delete:
        description:
          - The timeout for delete operation.
        type: str
        default: '15m'
  name:
    description:
      - The name of vpc.
    type: str
    required: true
  cidr:
    description:
      - The range of available subnets in the VPC.
    type: str
    required: true
extends_documentation_fragment:
  - community.general._hwc
  - community.general._attributes
"""

EXAMPLES = r"""
- name: Create a vpc
  community.general.hwc_network_vpc:
    identity_endpoint: "{{ identity_endpoint }}"
    user: "{{ user }}"
    password: "{{ password }}"
    domain: "{{ domain }}"
    project: "{{ project }}"
    region: "{{ region }}"
    name: "vpc_1"
    cidr: "192.168.100.0/24"
    state: present
"""

RETURN = r"""
id:
  description:
    - The ID of VPC.
  type: str
  returned: success
name:
  description:
    - The name of VPC.
  type: str
  returned: success
cidr:
  description:
    - The range of available subnets in the VPC.
  type: str
  returned: success
status:
  description:
    - The status of VPC.
  type: str
  returned: success
routes:
  description:
    - The route information.
  type: complex
  returned: success
  contains:
    destination:
      description:
        - The destination network segment of a route.
      type: str
      returned: success
    next_hop:
      description:
        - The next hop of a route. If the route type is peering, it provides VPC peering connection ID.
      type: str
      returned: success
enable_shared_snat:
  description:
    - Show whether the shared SNAT is enabled.
  type: bool
  returned: success
"""

###############################################################################
# Imports
###############################################################################

import re

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

###############################################################################
# Main
###############################################################################


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

    module = HwcModule(
        argument_spec=dict(
            state=dict(default="present", choices=["present", "absent"], type="str"),
            timeouts=dict(
                type="dict",
                options=dict(
                    create=dict(default="15m", type="str"),
                    update=dict(default="15m", type="str"),
                    delete=dict(default="15m", type="str"),
                ),
                default=dict(),
            ),
            name=dict(required=True, type="str"),
            cidr=dict(required=True, type="str"),
        ),
        supports_check_mode=True,
    )
    config = Config(module, "vpc")

    state = module.params["state"]

    if (not module.params.get("id")) and module.params.get("name"):
        module.params["id"] = get_id_by_name(config)

    fetch = None
    link = self_link(module)
    # the link will include Nones if required format parameters are missed
    if not re.search("/None/|/None$", link):
        client = config.client(get_region(module), "vpc", "project")
        fetch = fetch_resource(module, client, link)
        if fetch:
            fetch = fetch.get("vpc")
    changed = False

    if fetch:
        if state == "present":
            expect = _get_editable_properties(module)
            current_state = response_to_hash(module, fetch)
            current = {"cidr": current_state["cidr"]}
            if are_different_dicts(expect, current):
                if not module.check_mode:
                    fetch = update(config, self_link(module))
                    fetch = response_to_hash(module, fetch.get("vpc"))
                changed = True
            else:
                fetch = current_state
        else:
            if not module.check_mode:
                delete(config, self_link(module))
                fetch = {}
            changed = True
    else:
        if state == "present":
            if not module.check_mode:
                fetch = create(config, "vpcs")
                fetch = response_to_hash(module, fetch.get("vpc"))
            changed = True
        else:
            fetch = {}

    fetch.update({"changed": changed})

    module.exit_json(**fetch)


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

    r = None
    try:
        r = client.post(link, resource_to_create(module))
    except HwcClientException as ex:
        msg = f"module(hwc_network_vpc): error creating resource, error: {ex}"
        module.fail_json(msg=msg)

    wait_done = wait_for_operation(config, "create", r)
    v = ""
    try:
        v = navigate_value(wait_done, ["vpc", "id"])
    except Exception as ex:
        module.fail_json(msg=str(ex))

    url = build_path(module, "vpcs/{op_id}", {"op_id": v})
    return fetch_resource(module, client, url)


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

    r = None
    try:
        r = client.put(link, resource_to_update(module))
    except HwcClientException as ex:
        msg = f"module(hwc_network_vpc): error updating resource, error: {ex}"
        module.fail_json(msg=msg)

    wait_for_operation(config, "update", r)

    return fetch_resource(module, client, link)


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

    try:
        client.delete(link)
    except HwcClientException as ex:
        msg = f"module(hwc_network_vpc): error deleting resource, error: {ex}"
        module.fail_json(msg=msg)

    wait_for_delete(module, client, link)


def fetch_resource(module, client, link):
    try:
        return client.get(link)
    except HwcClientException as ex:
        msg = f"module(hwc_network_vpc): error fetching resource, error: {ex}"
        module.fail_json(msg=msg)


def get_id_by_name(config):
    module = config.module
    client = config.client(get_region(module), "vpc", "project")
    name = module.params.get("name")
    link = "vpcs"
    query_link = "?marker={marker}&limit=10"
    link += query_link
    not_format_keys = re.findall("={marker}", link)
    none_values = re.findall("=None", link)

    if not (not_format_keys or none_values):
        r = None
        try:
            r = client.get(link)
        except Exception:
            pass
        if r is None:
            return None
        r = r.get("vpcs", [])
        ids = [i.get("id") for i in r if i.get("name", "") == name]
        if not ids:
            return None
        elif len(ids) == 1:
            return ids[0]
        else:
            module.fail_json(msg="Multiple resources with same name are found.")
    elif none_values:
        module.fail_json(msg="Can not find id by name because url includes None.")
    else:
        p = {"marker": ""}
        ids = set()
        while True:
            r = None
            try:
                r = client.get(link.format(**p))
            except Exception:
                pass
            if r is None:
                break
            r = r.get("vpcs", [])
            if r == []:
                break
            for i in r:
                if i.get("name") == name:
                    ids.add(i.get("id"))
            if len(ids) >= 2:
                module.fail_json(msg="Multiple resources with same name are found.")

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

        return ids.pop() if ids else None


def self_link(module):
    return build_path(module, "vpcs/{id}")


def resource_to_create(module):
    params = dict()

    v = module.params.get("cidr")
    if not is_empty_value(v):
        params["cidr"] = v

    v = module.params.get("name")
    if not is_empty_value(v):
        params["name"] = v

    if not params:
        return params

    params = {"vpc": params}

    return params


def resource_to_update(module):
    params = dict()

    v = module.params.get("cidr")
    if not is_empty_value(v):
        params["cidr"] = v

    if not params:
        return params

    params = {"vpc": params}

    return params


def _get_editable_properties(module):
    return {
        "cidr": module.params.get("cidr"),
    }


def response_to_hash(module, response):
    """Remove unnecessary properties from the response.
    This is for doing comparisons with Ansible's current parameters.
    """
    return {
        "id": response.get("id"),
        "name": response.get("name"),
        "cidr": response.get("cidr"),
        "status": response.get("status"),
        "routes": VpcRoutesArray(response.get("routes", []), module).from_response(),
        "enable_shared_snat": response.get("enable_shared_snat"),
    }


def wait_for_operation(config, op_type, op_result):
    module = config.module
    op_id = ""
    try:
        op_id = navigate_value(op_result, ["vpc", "id"])
    except Exception as ex:
        module.fail_json(msg=str(ex))

    url = build_path(module, "vpcs/{op_id}", {"op_id": op_id})
    timeout = 60 * int(module.params["timeouts"][op_type].rstrip("m"))
    states = {
        "create": {
            "allowed": ["CREATING", "DONW", "OK"],
            "complete": ["OK"],
        },
        "update": {
            "allowed": ["PENDING_UPDATE", "DONW", "OK"],
            "complete": ["OK"],
        },
    }

    return wait_for_completion(url, timeout, states[op_type]["allowed"], states[op_type]["complete"], config)


def wait_for_completion(op_uri, timeout, allowed_states, complete_states, config):
    module = config.module
    client = config.client(get_region(module), "vpc", "project")

    def _refresh_status():
        r = None
        try:
            r = fetch_resource(module, client, op_uri)
        except Exception:
            return None, ""

        status = ""
        try:
            status = navigate_value(r, ["vpc", "status"])
        except Exception:
            return None, ""

        return r, status

    try:
        return wait_to_finish(complete_states, allowed_states, _refresh_status, timeout)
    except Exception as ex:
        module.fail_json(msg=str(ex))


def wait_for_delete(module, client, link):
    def _refresh_status():
        try:
            client.get(link)
        except HwcClientException404:
            return True, "Done"

        except Exception:
            return None, ""

        return True, "Pending"

    timeout = 60 * int(module.params["timeouts"]["delete"].rstrip("m"))
    try:
        return wait_to_finish(["Done"], ["Pending"], _refresh_status, timeout)
    except Exception as ex:
        module.fail_json(msg=str(ex))


class VpcRoutesArray:
    def __init__(self, request, module):
        self.module = module
        if request:
            self.request = request
        else:
            self.request = []

    def to_request(self):
        items = []
        for item in self.request:
            items.append(self._request_for_item(item))
        return items

    def from_response(self):
        items = []
        for item in self.request:
            items.append(self._response_from_item(item))
        return items

    def _request_for_item(self, item):
        return {"destination": item.get("destination"), "nexthop": item.get("next_hop")}

    def _response_from_item(self, item):
        return {"destination": item.get("destination"), "next_hop": item.get("nexthop")}


if __name__ == "__main__":
    main()
