# -*- coding: utf-8 -*-
#
# Copyright © 2009-2010 CEA
# Pierre Raybaut
# Licensed under the terms of the CECILL License
# Modified for Datadog

import yaml
try:
    from yaml import CLoader as Loader
except ImportError:
    from yaml import Loader
import sys
import os
import os.path as osp
import webbrowser
import thread  # To manage the windows process asynchronously

import win32serviceutil
import win32service

# GUI Imports
from guidata.qt.QtCore import SIGNAL, Qt, QSize, QPoint, QTimer

from guidata.configtools import get_icon, get_family, MONOSPACE
from guidata.qthelpers import get_std_icon
from spyderlib.widgets.sourcecode.codeeditor import CodeEditor


# Datadog
from common.util import get_os
from config import (get_confd_path, get_config_path, get_config,
                    _windows_commondata_path)

EXCLUDED_WINDOWS_CHECKS = [
    'cacti', 'directory', 'gearmand',
    'hdfs', 'kafka_consumer', 'mcache', 'network',
    'redis', 'postfix', 'process', 'gunicorn', 'zk',
]

MAIN_WINDOW_TITLE = "Datadog Agent Manager"

DATADOG_SERVICE = "DatadogAgent"

STATUS_PAGE_URL = "http://localhost:17125/status"
AGENT_LOG_FILE = osp.join(_windows_commondata_path(), 'Datadog', 'logs', 'ddagent.log')

HUMAN_SERVICE_STATUS = {win32service.SERVICE_RUNNING: 'Service is running',
                        win32service.SERVICE_START_PENDING: 'Service is starting',
                        win32service.SERVICE_STOP_PENDING: 'Service is stopping',
                        win32service.SERVICE_STOPPED: 'Service is stopped',
                        "Unknown": "Cannot get service status"}

REFRESH_PERIOD = 5000

START_AGENT = "Start Agent"
STOP_AGENT = "Stop Agent"
RESTART_AGENT = "Restart Agent"
STATUS_PAGE = "Status page"
EXIT_MANAGER = "Exit Agent Manager"
OPEN_LOG = "Open log file"

SYSTEM_TRAY_MENU = [
    (START_AGENT, lambda: service_manager("start")),
    (STOP_AGENT, lambda: service_manager("stop")),
    (RESTART_AGENT, lambda: service_manager("restart")),
    (STATUS_PAGE, lambda: webbrowser.open(STATUS_PAGE_URL)),
    (EXIT_MANAGER, lambda: sys.exit(0)),
]


def get_checks():
    checks = {}
    conf_d_directory = get_confd_path(get_os())

    for filename in sorted(os.listdir(conf_d_directory)):
        module_name, ext = osp.splitext(filename)
        if filename.split('.')[0] in EXCLUDED_WINDOWS_CHECKS:
            continue
        if ext not in ('.yaml', '.example', '.disabled'):
            continue

        agent_check = AgentCheck(filename, ext, conf_d_directory)
        if (agent_check.enabled or agent_check.module_name not in checks or
                (not agent_check.is_example and not checks[agent_check.module_name].enabled)):
            checks[agent_check.module_name] = agent_check

    checks_list = checks.values()
    checks_list.sort(key=lambda c: c.module_name)

    return checks_list


class EditorFile(object):

    def __init__(self, file_path, description):
        self.file_path = file_path
        self.description = description

    def get_description(self):
        return self.description

    def save(self, content):
        try:
            f = open(self.file_path, 'w')
            f.write(content)
            self.content = content
            info_popup("File saved.")
        except Exception as e:
            warning_popup("Unable to save file: \n %s" % str(e))
            raise


class LogFile(EditorFile):

    def __init__(self):
        EditorFile.__init__(self, AGENT_LOG_FILE, "Agent log file")


