# (Copyright) 2015 ABF OSIELL <http://osiell.com>
# (Copyright) 2018 Creu Blanca
#  License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).

import errno
import logging
import os
import shlex
import subprocess

import psutil

from odoo import _, api, fields, models
from odoo.exceptions import UserError
from odoo.tools import config


def is_exe(fpath):
    return os.path.isfile(fpath) and os.access(fpath, os.X_OK)


_logger = logging.getLogger(__name__)

SEND_NSCA_BIN = "/usr/sbin/send_nsca"


class NscaServer(models.Model):
    _name = "nsca.server"
    _description = "NSCA Server"

    name = fields.Char("Hostname", required=True)
    port = fields.Integer(default=5667, required=True)
    password = fields.Char()
    encryption_method = fields.Selection(
        selection="_selection_encryption_method",
        string="Encryption method",
        default="1",
        required=True,
    )
    config_dir_path = fields.Char(
        "Configuration directory", compute="_compute_config_dir_path"
    )
    config_file_path = fields.Char(
        "Configuration file", compute="_compute_config_file_path"
    )
    node_hostname = fields.Char(
        "Hostname of this node",
        required=True,
        help="This is the hostname of the current Odoo node declared in the "
        "monitoring server.",
    )
    check_ids = fields.One2many("nsca.check", "server_id", string="Checks")
    check_count = fields.Integer(compute="_compute_check_count")

    @api.depends("check_ids")
    def _compute_check_count(self):
        for r in self:
            r.check_count = len(r.check_ids)

    def _selection_encryption_method(self):
        return [
            ("0", "0 - None (Do NOT use this option)"),
            ("1", "1 - Simple XOR"),
            ("2", "2 - DES"),
            ("3", "3 - 3DES (Triple DES)"),
            ("4", "4 - CAST-128"),
            ("5", "5 - CAST-256"),
            ("6", "6 - xTEA"),
            ("7", "7 - 3WAY"),
            ("8", "8 - BLOWFISH"),
            ("9", "9 - TWOFISH"),
            ("10", "10 - LOKI97"),
            ("11", "11 - RC2"),
            ("12", "12 - ARCFOUR"),
            ("14", "14 - RIJNDAEL-128"),
            ("15", "15 - RIJNDAEL-192"),
            ("16", "16 - RIJNDAEL-256"),
            ("19", "19 - WAKE"),
            ("20", "20 - SERPENT"),
            ("22", "22 - ENIGMA (Unix crypt)"),
            ("23", "23 - GOST"),
            ("24", "24 - SAFER64"),
            ("25", "25 - SAFER128"),
            ("26", "26 - SAFER+"),
        ]

    def _compute_config_dir_path(self):
        for server in self:
            data_dir_path = config.get("data_dir")
            dir_path = os.path.join(data_dir_path, "nsca_client", self.env.cr.dbname)
            server.config_dir_path = dir_path

    def _compute_config_file_path(self):
        for server in self:
            file_name = "send_nsca_%s.cfg" % server.id
            full_path = os.path.join(server.config_dir_path, file_name)
            server.config_file_path = full_path

    def write_config_file(self):
        for server in self:
            try:
                os.makedirs(server.config_dir_path)
            except OSError as exception:
                if exception.errno != errno.EEXIST:
                    raise
            with open(server.config_file_path, "w") as config_file:
                if server.password:
                    config_file.write("password=%s\n" % server.password)
                config_file.write("encryption_method=%s\n" % server.encryption_method)
        return True

    def write(self, vals):
        res = super(NscaServer, self).write(vals)
        self.write_config_file()
        return res

    @api.model
    def create(self, vals):
        res = super(NscaServer, self).create(vals)
        res.write_config_file()
        return res

    @api.model
    def current_status(self):
        ram = 0
        cpu = 0
        if psutil:
            process = psutil.Process(os.getpid())
            # psutil changed its api through versions
            processes = [process]
            if config.get("workers") and process.parent:  # pragma: no cover
                if callable(process.parent):
                    process = process.parent()
                else:
                    process = process.parent
                if hasattr(process, "children"):
                    processes += process.children(True)
                elif hasattr(process, "get_children"):
                    processes += process.get_children(True)
            for process in processes:
                if hasattr(process, "memory_percent"):
                    ram += process.memory_percent()
                if hasattr(process, "cpu_percent"):
                    cpu += process.cpu_percent(interval=1)
        user_count = 0
        if "bus.presence" in self.env.registry:
            user_count = self.env["bus.presence"].search_count(
                [("status", "=", "online")]
            )
        performance = {
            "cpu": {"value": cpu},
            "ram": {"value": ram},
            "user_count": {"value": user_count},
        }
        return 0, "OK", performance

    def _prepare_command(self):
        """Prepare the shell command used to send the check result
        to the NSCA daemon.
        """
        cmd = "/usr/sbin/send_nsca -H {} -p {} -c {}".format(
            self.name,
            self.port,
            self.config_file_path,
        )
        return shlex.split(cmd)

    @api.model
    def _run_command(self, cmd, check_result):
        """Send the check result through the '/usr/sbin/send_nsca' command."""
        try:
            proc = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stdin=subprocess.PIPE,
                stderr=subprocess.STDOUT,
            )
            stdout = proc.communicate(input=check_result)[0]
            _logger.debug("%s: %s", check_result, stdout.strip())
        except Exception as exc:
            _logger.error(exc)

    def _check_send_nsca_command(self):
        """Check if the NSCA client is installed."""
        if not is_exe(SEND_NSCA_BIN):
            raise UserError(
                _(
                    "Command '%s' not found. Please install the NSCA client.\n"
                    "On Debian/Ubuntu: apt-get install nsca-client"
                )
                % (SEND_NSCA_BIN)
            )

    def _format_check_result(self, service, rc, message):
        """Format the check result with tabulations as delimiter."""
        message = message.replace("\t", " ")
        hostname = self.node_hostname
        check_result = "{}\t{}\t{}\t{}".format(hostname, service, rc, message)
        return check_result.encode("utf-8")

    def _send_nsca(self, service, rc, message, performance):
        """Send the result of the check to the NSCA daemon."""
        msg = message
        if len(performance) > 0:
            msg += "| " + "".join(
                [
                    "%s=%s%s;%s;%s;%s;%s"
                    % (
                        key,
                        performance[key]["value"],
                        performance[key].get("uom", ""),
                        performance[key].get("warn", ""),
                        performance[key].get("crit", ""),
                        performance[key].get("min", ""),
                        performance[key].get("max", ""),
                    )
                    for key in sorted(performance)
                ]
            )
        check_result = self._format_check_result(service, rc, msg)
        cmd = self._prepare_command()
        self._run_command(cmd, check_result)

    def show_checks(self):
        self.ensure_one()
        result = self.env["ir.actions.act_window"]._for_xml_id(
            "nsca_client.action_nsca_check_tree"
        )
        context = {"default_server_id": self.id}
        result["context"] = context
        result["domain"] = [("server_id", "=", self.id)]
        if len(self.check_ids) == 1:
            res = self.env.ref("nsca_client.view_nsca_check_form", False)
            result["views"] = [(res and res.id or False, "form")]
            result["res_id"] = self.check_ids.id
        return result
