#!/usr/bin/python

# Copyright (c) 2018 Nicolai Buchwitz <nb@tipi-net.de>
# 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: netcup_dns
notes: []
short_description: Manage Netcup DNS records
description:
  - Manages DNS records using the Netcup API, see the docs U(https://ccp.netcup.net/run/webservice/servers/endpoint.php).
extends_documentation_fragment:
  - community.general.attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: full
    version_added: 12.3.0
options:
  api_key:
    description:
      - API key for authentication, must be obtained using the netcup CCP (U(https://ccp.netcup.net)).
    required: true
    type: str
  api_password:
    description:
      - API password for authentication, must be obtained using the netcup CCP (U(https://ccp.netcup.net)).
    required: true
    type: str
  customer_id:
    description:
      - Netcup customer ID.
    required: true
    type: int
  domain:
    description:
      - Domainname the records should be added / removed.
    required: true
    type: str
  record:
    description:
      - Record to add or delete, supports wildcard (V(*)). Default is V(@) (that is, the zone name).
    default: "@"
    aliases: [name]
    type: str
  type:
    description:
      - Record type.
      - Support for V(OPENPGPKEY), V(SMIMEA) and V(SSHFP) was added in community.general 8.1.0.
      - Record types V(OPENPGPKEY) and V(SMIMEA) require nc-dnsapi >= 0.1.5.
      - Record type V(SSHFP) requires nc-dnsapi >= 0.1.6.
    choices: ['A', 'AAAA', 'MX', 'CNAME', 'CAA', 'SRV', 'TXT', 'TLSA', 'NS', 'DS', 'OPENPGPKEY', 'SMIMEA', 'SSHFP']
    required: true
    type: str
  value:
    description:
      - Record value.
    required: true
    type: str
  solo:
    type: bool
    default: false
    description:
      - Whether the record should be the only one for that record type and record name. Only use with O(state=present).
      - This deletes all other records with the same record name and type.
  priority:
    description:
      - Record priority. Required for O(type=MX).
    type: int
  state:
    description:
      - Whether the record should exist or not.
    default: present
    choices: ['present', 'absent']
    type: str
  timeout:
    description:
      - HTTP(S) connection timeout in seconds.
    default: 5
    type: int
    version_added: 5.7.0
requirements:
  - "nc-dnsapi >= 0.1.3"
author: "Nicolai Buchwitz (@nbuchwitz)"
"""

EXAMPLES = r"""
- name: Create a record of type A
  community.general.netcup_dns:
    api_key: "..."
    api_password: "..."
    customer_id: "..."
    domain: "example.com"
    name: "mail"
    type: "A"
    value: "127.0.0.1"

- name: Delete that record
  community.general.netcup_dns:
    api_key: "..."
    api_password: "..."
    customer_id: "..."
    domain: "example.com"
    name: "mail"
    type: "A"
    value: "127.0.0.1"
    state: absent

- name: Create a wildcard record
  community.general.netcup_dns:
    api_key: "..."
    api_password: "..."
    customer_id: "..."
    domain: "example.com"
    name: "*"
    type: "A"
    value: "127.0.1.1"

- name: Set the MX record for example.com
  community.general.netcup_dns:
    api_key: "..."
    api_password: "..."
    customer_id: "..."
    domain: "example.com"
    type: "MX"
    value: "mail.example.com"

- name: Set a record and ensure that this is the only one
  community.general.netcup_dns:
    api_key: "..."
    api_password: "..."
    customer_id: "..."
    name: "demo"
    domain: "example.com"
    type: "AAAA"
    value: "::1"
    solo: true

- name: Increase the connection timeout to avoid problems with an unstable connection
  community.general.netcup_dns:
    api_key: "..."
    api_password: "..."
    customer_id: "..."
    domain: "example.com"
    name: "mail"
    type: "A"
    value: "127.0.0.1"
    timeout: 30
"""

RETURN = r"""
records:
  description: List containing all records.
  returned: success
  type: list
  elements: dict
  contains:
    name:
      description: The record name.
      returned: success
      type: str
      sample: fancy-hostname
    type:
      description: The record type.
      returned: success
      type: str
      sample: A
    value:
      description: The record destination.
      returned: success
      type: str
      sample: 127.0.0.1
    priority:
      description: The record priority (only relevant if RV(records[].type=MX)).
      returned: success
      type: int
      sample: 0
    id:
      description: Internal ID of the record.
      returned: success
      type: int
      sample: 12345
"""

import traceback

from ansible.module_utils.basic import AnsibleModule, missing_required_lib

NCDNSAPI_IMP_ERR = None
try:
    import nc_dnsapi
    from nc_dnsapi import DNSRecord

    HAS_NCDNSAPI = True
except ImportError:
    NCDNSAPI_IMP_ERR = traceback.format_exc()
    HAS_NCDNSAPI = False


def main():
    module = AnsibleModule(
        argument_spec=dict(
            api_key=dict(required=True, no_log=True),
            api_password=dict(required=True, no_log=True),
            customer_id=dict(required=True, type="int"),
            domain=dict(required=True),
            record=dict(default="@", aliases=["name"]),
            type=dict(
                required=True,
                choices=[
                    "A",
                    "AAAA",
                    "MX",
                    "CNAME",
                    "CAA",
                    "SRV",
                    "TXT",
                    "TLSA",
                    "NS",
                    "DS",
                    "OPENPGPKEY",
                    "SMIMEA",
                    "SSHFP",
                ],
            ),
            value=dict(required=True),
            priority=dict(type="int"),
            solo=dict(type="bool", default=False),
            state=dict(choices=["present", "absent"], default="present"),
            timeout=dict(type="int", default=5),
        ),
        supports_check_mode=True,
    )

    if not HAS_NCDNSAPI:
        module.fail_json(msg=missing_required_lib("nc-dnsapi"), exception=NCDNSAPI_IMP_ERR)

    api_key = module.params.get("api_key")
    api_password = module.params.get("api_password")
    customer_id = module.params.get("customer_id")
    domain = module.params.get("domain")
    record_type = module.params.get("type")
    record = module.params.get("record")
    value = module.params.get("value")
    priority = module.params.get("priority")
    solo = module.params.get("solo")
    state = module.params.get("state")
    timeout = module.params.get("timeout")

    if record_type == "MX" and not priority:
        module.fail_json(msg="record type MX required the 'priority' argument")

    has_changed = False

    diff_mode = module._diff
    diff = None

    all_records = []
    try:
        with nc_dnsapi.Client(customer_id, api_key, api_password, timeout) as api:
            all_records = api.dns_records(domain)
            record = DNSRecord(record, record_type, value, priority=priority)

            if diff_mode:
                diff_after = all_records.copy()
            # try to get existing record
            record_exists = False
            for r in all_records:
                if r == record:
                    record_exists = True
                    record = r

                    break

            if state == "present":
                if solo:
                    obsolete_records = [
                        r
                        for r in all_records
                        if r.hostname == record.hostname
                        and r.type == record.type
                        and r.destination != record.destination
                    ]

                    if obsolete_records:
                        if not module.check_mode:
                            all_records = api.delete_dns_records(domain, obsolete_records)

                        if diff_mode:
                            diff_after = [r for r in diff_after if r not in obsolete_records]
                        has_changed = True

                if not record_exists:
                    if not module.check_mode:
                        all_records = api.add_dns_record(domain, record)

                    if diff_mode:
                        diff_after.append(record)
                    has_changed = True
            elif state == "absent" and record_exists:
                if not module.check_mode:
                    all_records = api.delete_dns_record(domain, record)

                if diff_mode:
                    diff_after.remove(record)
                has_changed = True
        if diff_mode:
            diff = dict(before=into_diffable(all_records, domain), after=into_diffable(diff_after, domain))

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

    module.exit_json(changed=has_changed, diff=diff, result={"records": [record_data(r) for r in all_records]})


def record_data(r):
    return {"name": r.hostname, "type": r.type, "value": r.destination, "priority": r.priority, "id": r.id}


def into_diffable(records, domain):
    return {domain: [record_data(r) for r in records]}


if __name__ == "__main__":
    main()