class DatadogConf(EditorFile):

    @property
    def api_key(self):
        config = get_config(parse_args=False, cfg_path=self.file_path)
        api_key = config.get('api_key', None)
        if not api_key or api_key == 'APIKEYHERE':
            return None
        return api_key

    def check_api_key(self, editor):
        if self.api_key is None:
            api_key, ok = QInputDialog.getText(
                None, "Add your API KEY", "You must first set your api key in this file. You can find it here: https://app.datadoghq.com/account/settings#api")
            if ok and api_key:
                new_content = []
                for line in self.content.splitlines():
                    if "api_key:" in line:
                        new_content.append("api_key: %s" % str(api_key))
                    else:
                        new_content.append("%s" % line)
                new_content = "\n".join(new_content)
                self.save(new_content)
                editor.set_text(new_content)

                if not is_service_stopped():
                    service_manager("restart")
                else:
                    service_manager("start")
            else:
                self.check_api_key(editor)


class AgentCheck(EditorFile):

    def __init__(self, filename, ext, conf_d_directory):
        file_path = osp.join(conf_d_directory, filename)
        self.module_name = filename.split('.')[0]

        EditorFile.__init__(self, file_path, description=self.module_name.replace("_", " ").title())

        self.enabled = ext == '.yaml'
        self.is_example = ext == '.example'
        self.enabled_name = osp.join(conf_d_directory, "%s.yaml" % self.module_name)
        self.disabled_name = "%s.disabled" % self.enabled_name

    def enable(self):
        self.enabled = True
        os.rename(self.file_path, self.enabled_name)
        self.file_path = self.enabled_name

    def disable(self):
        self.enabled = False
        os.rename(self.file_path, self.disabled_name)
        self.file_path = self.disabled_name

    def save(self, content):
        check_yaml_syntax(content)
        EditorFile.save(self, content)


class PropertiesWidget(QWidget):

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        font = QFont(get_family(MONOSPACE), 10, QFont.Normal)

        info_icon = QLabel()
        icon = get_std_icon('MessageBoxInformation').pixmap(24, 24)
        info_icon.setPixmap(icon)
        info_icon.setFixedWidth(32)
        info_icon.setAlignment(Qt.AlignTop)

        self.service_status_label = QLabel()
        self.service_status_label.setWordWrap(True)
        self.service_status_label.setAlignment(Qt.AlignTop)
        self.service_status_label.setFont(font)

        self.desc_label = QLabel()
        self.desc_label.setWordWrap(True)
        self.desc_label.setAlignment(Qt.AlignTop)
        self.desc_label.setFont(font)

        group_desc = QGroupBox("Description", self)
        layout = QHBoxLayout()
        layout.addWidget(info_icon)
        layout.addWidget(self.desc_label)
        layout.addStretch()
        layout.addWidget(self.service_status_label)

        group_desc.setLayout(layout)

        self.editor = CodeEditor(self)
        self.editor.setup_editor(linenumbers=True, font=font)
        self.editor.setReadOnly(False)
        group_code = QGroupBox("Source code", self)
        layout = QVBoxLayout()
        layout.addWidget(self.editor)
        group_code.setLayout(layout)

        self.enable_button = QPushButton(get_icon("apply.png"),
                                         "Enable", self)

        self.save_button = QPushButton(get_icon("filesave.png"),
                                       "Save", self)

        self.edit_datadog_conf_button = QPushButton(get_icon("edit.png"),
                                                    "Edit agent settings", self)

        self.disable_button = QPushButton(get_icon("delete.png"),
                                          "Disable", self)

        self.view_log_button = QPushButton(get_icon("txt.png"),
                                           "View log", self)

        self.menu_button = QPushButton(get_icon("settings.png"),
                                       "Manager", self)

        hlayout = QHBoxLayout()
        hlayout.addWidget(self.save_button)
        hlayout.addStretch()
        hlayout.addWidget(self.enable_button)
        hlayout.addStretch()
        hlayout.addWidget(self.disable_button)
        hlayout.addStretch()
        hlayout.addWidget(self.edit_datadog_conf_button)
        hlayout.addStretch()
        hlayout.addWidget(self.view_log_button)
        hlayout.addStretch()
        hlayout.addWidget(self.menu_button)

        vlayout = QVBoxLayout()
        vlayout.addWidget(group_desc)
        vlayout.addWidget(group_code)
        vlayout.addLayout(hlayout)
        self.setLayout(vlayout)

        self.current_file = None

    def set_item(self, check):
        self.current_file = check
        self.desc_label.setText(check.get_description())
        self.editor.set_text_from_file(check.file_path)
        check.content = self.editor.toPlainText().__str__()
        if check.enabled:
            self.disable_button.setEnabled(True)
            self.enable_button.setEnabled(False)
        else:
            self.disable_button.setEnabled(False)
            self.enable_button.setEnabled(True)

    def set_datadog_conf(self, datadog_conf):
        self.current_file = datadog_conf
        self.desc_label.setText(datadog_conf.get_description())
        self.editor.set_text_from_file(datadog_conf.file_path)
        datadog_conf.content = self.editor.toPlainText().__str__()
        self.disable_button.setEnabled(False)
        self.enable_button.setEnabled(False)

        datadog_conf.check_api_key(self.editor)

    def set_log_file(self, log_file):
        self.current_file = log_file
        self.desc_label.setText(log_file.get_description())
        self.editor.set_text_from_file(log_file.file_path)
        log_file.content = self.editor.toPlainText().__str__()
        self.disable_button.setEnabled(False)
        self.enable_button.setEnabled(False)
        self.editor.go_to_line(len(log_file.content.splitlines()))


