# Copyright (c) Cloud Linux Software, Inc
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENCE.TXT

import os
import time

from kcarectl import delivery_kit, kcare, log_utils, utils

if False:  # pragma: no cover
    from typing import Any, Dict, List  # noqa: F401


@utils.catch_errors(logger=log_utils.logwarn)
def send_data_package(data_package):
    # type: (delivery_kit.DataPackage) -> str
    """Send the DataPackage archive to the patch server.

    Upload errors are logged and swallowed (catch_errors), preserving
    the historical kernel-anomaly behavior.

    :param data_package: DataPackage instance to send
    :return: Upload name (package identifier)
    """

    return data_package.send()


def copy_recent_files(files, data_package, archive_prefix):
    # type: (List[str], delivery_kit.DataPackage, str) -> None
    """adds recent files for the last hour to the given data package starting from the newest one"""

    now = time.time()
    for path, ctime in kcare.sort_files_by_ctime(files):
        if now - ctime > 3600:
            break

        arcname = '{0}/{1}'.format(archive_prefix, path.replace('/', '_'))
        data_package.add_file(arcname, src_path=path)


@utils.catch_errors(logger=log_utils.logwarn)
def prepare_kernel_anomaly_report(server_info):  # pragma: no cover
    # type: (Dict[str, Any]) -> delivery_kit.KernelAnomalyPackage

    data_package = delivery_kit.KernelAnomalyPackage()

    with data_package:
        if os.path.exists('/var/log/messages'):
            data_package.add_stdout('messages', 'tail -n10000 /var/log/messages')

        if os.path.exists('/var/log/syslog'):
            data_package.add_stdout('syslog', 'tail -n10000 /var/log/syslog')

        data_package.add_stdout('kcarectl.log', 'tail -n10000 /var/log/kcarectl.log')
        data_package.add_stdout('dmesg', 'dmesg')
        data_package.add_stdout('ls_var_cache_kcare', 'ls -lR /var/cache/kcare/')

        if os.path.exists('/usr/bin/rpm') or os.path.exists('/bin/rpm'):
            packages_cmd = r'rpm -q -a --queryformat="%{N}|%{V}-%{R}|%{arch}|%{INSTALLTIME:date}\n"'
        elif os.path.exists('/usr/bin/dpkg'):
            packages_cmd = r'/usr/bin/dpkg-query -W -f "${binary:Package}|${Version}|${Architecture}\n"'
            data_package.add_file('dpkg.log', src_path='/var/log/dpkg.log')
        else:
            packages_cmd = 'echo "unknown package manager"'

        data_package.add_stdout('packages.list', packages_cmd)

        # tar fails to add files from /proc directly so we first read them to memory
        with open('/proc/version') as f:
            data_package.add_file('proc_version', data_bytes=utils.bstr(f.read()))

        with open('/proc/modules') as f:
            data_package.add_file('proc_modules', data_bytes=utils.bstr(f.read()))

        data_package.add_file('kcare.conf', src_path='/etc/sysconfig/kcare/kcare.conf')
        data_package.add_json('server_info.json', server_info)

        # kdump
        data_package.add_file('kdump.conf', src_path='/etc/kdump.conf')
        data_package.add_stdout('ls_kdump', 'ls -lR {0}'.format(kcare.get_kdump_root()))
        try:
            copy_recent_files(kcare.list_kdump_txt_files(), data_package, 'kdump')
        except Exception as e:
            data_package.log_error('failed to copy kdumps:\n{0}'.format(e))

        try:
            copy_recent_files(kcare.list_crashreporter_log_files(), data_package, 'crashreporter')
        except Exception as e:
            data_package.log_error('failed to copy crashreporter artifacts:\n{0}'.format(e))

    return data_package


@utils.catch_errors(logger=log_utils.logwarn, default_return=False)
def detect_anomaly(server_info):
    # type: (Dict[str, Any]) -> bool
    """taken from eportal - anomalies::detect_agent_reboot"""

    reason = server_info['reason']  # type: str
    uptime = int(server_info['uptime'])
    patch_level = int(server_info.get('patch_level') or '-1')
    last_stop = int(server_info['last_stop'])
    ts = server_info['ts']  # type: int

    try:
        state_ts = int(server_info['state']['ts'])
    except (KeyError, TypeError, ValueError):
        state_ts = 0

    fields = [reason, uptime, patch_level, state_ts, last_stop, ts]

    if not all(fields):
        return False

    first_update_after_reboot_marker = False
    crash_soon_after_update_marker = False
    no_proper_shutdown_marker = False

    if uptime < 300 and patch_level == -1:
        first_update_after_reboot_marker = True
    if (ts - uptime) > state_ts > (ts - uptime - 1800) and reason == 'update':
        crash_soon_after_update_marker = True
    if last_stop < state_ts:  # pragma: no branch
        no_proper_shutdown_marker = True

    markers = [
        first_update_after_reboot_marker,
        crash_soon_after_update_marker,
        no_proper_shutdown_marker,
    ]

    if all(markers):
        log_utils.loginfo('Agent anomaly detected: {0}'.format(server_info))
        return True

    return False


if __name__ == '__main__':  # pragma: no cover
    prepare_kernel_anomaly_report({})
