#!/usr/bin/python
# Copyright: Ansible Project
# 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 = """
module: restconf_config
author: Ganesh Nalawade (@ganeshrn)
short_description: Handles create, update, read and delete of configuration data on
  RESTCONF enabled devices.
description:
- RESTCONF is a standard mechanisms to allow web applications to configure and manage
  data. RESTCONF is a IETF standard and documented on RFC 8040.
- This module allows the user to configure data on RESTCONF enabled devices.
version_added: 1.0.0
options:
  path:
    description:
    - URI being used to execute API calls.
    required: true
    type: str
  content:
    description:
    - The configuration data in format as specififed in C(format) option. Required
      unless C(method) is I(delete).
    type: str
  method:
    description:
    - The RESTCONF method to manage the configuration change on device. The value
      I(post) is used to create a data resource or invoke an operation resource, I(put)
      is used to replace the target data resource, I(patch) is used to modify the
      target resource, and I(delete) is used to delete the target resource.
    required: false
    type: str
    default: post
    choices:
    - post
    - put
    - patch
    - delete
  format:
    description:
    - The format of the configuration provided as value of C(content). Accepted values
      are I(xml) and I(json) and the given configuration format should be supported
      by remote RESTCONF server.
    type: str
    default: json
    choices:
    - json
    - xml
notes:
- This module requires the RESTCONF system service be enabled on the remote device
  being managed.
- This module is supported with I(ansible_connection) value of I(ansible.netcommon.httpapi) and
  I(ansible_network_os) value of I(ansible.netcommon.restconf).
- This module is tested against Cisco IOSXE 16.12.02 version.
"""

EXAMPLES = """
- name: create l3vpn services
  ansible.netcommon.restconf_config:
    path: /config/ietf-l3vpn-svc:l3vpn-svc/vpn-services
    content: |
      {
        "vpn-service":[
                        {
                          "vpn-id": "red_vpn2",
                          "customer-name": "blue",
                          "vpn-service-topology": "ietf-l3vpn-svc:any-to-any"
                        },
                        {
                          "vpn-id": "blue_vpn1",
                          "customer-name": "red",
                          "vpn-service-topology": "ietf-l3vpn-svc:any-to-any"
                        }
                      ]
       }
"""

RETURN = """
candidate:
  description: The configuration sent to the device.
  returned: When the method is not delete
  type: dict
  sample: |
        {
            "vpn-service": [
                {
                    "customer-name": "red",
                    "vpn-id": "blue_vpn1",
                    "vpn-service-topology": "ietf-l3vpn-svc:any-to-any"
                }
            ]
        }
running:
  description: The current running configuration on the device.
  returned: When the method is not delete
  type: dict
  sample: |
        {
            "vpn-service": [
                {
                  "vpn-id": "red_vpn2",
                  "customer-name": "blue",
                  "vpn-service-topology": "ietf-l3vpn-svc:any-to-any"
                },
                {
                  "vpn-id": "blue_vpn1",
                  "customer-name": "red",
                  "vpn-service-topology": "ietf-l3vpn-svc:any-to-any"
                }
            ]
        }

"""

import json

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.connection import ConnectionError

from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
    dict_diff,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.restconf import restconf


string_types = (str,)


def main():
    """entry point for module execution"""
    argument_spec = dict(
        path=dict(required=True),
        content=dict(),
        method=dict(choices=["post", "put", "patch", "delete"], default="post"),
        format=dict(choices=["json", "xml"], default="json"),
    )
    required_if = [
        ["method", "post", ["content"]],
        ["method", "put", ["content"]],
        ["method", "patch", ["content"]],
    ]

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

    path = module.params["path"]
    candidate = module.params["content"]
    method = module.params["method"]
    format = module.params["format"]

    if isinstance(candidate, string_types):
        candidate = json.loads(candidate)

    warnings = list()
    result = {"changed": False, "warnings": warnings}

    running = None
    commit = not module.check_mode
    try:
        running = restconf.get(module, path, output=format)
    except ConnectionError as exc:
        if exc.code == 404:
            running = None
        else:
            module.fail_json(msg=to_text(exc), code=exc.code)

    try:
        if method == "delete":
            if running:
                if commit:
                    restconf.edit_config(module, path=path, method="DELETE")
                result["changed"] = True
            else:
                warnings.append("delete not executed as resource '%s' does not exist" % path)
        else:
            if running:
                diff = dict_diff(running, candidate)
                result["candidate"] = candidate
                result["running"] = running
            else:
                diff = candidate

            if diff:
                if module._diff:
                    result["diff"] = {
                        "prepared": diff,
                        "before": candidate,
                        "after": running,
                    }

                if commit:
                    restconf.edit_config(
                        module,
                        path=path,
                        content=diff,
                        method=method.upper(),
                        format=format,
                    )
                result["changed"] = True

    except ConnectionError as exc:
        module.fail_json(msg=str(exc), code=exc.code)

    module.exit_json(**result)


if __name__ == "__main__":
    main()
