#!/usr/bin/python
#
# Copyright (c) 2018, Simon Weald <ansible@simonweald.com>
# 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 = r"""
module: memset_zone_domain
author: "Simon Weald (@glitchcrab)"
short_description: Create and delete domains in Memset DNS zones
notes:
  - Zone domains can be thought of as a collection of domains, all of which share the same DNS records (in other words, they
    point to the same IP). An API key generated using the Memset customer control panel is needed with the following minimum
    scope - C(dns.zone_domain_create), C(dns.zone_domain_delete), C(dns.zone_domain_list).
  - Currently this module can only create one domain at a time. Multiple domains should be created using C(loop).
description:
  - Manage DNS zone domains in a Memset account.
extends_documentation_fragment:
  - community.general._attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  state:
    default: present
    description:
      - Indicates desired state of resource.
    type: str
    choices: [absent, present]
  api_key:
    required: true
    description:
      - The API key obtained from the Memset control panel.
    type: str
  domain:
    required: true
    description:
      - The zone domain name. Ensure this value has at most 250 characters.
    type: str
    aliases: ['name']
  zone:
    required: true
    description:
      - The zone to add the domain to (this must already exist).
    type: str
"""

EXAMPLES = r"""
# Create the zone domain 'test.com'
- name: Create zone domain
  community.general.memset_zone_domain:
    domain: test.com
    zone: testzone
    state: present
    api_key: 5eb86c9196ab03919abcf03857163741
  delegate_to: localhost
"""

RETURN = r"""
memset_api:
  description: Domain info from the Memset API.
  returned: when changed or state == present
  type: complex
  contains:
    domain:
      description: Domain name.
      returned: always
      type: str
      sample: "example.com"
    id:
      description: Domain ID.
      returned: always
      type: str
      sample: "b0bb1ce851aeea6feeb2dc32fe83bf9c"