class MainWindow(QSplitter):

    def __init__(self, parent=None):

        QSplitter.__init__(self, parent)
        self.setWindowTitle(MAIN_WINDOW_TITLE)
        self.setWindowIcon(get_icon("agent.svg"))

        self.sysTray = SystemTray(self)

        self.connect(self.sysTray, SIGNAL(
            "activated(QSystemTrayIcon::ActivationReason)"), self.__icon_activated)

        checks = get_checks()
        datadog_conf = DatadogConf(
            get_config_path(), description="Agent settings file: datadog.conf")
        self.log_file = LogFile()

        listwidget = QListWidget(self)
        listwidget.addItems(
            [osp.basename(check.module_name).replace("_", " ").title() for check in checks])

        self.properties = PropertiesWidget(self)

        self.addWidget(listwidget)
        self.addWidget(self.properties)

        self.connect(self.properties.enable_button, SIGNAL("clicked()"),
                     lambda: enable_check(self.properties))

        self.connect(self.properties.disable_button, SIGNAL("clicked()"),
                     lambda: disable_check(self.properties))

        self.connect(self.properties.save_button, SIGNAL("clicked()"),
                     lambda: save_file(self.properties))

        self.connect(listwidget, SIGNAL('currentRowChanged(int)'),
                     lambda row: self.properties.set_item(checks[row]))

        self.connect(self.properties.edit_datadog_conf_button, SIGNAL('clicked()'),
                     lambda: self.properties.set_datadog_conf(datadog_conf))

        self.connect(self.properties.view_log_button, SIGNAL('clicked()'),
                     lambda: self.properties.set_log_file(self.log_file))

        self.manager_menu = Menu(self)
        self.connect(
            self.properties.menu_button,
            SIGNAL("clicked()"),
            lambda: self.manager_menu.popup(
                self.properties.menu_button.mapToGlobal(
                    QPoint(
                        0,
                        0))))

        listwidget.setCurrentRow(0)

        self.setSizes([150, 1])
        self.setStretchFactor(1, 1)
        self.resize(QSize(950, 600))
        self.properties.set_datadog_conf(datadog_conf)

        self.do_refresh()

    def do_refresh(self):
        try:
            if self.isVisible():
                service_status = get_service_status()
                self.properties.service_status_label.setText(HUMAN_SERVICE_STATUS[service_status])

                if not is_service_stopped(
                        service_status) and self.properties.current_file == self.log_file:
                    self.properties.set_log_file(self.log_file)
        finally:
            QTimer.singleShot(REFRESH_PERIOD, self.do_refresh)

    def closeEvent(self, event):
        self.hide()
        self.sysTray.show()
        event.ignore()

    def __icon_activated(self, reason):
        if reason == QSystemTrayIcon.DoubleClick:
            self.show()


