#!/usr/bin/env python3
# coding: utf-8
# Copyright 2025 Huawei Technologies Co., Ltd
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
# ===========================================================================
import os
import time
import urllib.request
import json
import platform

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils import common_utils
from ansible.module_utils.common_info import DeployStatus


class NodeExporterInstaller:
    SYSTEMCTL_CMD = "systemctl"
    SERVICE_NAME = "node-exporter"
    INSTALL_PATH = "/usr/local/bin/node_exporter"

    def __init__(self):
        self.module = AnsibleModule(
            argument_spec=dict(
                tags=dict(type="list", required=True),
                resources_dir=dict(type="str", required=True),
                ip_address=dict(type="str", required=True),
            )
        )
        self.run_tags = self.module.params["tags"]
        self.username = "node_exporter"
        self.install_path = self.INSTALL_PATH
        self.arch = platform.machine()
        self.resources_dir = os.path.expanduser(self.module.params["resources_dir"])
        self.ip_address = self.module.params["ip_address"]
        self.config = {}
        self.port = None
        self.messages = []

    def create_user(self):
        user_exit, _, _ = self.module.run_command(["getent", "passwd", self.username])
        group_exit, _, _ = self.module.run_command(["getent", "group", self.username])
        if user_exit != 0 and group_exit != 0:
            self.module.run_command(
                ["useradd", "-M", "-s", "/sbin/nologin", self.username], check_rc=True)
        elif user_exit == 0 and group_exit == 0:
            self.messages.append("The user {} already exists, skipping".format(self.username))
        else:
            self.module.fail_json(
                msg="The user or group of {} is invalid, please delete it and try again".format(self.username),
                rc=1, changed=False
            )

    def install_pkg(self):
        tar_pkg = self._find_node_exporter_pkg("node_exporter*{}.tar.gz".format(self.arch))
        self.module.run_command(["tar", "-xzf", tar_pkg, "-C", os.path.dirname(tar_pkg), "--strip-components=1"], check_rc=True)
        pkg = self._find_node_exporter_pkg("node_exporter")
        self.module.run_command(["chmod", "755", pkg], check_rc=True)
        self.module.run_command(["cp", pkg, self.install_path], check_rc=True)
        self.module.run_command(["chown", "root:root", self.install_path], check_rc=True)

    def create_service(self):
        cmd = [self.install_path,
               "--web.listen-address=:{}".format(self.port)]
        content = [
            "[Unit]",
            "Description=Prometheus Node-Exporter",
            "Documentation=https://github.com/prometheus/node_exporter",
            "After=network.target",
            "[Service]",
            "Type=simple",
            "User={}".format(self.username),
            "Group={}".format(self.username),
            "ExecStart={}".format(" ".join(cmd)),
            "Restart=always",
            "RestartSec=2",
            "[Install]",
            "WantedBy=multi-user.target"
        ]
        with open("/etc/systemd/system/node-exporter.service", 'w') as f:
            f.write("\n".join(content))

    def setup_service(self):
        self.module.run_command([self.SYSTEMCTL_CMD, "daemon-reload"], check_rc=True)
        self.module.run_command([self.SYSTEMCTL_CMD, "enable", self.SERVICE_NAME], check_rc=True)
        self.module.run_command([self.SYSTEMCTL_CMD, "start", self.SERVICE_NAME], check_rc=True)

    def wait_server_ready(self):
        url = "http://{}:{}/metrics".format(self.ip_address, self.port)
        interval = 5
        timeout = self.config.get("connect_timeout", 60)
        start_time = time.time()
        while True:
            try:
                response = urllib.request.urlopen(url)
                if response.getcode() == 200:
                    break
            except Exception as e:
                self.messages.append("Waiting for node_exporter service to be ready: {}".format(str(e)))
            if time.time() - start_time > timeout:
                self.messages.append("Install node_exporter success, but connect to the service failed")
                self._module_failed()
            time.sleep(interval)

    def run(self):
        try:
            self._get_config()
            self.create_user()
            self.install_pkg()
            self.create_service()
            self.setup_service()
            self.wait_server_ready()
        except Exception as e:
            self.messages.append(str(e))
            self._module_failed()
        self._module_success()

    def _module_failed(self):
        return self.module.fail_json(msg="\n".join(self.messages), rc=1, changed=False)

    def _module_success(self):
        return self.module.exit_json(msg="Install node_exporter success.", rc=0, changed=True)

    def _find_node_exporter_pkg(self, pattern):
        pkgs, msgs = common_utils.find_files(os.path.join(self.resources_dir, "NodeExporter*"), pattern)
        self.messages.extend(msgs)
        if not pkgs:
            if "auto" in self.run_tags:
                self.module.exit_json(std_out="can not find node_exporter package, node_exporter install skipped", rc=0,
                                      result={DeployStatus.DEPLOY_STATUS: DeployStatus.SKIP},
                                      changed=False)
            else:
                self.messages.append("can not find node_exporter package.")
                self._module_failed()

        return pkgs[0]

    def _get_config(self):
        config_path = self._find_node_exporter_pkg("npu_monitor_config.json")
        with open(config_path, 'r') as f:
            self.config = json.load(f).get("node_exporter", {})
            self.port = self.config.get("port", 9100)


def main():
    NodeExporterInstaller().run()


if __name__ == "__main__":
    main()