#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# 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 absolute_import, division, print_function
__metaclass__ = type

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

DOCUMENTATION = '''
---
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
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
        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

'''

EXAMPLES = '''
- 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 = '''
    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 will provide VPC peering connection ID.
                type: str
                returned: success
    enable_shared_snat:
        description:
            - show whether the shared snat is enabled.
        type: bool
        returned: success
'''

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

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

###############################################################################
# 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 = ("module(hwc_network_vpc): error creating "
               "resource, error: %s" % str(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 = ("module(hwc_network_vpc): error updating "
               "resource, error: %s" % str(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 = ("module(hwc_network_vpc): error deleting "
               "resource, error: %s" % str(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 = ("module(hwc_network_vpc): error fetching "
               "resource, error: %s" % str(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 {
        u'id': response.get(u'id'),
        u'name': response.get(u'name'),
        u'cidr': response.get(u'cidr'),
        u'status': response.get(u'status'),
        u'routes': VpcRoutesArray(
            response.get(u'routes', []), module).from_response(),
        u'enable_shared_snat': response.get(u'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(object):
    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 {
            u'destination': item.get('destination'),
            u'nexthop': item.get('next_hop')
        }

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


if __name__ == '__main__':
    main()
