#!/usr/bin/python
# Copyright (c) 2016, Andrew Gaffney <andrew@agaffney.org>
# 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: openwrt_init
author:
  - "Andrew Gaffney (@agaffney)"
short_description: Manage services on OpenWrt
description:
  - Controls OpenWrt services on remote hosts.
extends_documentation_fragment:
  - community.general._attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  name:
    type: str
    description:
      - Name of the service.
    required: true
    aliases: ['service']
  state:
    type: str
    description:
      - V(started)/V(stopped) are idempotent actions that do not run commands unless necessary.
      - V(restarted) always bounces the service.
      - V(reloaded) always reloads.
    choices: ['started', 'stopped', 'restarted', 'reloaded']
  enabled:
    description:
      - Whether the service should start on boot. B(At least one) of O(state) and O(enabled) are required.
    type: bool
  pattern:
    type: str
    description:
      - If the service does not respond to the C(running) command, name a substring to look for as would be found in the output
        of the C(ps) command as a stand-in for a C(running) result. If the string is found, the service is assumed to be running.
notes:
  - One option other than O(name) is required.
requirements:
  - An OpenWrt system (with python)
"""

EXAMPLES = r"""
- name: Start service httpd, if not running
  community.general.openwrt_init:
    state: started
    name: httpd

- name: Stop service cron, if running
  community.general.openwrt_init:
    name: cron
    state: stopped

- name: Reload service httpd, in all cases
  community.general.openwrt_init:
    name: httpd
    state: reloaded

- name: Enable service httpd
  community.general.openwrt_init:
    name: httpd
    enabled: true
"""

RETURN = r"""
"""

import os

from ansible.module_utils.basic import AnsibleModule

module = None
init_script = None


# ===============================
# Check if service is enabled
def is_enabled():
    rc, dummy, dummy = module.run_command([init_script, "enabled"])
    return rc == 0


# ===========================================
# Main control flow
def main():
    global module, init_script
    # init
    module = AnsibleModule(
        argument_spec=dict(
            name=dict(required=True, type="str", aliases=["service"]),
            state=dict(type="str", choices=["started", "stopped", "restarted", "reloaded"]),
            enabled=dict(type="bool"),
            pattern=dict(type="str"),
        ),
        supports_check_mode=True,
        required_one_of=[("state", "enabled")],
    )
    module.run_command_environ_update = {"LANGUAGE": "C", "LC_ALL": "C"}

    # initialize
    service = module.params["name"]
    init_script = f"/etc/init.d/{service}"
    result = {
        "name": service,
        "changed": False,
    }
    # check if service exists
    if not os.path.exists(init_script):
        module.fail_json(msg=f"service {service} does not exist")

    # Enable/disable service startup at boot if requested
    if module.params["enabled"] is not None:
        # do we need to enable the service?
        enabled = is_enabled()

        # default to current state
        result["enabled"] = enabled

        # Change enable/disable if needed
        if enabled != module.params["enabled"]:
            result["changed"] = True
            action = "enable" if module.params["enabled"] else "disable"

            if not module.check_mode:
                rc, dummy, err = module.run_command([init_script, action])
                # openwrt init scripts can return a non-zero exit code on a successful 'enable'
                # command if the init script doesn't contain a STOP value, so we ignore the exit
                # code and explicitly check if the service is now in the desired state
                if is_enabled() != module.params["enabled"]:
                    module.fail_json(msg=f"Unable to {action} service {service}: {err}")

            result["enabled"] = not enabled

    if module.params["state"] is not None:
        running = False

        # check if service is currently running
        if module.params["pattern"]:
            # Find ps binary
            psbin = module.get_bin_path("ps", True)

            # this should be busybox ps, so we only want/need to the 'w' option
            rc, psout, dummy = module.run_command([psbin, "w"])
            # If rc is 0, set running as appropriate
            if rc == 0:
                lines = psout.split("\n")
                running = any((module.params["pattern"] in line and "pattern=" not in line) for line in lines)
        else:
            rc, dummy, dummy = module.run_command([init_script, "running"])
            if rc == 0:
                running = True

        # default to desired state
        result["state"] = module.params["state"]

        # determine action, if any
        action = None
        if module.params["state"] == "started":
            if not running:
                action = "start"
                result["changed"] = True
        elif module.params["state"] == "stopped":
            if running:
                action = "stop"
                result["changed"] = True
        else:
            action = module.params["state"][:-2]  # remove 'ed' from restarted/reloaded
            result["state"] = "started"
            result["changed"] = True

        if action:
            if not module.check_mode:
                rc, dummy, err = module.run_command([init_script, action])
                if rc != 0:
                    module.fail_json(msg=f"Unable to {action} service {service}: {err}")

    module.exit_json(**result)


if __name__ == "__main__":
    main()