"""

from ansible.module_utils.basic import AnsibleModule

from ansible_collections.community.general.plugins.module_utils._memset import (
    check_zone_domain,
    get_zone_id,
    memset_api_call,
)


def api_validation(args=None):
    """
    Perform some validation which will be enforced by Memset's API (see:
    https://www.memset.com/apidocs/methods_dns.html#dns.zone_domain_create)
    """
    # zone domain length must be less than 250 chars
    if len(args["domain"]) > 250:
        stderr = "Zone domain must be less than 250 characters in length."
        module.fail_json(failed=True, msg=stderr)


def check(args=None):
    """
    Support for running with check mode.
    """
    retvals = dict()
    has_changed = False

    api_method = "dns.zone_domain_list"
    has_failed, msg, response = memset_api_call(api_key=args["api_key"], api_method=api_method)

    domain_exists = check_zone_domain(data=response, domain=args["domain"])

    # set changed to true if the operation would cause a change.
    has_changed = (domain_exists and args["state"] == "absent") or (not domain_exists and args["state"] == "present")

    retvals["changed"] = has_changed
    retvals["failed"] = has_failed

    return retvals


def create_zone_domain(args=None, zone_exists=None, zone_id=None, payload=None):
    """
    At this point we already know whether the containing zone exists,
    so we just need to create the domain (or exit if it already exists).
    """
    has_changed, has_failed = False, False
    msg = None

    api_method = "dns.zone_domain_list"
    _has_failed, _msg, response = memset_api_call(api_key=args["api_key"], api_method=api_method)

    for zone_domain in response.json():
        if zone_domain["domain"] == args["domain"]:
            # zone domain already exists, nothing to change.
            has_changed = False
            break
    else:
        # we need to create the domain
        api_method = "dns.zone_domain_create"
        payload["domain"] = args["domain"]
        payload["zone_id"] = zone_id
        has_failed, msg, response = memset_api_call(api_key=args["api_key"], api_method=api_method, payload=payload)
        if not has_failed:
            has_changed = True

    return has_failed, has_changed, msg


def delete_zone_domain(args=None, payload=None):
    """
    Deletion is pretty simple, domains are always unique so we
    we don't need to do any sanity checking to avoid deleting the
    wrong thing.
    """
    has_changed, has_failed = False, False
    msg, memset_api = None, None

    api_method = "dns.zone_domain_list"
    _has_failed, _msg, response = memset_api_call(api_key=args["api_key"], api_method=api_method)

    domain_exists = check_zone_domain(data=response, domain=args["domain"])

    if domain_exists:
        api_method = "dns.zone_domain_delete"
        payload["domain"] = args["domain"]
        has_failed, msg, response = memset_api_call(api_key=args["api_key"], api_method=api_method, payload=payload)
        if not has_failed:
            has_changed = True
            memset_api = response.json()
            # unset msg as we don't want to return unnecessary info to the user.
            msg = None

    return has_failed, has_changed, memset_api, msg


def create_or_delete_domain(args=None):
    """
    We need to perform some initial sanity checking and also look
    up required info before handing it off to create or delete.
    """
    retvals, payload = dict(), dict()
    has_changed, has_failed = False, False
    msg, stderr, memset_api = None, None, None

    # get the zones and check if the relevant zone exists.
    api_method = "dns.zone_list"
    has_failed, msg, response = memset_api_call(api_key=args["api_key"], api_method=api_method)

    if has_failed:
        # this is the first time the API is called; incorrect credentials will
        # manifest themselves at this point so we need to ensure the user is
        # informed of the reason.
        retvals["failed"] = has_failed
        retvals["msg"] = msg
        if response.status_code is not None:
            retvals["stderr"] = f"API returned an error: {response.status_code}"
        else:
            retvals["stderr"] = response.stderr
        return retvals

    zone_exists, msg, counter, zone_id = get_zone_id(zone_name=args["zone"], current_zones=response.json())

    if not zone_exists:
        # the zone needs to be unique - this isn't a requirement of Memset's API but it
        # makes sense in the context of this module.
        has_failed = True
        if counter == 0:
            stderr = f"DNS zone '{args['zone']}' does not exist, cannot create domain."
        elif counter > 1:
            stderr = f"{args['zone']} matches multiple zones, cannot create domain."

        retvals["failed"] = has_failed
        retvals["msg"] = stderr
        return retvals

    if args["state"] == "present":
        has_failed, has_changed, msg = create_zone_domain(
            args=args, zone_exists=zone_exists, zone_id=zone_id, payload=payload
        )

    if args["state"] == "absent":
        has_failed, has_changed, memset_api, msg = delete_zone_domain(args=args, payload=payload)

    retvals["changed"] = has_changed
    retvals["failed"] = has_failed
    for val in ["msg", "stderr", "memset_api"]:
        if val is not None:
            retvals[val] = eval(val)

    return retvals


def main():
    global module
    module = AnsibleModule(
        argument_spec=dict(
            state=dict(default="present", choices=["present", "absent"], type="str"),
            api_key=dict(required=True, type="str", no_log=True),
            domain=dict(required=True, aliases=["name"], type="str"),
            zone=dict(required=True, type="str"),
        ),
        supports_check_mode=True,
    )

    # populate the dict with the user-provided vars.
    args = dict(module.params)
    args["check_mode"] = module.check_mode

    # validate some API-specific limitations.
    api_validation(args=args)

    if module.check_mode:
        retvals = check(args)
    else:
        retvals = create_or_delete_domain(args)

    # we would need to populate the return values with the API's response
    # in several places so it is easier to do it at the end instead.
    if not retvals["failed"]:
        if args["state"] == "present" and not module.check_mode:
            payload = dict()
            payload["domain"] = args["domain"]
            api_method = "dns.zone_domain_info"
            _has_failed, _msg, response = memset_api_call(
                api_key=args["api_key"], api_method=api_method, payload=payload
            )
            retvals["memset_api"] = response.json()

    if retvals["failed"]:
        module.fail_json(**retvals)
    else:
        module.exit_json(**retvals)


if __name__ == "__main__":
    main()
