#!/usr/bin/python

# Copyright (c) 2017, Vitaliy Zhhuta <zhhuta () gmail.com>
# insipred by Kamil Szczygiel <kamil.szczygiel () intel.com> influxdb_database module
# 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: influxdb_user
short_description: Manage InfluxDB users
description:
  - Manage InfluxDB users.
author: "Vitaliy Zhhuta (@zhhuta)"
requirements:
  - "influxdb >= 0.9"
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  user_name:
    description:
      - Name of the user.
    required: true
    type: str
  user_password:
    description:
      - Password to be set for the user.
    type: str
  admin:
    description:
      - Whether the user should be in the admin role or not.
      - Since version 2.8, the role is also updated.
    default: false
    type: bool
  state:
    description:
      - State of the user.
    choices: [absent, present]
    default: present
    type: str
  grants:
    description:
      - Privileges to grant to this user.
      - Takes a list of dicts containing the "database" and "privilege" keys.
      - If this argument is not provided, the current grants are left alone.
      - If an empty list is provided, all grants for the user are removed.
    type: list
    elements: dict
extends_documentation_fragment:
  - community.general._influxdb
  - community.general._attributes
"""

EXAMPLES = r"""
- name: Create a user on localhost using default login credentials
  community.general.influxdb_user:
    user_name: john
    user_password: s3cr3t

- name: Create a user on localhost using custom login credentials
  community.general.influxdb_user:
    user_name: john
    user_password: s3cr3t
    login_username: "{{ influxdb_username }}"
    login_password: "{{ influxdb_password }}"

- name: Create an admin user on a remote host using custom login credentials
  community.general.influxdb_user:
    user_name: john
    user_password: s3cr3t
    admin: true
    hostname: "{{ influxdb_hostname }}"
    login_username: "{{ influxdb_username }}"
    login_password: "{{ influxdb_password }}"

- name: Create a user on localhost with privileges
  community.general.influxdb_user:
    user_name: john
    user_password: s3cr3t
    login_username: "{{ influxdb_username }}"
    login_password: "{{ influxdb_password }}"
    grants:
      - database: 'collectd'
        privilege: 'WRITE'
      - database: 'graphite'
        privilege: 'READ'

- name: Destroy a user using custom login credentials
  community.general.influxdb_user:
    user_name: john
    login_username: "{{ influxdb_username }}"
    login_password: "{{ influxdb_password }}"
    state: absent
"""

RETURN = r"""#"""

import json

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import ConnectionError

import ansible_collections.community.general.plugins.module_utils._influxdb as influx


def find_user(module, client, user_name):
    user_result = None

    try:
        users = client.get_list_users()
        for user in users:
            if user["user"] == user_name:
                user_result = user
                break
    except ConnectionError as e:
        module.fail_json(msg=f"{e}")
    return user_result


def check_user_password(module, client, user_name, user_password):
    try:
        client.switch_user(user_name, user_password)
        client.get_list_users()
    except influx.exceptions.InfluxDBClientError as e:
        if e.code == 401:
            return False
    except ConnectionError as e:
        module.fail_json(msg=f"{e}")
    finally:
        # restore previous user
        client.switch_user(module.params["username"], module.params["password"])
    return True


def set_user_password(module, client, user_name, user_password):
    if not module.check_mode:
        try:
            client.set_user_password(user_name, user_password)
        except ConnectionError as e:
            module.fail_json(msg=f"{e}")


def create_user(module, client, user_name, user_password, admin):
    if not module.check_mode:
        try:
            client.create_user(user_name, user_password, admin)
        except ConnectionError as e:
            module.fail_json(msg=f"{e}")


def drop_user(module, client, user_name):
    if not module.check_mode:
        try:
            client.drop_user(user_name)
        except influx.exceptions.InfluxDBClientError as e:
            module.fail_json(msg=e.content)

    module.exit_json(changed=True)


def set_user_grants(module, client, user_name, grants):
    changed = False

    current_grants = []
    try:
        current_grants = client.get_list_privileges(user_name)
    except influx.exceptions.InfluxDBClientError as e:
        if not module.check_mode or "user not found" not in e.content:
            module.fail_json(msg=e.content)

    try:
        parsed_grants = []
        # Fix privileges wording
        for v in current_grants:
            if v["privilege"] != "NO PRIVILEGES":
                if v["privilege"] == "ALL PRIVILEGES":
                    v["privilege"] = "ALL"
                parsed_grants.append(v)

        # check if the current grants are included in the desired ones
        for current_grant in parsed_grants:
            if current_grant not in grants:
                if not module.check_mode:
                    client.revoke_privilege(current_grant["privilege"], current_grant["database"], user_name)
                changed = True

        # check if the desired grants are included in the current ones
        for grant in grants:
            if grant not in parsed_grants:
                if not module.check_mode:
                    client.grant_privilege(grant["privilege"], grant["database"], user_name)
                changed = True

    except influx.exceptions.InfluxDBClientError as e:
        module.fail_json(msg=e.content)

    return changed


INFLUX_AUTH_FIRST_USER_REQUIRED = "error authorizing query: create admin user first or disable authentication"


def main():
    argument_spec = influx.InfluxDb.influxdb_argument_spec()
    argument_spec.update(
        state=dict(default="present", type="str", choices=["present", "absent"]),
        user_name=dict(required=True, type="str"),
        user_password=dict(type="str", no_log=True),
        admin=dict(default="False", type="bool"),
        grants=dict(type="list", elements="dict"),
    )
    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)

    state = module.params["state"]
    user_name = module.params["user_name"]
    user_password = module.params["user_password"]
    admin = module.params["admin"]
    grants = module.params["grants"]
    influxdb = influx.InfluxDb(module)
    client = influxdb.connect_to_influxdb()

    user = None
    try:
        user = find_user(module, client, user_name)
    except influx.exceptions.InfluxDBClientError as e:
        if e.code == 403:
            reason = None
            try:
                msg = json.loads(e.content)
                reason = msg["error"]
            except (KeyError, ValueError):
                module.fail_json(msg=f"{e}")

            if reason != INFLUX_AUTH_FIRST_USER_REQUIRED:
                module.fail_json(msg=f"{e}")
        else:
            module.fail_json(msg=f"{e}")

    changed = False

    if state == "present":
        if user:
            if not check_user_password(module, client, user_name, user_password) and user_password is not None:
                set_user_password(module, client, user_name, user_password)
                changed = True

            try:
                if admin and not user["admin"]:
                    if not module.check_mode:
                        client.grant_admin_privileges(user_name)
                    changed = True
                elif not admin and user["admin"]:
                    if not module.check_mode:
                        client.revoke_admin_privileges(user_name)
                    changed = True
            except influx.exceptions.InfluxDBClientError as e:
                module.fail_json(msg=f"{e}")

        else:
            user_password = user_password or ""
            create_user(module, client, user_name, user_password, admin)
            changed = True

        if grants is not None:
            if set_user_grants(module, client, user_name, grants):
                changed = True

        module.exit_json(changed=changed)

    if state == "absent":
        if user:
            drop_user(module, client, user_name)
        else:
            module.exit_json(changed=False)


if __name__ == "__main__":
    main()