class Menu(QMenu):

    def __init__(self, parent=None, ):
        QMenu.__init__(self, parent)
        self.options = {}

        for name, action in SYSTEM_TRAY_MENU:
            menu_action = self.addAction(name)
            self.connect(menu_action, SIGNAL('triggered()'), action)
            self.options[name] = menu_action

        self.connect(self, SIGNAL("aboutToShow()"), lambda: self.update_options())

    def update_options(self):
        status = get_service_status()
        if is_service_running(status):
            self.options[START_AGENT].setEnabled(False)
            self.options[RESTART_AGENT].setEnabled(True)
            self.options[STOP_AGENT].setEnabled(True)
        elif is_service_stopped(status):
            self.options[START_AGENT].setEnabled(True)
            self.options[RESTART_AGENT].setEnabled(False)
            self.options[STOP_AGENT].setEnabled(False)
        elif is_service_pending(status):
            self.options[START_AGENT].setEnabled(False)
            self.options[RESTART_AGENT].setEnabled(False)
            self.options[STOP_AGENT].setEnabled(False)


class SystemTray(QSystemTrayIcon):

    def __init__(self, parent=None):
        QSystemTrayIcon.__init__(self, parent)
        self.setIcon(get_icon("agent.png"))
        self.setVisible(True)
        self.options = {}

        menu = Menu(self.parent())
        self.setContextMenu(menu)


def disable_check(properties):
    check = properties.current_file
    new_content = properties.editor.toPlainText().__str__()

    if check.content != new_content:
        warning_popup("You must first save the file.")
        return

    properties.enable_button.setEnabled(True)
    properties.disable_button.setEnabled(False)
    check.disable()


def enable_check(properties):
    check = properties.current_file

    new_content = properties.editor.toPlainText().__str__()
    if check.content != new_content:
        warning_popup("You must first save the file")
        return

    properties.enable_button.setEnabled(False)
    properties.disable_button.setEnabled(True)
    check.enable()


def save_file(properties):
    current_file = properties.current_file
    new_content = properties.editor.toPlainText().__str__()
    current_file.save(new_content)


def check_yaml_syntax(content):
    try:
        yaml.load(content, Loader=Loader)
    except Exception as e:
        warning_popup("Unable to parse yaml: \n %s" % str(e))
        raise


def _service_manager(action):
    try:
        if action == 'stop':
            win32serviceutil.StopService(DATADOG_SERVICE)
        elif action == 'start':
            win32serviceutil.StartService(DATADOG_SERVICE)
        elif action == 'restart':
            win32serviceutil.RestartService(DATADOG_SERVICE)
    except Exception as e:
        warning_popup("Couldn't %s service: \n %s" % (action, str(e)))


def service_manager(action, async=True):
    if not async:
        _service_manager(action)
    else:
        thread.start_new_thread(_service_manager, (action,))


def get_service_status():
    try:
        return win32serviceutil.QueryServiceStatus(DATADOG_SERVICE)[1]
    except Exception:
        return "Unknown"


def is_service_running(status=None):
    if status is None:
        status = get_service_status()
    return status == win32service.SERVICE_RUNNING


def is_service_pending(status=None):
    if status is None:
        status = get_service_status()
    return status in [win32service.SERVICE_STOP_PENDING, win32service.SERVICE_START_PENDING]


def is_service_stopped(status=None):
    if status is None:
        status = get_service_status()
    return status == win32service.SERVICE_STOPPED


def warning_popup(message, parent=None):
    QMessageBox.warning(parent, 'Message', message, QMessageBox.Ok)


def info_popup(message, parent=None):
    QMessageBox.information(parent, 'Message', message, QMessageBox.Ok)


if __name__ == '__main__':
    from guidata.qt.QtGui import QApplication
    app = QApplication([])
    win = MainWindow()
    win.show()
    app.exec_()
