#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright (c) 2024, Michael Ilg
# GNU General Public License v3.0+ (see COPYING 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: consul_agent_service
short_description: Add, modify and delete services within a consul cluster
version_added: 9.1.0
description:
 - Allows the addition, modification and deletion of services in a consul
   cluster via the agent.
 - There are currently no plans to create services and checks in one.
   This is because the Consul API does not provide checks for a service and
   the checks themselves do not match the module parameters.
   Therefore, only a service without checks can be created in this module.
author:
  - Michael Ilg (@Ilgmi)
extends_documentation_fragment:
  - community.general.consul
  - community.general.consul.actiongroup_consul
  - community.general.consul.token
  - community.general.attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: partial
    details:
      - In check mode the diff will miss operational attributes.
options:
  state:
    description:
      - Whether the service should be present or absent.
    choices: ['present', 'absent']
    default: present
    type: str
  name:
    description:
      - Unique name for the service on a node, must be unique per node,
        required if registering a service.
    type: str
  id:
    description:
      - Specifies a unique ID for this service. This must be unique per agent. This defaults to the O(name) parameter if not provided.
        If O(state=absent), defaults to the service name if supplied.
    type: str
  tags:
    description:
      - Tags that will be attached to the service registration.
    type: list
    elements: str
  address:
    description:
      - The address to advertise that the service will be listening on.
        This value will be passed as the C(address) parameter to Consul's
        C(/v1/agent/service/register) API method, so refer to the Consul API
        documentation for further details.
    type: str
  meta:
    description:
      - Optional meta data used for filtering.
        For keys, the characters C(A-Z), C(a-z), C(0-9), C(_), C(-) are allowed.
        Not allowed characters are replaced with underscores.
    type: dict
  service_port:
    description:
      - The port on which the service is listening. Can optionally be supplied for
        registration of a service, that is if O(name) or O(id) is set.
    type: int
  enable_tag_override:
    description:
      - Specifies to disable the anti-entropy feature for this service's tags.
        If EnableTagOverride is set to true then external agents can update this service in the catalog and modify the tags.
    type: bool
    default: False
  weights:
    description:
      - Specifies weights for the service
    type: dict
    suboptions:
      passing:
        description:
        - Weights for passing.
        type: int
        default: 1
      warning:
        description:
          - Weights for warning.
        type: int
        default: 1
    default: {"passing": 1, "warning": 1}
'''

EXAMPLES = '''
- name: Register nginx service with the local consul agent
  community.general.consul_agent_service:
    host: consul1.example.com
    token: some_management_acl
    name: nginx
    service_port: 80

- name: Register nginx with a tcp check
  community.general.consul_agent_service:
    host: consul1.example.com
    token: some_management_acl
    name: nginx
    service_port: 80

- name: Register nginx with an http check
  community.general.consul_agent_service:
    host: consul1.example.com
    token: some_management_acl
    name: nginx
    service_port: 80

- name: Register external service nginx available at 10.1.5.23
  community.general.consul_agent_service:
    host: consul1.example.com
    token: some_management_acl
    name: nginx
    service_port: 80
    address: 10.1.5.23

- name: Register nginx with some service tags
  community.general.consul_agent_service:
    host: consul1.example.com
    token: some_management_acl
    name: nginx
    service_port: 80
    tags:
      - prod
      - webservers

- name: Register nginx with some service meta
  community.general.consul_agent_service:
    host: consul1.example.com
    token: some_management_acl
    name: nginx
    service_port: 80
    meta:
      nginx_version: 1.25.3

- name: Remove nginx service
  community.general.consul_agent_service:
    host: consul1.example.com
    token: some_management_acl
    service_id: nginx
    state: absent

- name: Register celery worker service
  community.general.consul_agent_service:
    host: consul1.example.com
    token: some_management_acl
    name: celery-worker
    tags:
      - prod
      - worker
'''

RETURN = """
service:
    description: The service as returned by the consul HTTP API.
    returned: always
    type: dict
    sample:
        ID: nginx
        Service: nginx
        Address: localhost
        Port: 80
        Tags:
            - http
        Meta:
            - nginx_version: 1.23.3
        Datacenter: dc1
        Weights:
            Passing: 1
            Warning: 1
        ContentHash: 61a245cd985261ac
        EnableTagOverride: false
operation:
    description: The operation performed.
    returned: changed
    type: str
    sample: update
"""

from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.consul import (
    AUTH_ARGUMENTS_SPEC,
    OPERATION_CREATE,
    OPERATION_UPDATE,
    OPERATION_DELETE,
    _ConsulModule
)

_CHECK_MUTUALLY_EXCLUSIVE = [('args', 'ttl', 'tcp', 'http')]
_CHECK_REQUIRED_BY = {
    'args': 'interval',
    'http': 'interval',
    'tcp': 'interval',
}

_ARGUMENT_SPEC = {
    "state": dict(default="present", choices=["present", "absent"]),
    "name": dict(type='str'),
    "id": dict(type='str'),
    "tags": dict(type='list', elements='str'),
    "address": dict(type='str'),
    "meta": dict(type='dict'),
    "service_port": dict(type='int'),
    "enable_tag_override": dict(type='bool', default=False),
    "weights": dict(type='dict', options=dict(
        passing=dict(type='int', default=1, no_log=False),
        warning=dict(type='int', default=1)
    ), default={"passing": 1, "warning": 1})
}

_REQUIRED_IF = [
    ('state', 'present', ['name']),
    ('state', 'absent', ('id', 'name'), True),
]

_ARGUMENT_SPEC.update(AUTH_ARGUMENTS_SPEC)


class ConsulAgentServiceModule(_ConsulModule):
    api_endpoint = "agent/service"
    result_key = "service"
    unique_identifiers = ["id", "name"]
    operational_attributes = {"Service", "ContentHash", "Datacenter"}

    def endpoint_url(self, operation, identifier=None):
        if operation in [OPERATION_CREATE, OPERATION_UPDATE]:
            return "/".join([self.api_endpoint, "register"])
        if operation == OPERATION_DELETE:
            return "/".join([self.api_endpoint, "deregister", identifier])

        return super(ConsulAgentServiceModule, self).endpoint_url(operation, identifier)

    def prepare_object(self, existing, obj):
        existing = super(ConsulAgentServiceModule, self).prepare_object(existing, obj)
        if "ServicePort" in existing:
            existing["Port"] = existing.pop("ServicePort")

        if "ID" not in existing:
            existing["ID"] = existing["Name"]

        return existing

    def needs_update(self, api_obj, module_obj):
        obj = {}
        if "Service" in api_obj:
            obj["Service"] = api_obj["Service"]
        api_obj = self.prepare_object(api_obj, obj)

        if "Name" in module_obj:
            module_obj["Service"] = module_obj.pop("Name")
        if "ServicePort" in module_obj:
            module_obj["Port"] = module_obj.pop("ServicePort")

        return super(ConsulAgentServiceModule, self).needs_update(api_obj, module_obj)

    def delete_object(self, obj):
        if not self._module.check_mode:
            url = self.endpoint_url(OPERATION_DELETE, self.id_from_obj(obj, camel_case=True))
            self.put(url)
        return {}


def main():
    module = AnsibleModule(
        _ARGUMENT_SPEC,
        required_if=_REQUIRED_IF,
        supports_check_mode=True,
    )

    consul_module = ConsulAgentServiceModule(module)
    consul_module.execute()


if __name__ == "__main__":
    main()
