#!/usr/bin/python

# Copyright (c) 2023, Salvatore Mesoraca <s.mesoraca16@gmail.com>
# 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 annotations

DOCUMENTATION = r"""
module: kdeconfig
short_description: Manage KDE configuration files
version_added: "6.5.0"
description:
  - Add or change individual settings in KDE configuration files.
  - It uses B(kwriteconfig) under the hood.
options:
  path:
    description:
      - Path to the config file. If the file does not exist it is created.
    type: path
    required: true
  kwriteconfig_path:
    description:
      - Path to the kwriteconfig executable. If not specified, Ansible tries to discover it.
    type: path
  values:
    description:
      - List of values to set.
    type: list
    elements: dict
    suboptions:
      group:
        description:
          - The option's group. One between this and O(values[].groups) is required.
        type: str
      groups:
        description:
          - List of the option's groups. One between this and O(values[].group) is required.
        type: list
        elements: str
      key:
        description:
          - The option's name.
        type: str
        required: true
      value:
        description:
          - The option's value. One between this and O(values[].bool_value) is required.
        type: str
      bool_value:
        description:
          - Boolean value.
          - One between this and O(values[].value) is required.
        type: bool
    required: true
  backup:
    description:
      - Create a backup file.
    type: bool
    default: false
extends_documentation_fragment:
  - ansible.builtin.files
  - community.general._attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: full
requirements:
  - kwriteconfig
author:
  - Salvatore Mesoraca (@smeso)
"""

EXAMPLES = r"""
- name: Ensure "Homepage=https://www.ansible.com/" in group "Branding"
  community.general.kdeconfig:
    path: /etc/xdg/kickoffrc
    values:
      - group: Branding
        key: Homepage
        value: https://www.ansible.com/
    mode: '0644'

- name: Ensure "KEY=true" in groups "Group" and "Subgroup", and "KEY=VALUE" in Group2
  community.general.kdeconfig:
    path: /etc/xdg/someconfigrc
    values:
      - groups: [Group, Subgroup]
        key: KEY
        bool_value: true
      - group: Group2
        key: KEY
        value: VALUE
    backup: true
"""

RETURN = r""" # """

import os
import shutil
import tempfile
import traceback

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_bytes, to_text


class TemporaryDirectory:
    """Basic backport of tempfile.TemporaryDirectory"""

    def __init__(self, suffix="", prefix="tmp", dir=None):
        self.name = None
        self.name = tempfile.mkdtemp(suffix, prefix, dir)

    def __enter__(self):
        return self.name

    def rm(self):
        if self.name:
            shutil.rmtree(self.name, ignore_errors=True)
            self.name = None

    def __exit__(self, exc, value, tb):
        self.rm()

    def __del__(self):
        self.rm()


def run_kwriteconfig(module, cmd, path, groups, key, value):
    """Invoke kwriteconfig with arguments"""
    args = [cmd, "--file", path, "--key", key]
    for group in groups:
        args.extend(["--group", group])
    if isinstance(value, bool):
        args.extend(["--type", "bool"])
        if value:
            args.append("true")
        else:
            args.append("false")
    else:
        args.extend(["--", value])
    module.run_command(args, check_rc=True)


def run_module(module, tmpdir, kwriteconfig):
    result = dict(changed=False, msg="OK", path=module.params["path"])
    b_path = to_bytes(module.params["path"])
    tmpfile = os.path.join(tmpdir, "file")
    b_tmpfile = to_bytes(tmpfile)
    diff = dict(
        before="",
        after="",
        before_header=result["path"],
        after_header=result["path"],
    )
    try:
        with open(b_tmpfile, "wb") as dst:
            try:
                with open(b_path, "rb") as src:
                    b_data = src.read()
            except OSError:
                result["changed"] = True
            else:
                dst.write(b_data)
                try:
                    diff["before"] = to_text(b_data)
                except UnicodeError:
                    diff["before"] = repr(b_data)
    except OSError:
        module.fail_json(msg="Unable to create temporary file", traceback=traceback.format_exc())

    for row in module.params["values"]:
        groups = row["groups"]
        if groups is None:
            groups = [row["group"]]
        key = row["key"]
        value = row["bool_value"]
        if value is None:
            value = row["value"]
        run_kwriteconfig(module, kwriteconfig, tmpfile, groups, key, value)

    with open(b_tmpfile, "rb") as tmpf:
        b_data = tmpf.read()
        try:
            diff["after"] = to_text(b_data)
        except UnicodeError:
            diff["after"] = repr(b_data)

    result["changed"] = result["changed"] or diff["after"] != diff["before"]

    file_args = module.load_file_common_arguments(module.params)

    if module.check_mode:
        if not result["changed"]:
            shutil.copystat(b_path, b_tmpfile)
            uid, gid = module.user_and_group(b_path)
            os.chown(b_tmpfile, uid, gid)
            if module._diff:
                diff = {}
            else:
                diff = None
            result["changed"] = module.set_fs_attributes_if_different(file_args, result["changed"], diff=diff)
        if module._diff:
            result["diff"] = diff
        module.exit_json(**result)

    if result["changed"]:
        if module.params["backup"] and os.path.exists(b_path):
            result["backup_file"] = module.backup_local(result["path"])
        try:
            module.atomic_move(b_tmpfile, os.path.abspath(b_path))
        except OSError:
            module.ansible.fail_json(
                msg=f"Unable to move temporary file {tmpfile} to {result['path']}, IOError",
                traceback=traceback.format_exc(),
            )

    if result["changed"]:
        module.set_fs_attributes_if_different(file_args, result["changed"])
    else:
        if module._diff:
            diff = {}
        else:
            diff = None
        result["changed"] = module.set_fs_attributes_if_different(file_args, result["changed"], diff=diff)
    if module._diff:
        result["diff"] = diff
    module.exit_json(**result)


def main():
    single_value_arg = dict(
        group=dict(type="str"),
        groups=dict(type="list", elements="str"),
        key=dict(type="str", required=True, no_log=False),
        value=dict(type="str"),
        bool_value=dict(type="bool"),
    )
    required_alternatives = [("group", "groups"), ("value", "bool_value")]
    module_args = dict(
        values=dict(
            type="list",
            elements="dict",
            options=single_value_arg,
            mutually_exclusive=required_alternatives,
            required_one_of=required_alternatives,
            required=True,
        ),
        path=dict(type="path", required=True),
        kwriteconfig_path=dict(type="path"),
        backup=dict(type="bool", default=False),
    )

    module = AnsibleModule(
        argument_spec=module_args,
        add_file_common_args=True,
        supports_check_mode=True,
    )

    kwriteconfig = None
    if module.params["kwriteconfig_path"] is not None:
        kwriteconfig = module.get_bin_path(module.params["kwriteconfig_path"], required=True)
    else:
        for progname in ("kwriteconfig6", "kwriteconfig5", "kwriteconfig", "kwriteconfig4"):
            kwriteconfig = module.get_bin_path(progname)
            if kwriteconfig is not None:
                break
        if kwriteconfig is None:
            module.fail_json(msg="kwriteconfig is not installed")
    for v in module.params["values"]:
        if not v["key"]:
            module.fail_json(msg="'key' cannot be empty")
    with TemporaryDirectory(dir=module.tmpdir) as tmpdir:
        run_module(module, tmpdir, kwriteconfig)


if __name__ == "__main__":
    main()
