# Copyright (c) Cloud Linux GmbH & Cloud Linux Software, Inc 2010-2018 All Rights Reserved
#
# Licensed under CLOUD LINUX LICENSE AGREEMENT
# http://cloudlinux.com/docs/LICENSE.TXT

import string
import sys
import os
import traceback
import json
import configparser
from io import StringIO
from pipes import quote
import exec_command


class StatUtilsException(Exception):
    pass


def cpanel_whmapi(cmd, **kwargs):
    """
    Perform cPanel WHM API console request and return data from result
    :param cmd: whm api command
    :return: data dict from result
    """
    joined_kwargs = ' '.join([quote('{0}={1}'.format(k, v)) for k, v in kwargs.items()])
    result = exec_command.exec_command("/usr/sbin/whmapi1 {cmd} {kw} --output json".format(cmd=cmd, kw=joined_kwargs))
    try:
        dict_result = json.loads(''.join(result))
    except ValueError:
        # if result is not JSON
        raise StatUtilsException(
            "Failed to get JSON from this API request: {0} {1}; output: {2}".format(cmd,
                                                                                    joined_kwargs,
                                                                                    ''.join(result)))

    try:
        return dict_result['data']
    except KeyError:
        # if there are no data field in received JSON
        raise StatUtilsException("Failed to get data from this API result: {0}".format(dict_result))


def plesk_bin_php_handler(cmd, **kwargs):
    """
    Perform Plesk php_handler utility console request and return result
    :param cmd: php_handler command
    :return: dict result
    """
    # need to quote only value (-k 'v'), quoting full params ('-k v') causes API failure
    joined_kwargs = ' '.join(['-{0} {1}'.format(k, quote(v)) for k, v in kwargs.items()])
    result = exec_command.exec_command("/usr/local/psa/bin/php_handler --{cmd} {kw} -json true".format(cmd=cmd,
                                                                                                       kw=joined_kwargs))
    try:
        return json.loads(''.join(result))
    except ValueError:
        raise StatUtilsException(
            "Failed to get JSON from this API request: php_handler {0} {1}; output: {2}".format(cmd,
                                                                                                joined_kwargs,
                                                                                                ''.join(result)))


def get_da_domains():
    """
    Get domains per user
    :return: dict(
                user: list of domains
            )
    """
    da_users_path = '/usr/local/directadmin/data/users'
    da_domains_path = '/usr/local/directadmin/data/users/{user}/domains.list'

    da_users = os.listdir(da_users_path)
    domains = dict.fromkeys(da_users)
    try:
        for user in da_users:
            with open(da_domains_path.format(user=user), 'r') as domains_list_file:
                domains[user] = set([l.strip() for l in domains_list_file.readlines()])
    except (OSError, IOError):
        raise StatUtilsException(''.join(traceback.format_exc().split('\n')))
    return domains


def get_da_php_options():
    """
    Get php settings from options.conf
    :return: dict(
                first php setting: {version, mode},
                second php setting: {version, mode},
            )
    """
    options_path = '/usr/local/directadmin/custombuild/options.conf'

    try:
        config_parser, global_section = read_da_config(options_path)
        php1_ver = config_parser.get(global_section, 'php1_release')
        php2_ver = config_parser.get(global_section, 'php2_release')
        php1_handler = config_parser.get(global_section, 'php1_mode')
        php2_handler = config_parser.get(global_section, 'php2_mode')
    except configparser.NoOptionError:
        raise StatUtilsException('No option found: {0}'.format(''.join(traceback.format_exc().split('\n'))))

    return {
        1: {
            'version': php1_ver,
            'handler': 'lsapi' if php1_handler == 'lsphp' else php1_handler
        },
        2: {
            'version': php2_ver,
            'handler': 'lsapi' if php2_handler == 'lsphp' else php2_handler
        }
    }


def read_da_config(conf_file, append_section_name='dummy_section'):
    """
    Read DA config file with ConfigParser.
    Need to add dummy section for success
    :param conf_file: config file name
    :param append_section_name: name of section to place in the beginning of file
    :return: RawConfigParser instance
    """
    try:
        with open(conf_file) as f:
            file_content = StringIO('[{s}]\n'.format(s=append_section_name) + f.read())
        config_parser = configparser.RawConfigParser(strict=False)
        config_parser.read_file(file_content)
    except (OSError, IOError):
        raise StatUtilsException(''.join(traceback.format_exc().split('\n')))
    return config_parser, append_section_name


def pretty_version_keys(php_ver, pre='php'):
    """
    Convert simple php versions to pretty format
    :param php_ver: {major}.{minor} version
    :param pre: desired key start
    :return: alt-php{major}{minor} or desired `pre`{major}{minor}
    """
    template = '{0}%s%s'.format(pre)
    try:
        return template % tuple(php_ver.split('.'))
    except Exception:
        return php_ver


def dump_lsapi(ctl_path='/usr/sbin/httpd'):
    """
    Get `httpd -t -D DUMP_RUN_LSAPI` info
    For httpd24 this default path is `/opt/rh/httpd24/root/usr/sbin/httpd`, generated in make_from_templates.sh script
    :param ctl_path: path to httpd (also apachectl may be used)
    :return: dict(
                lsapi_option: value
            )
    """
    apache_conf_data = exec_command.exec_command('{ctl} -t -D DUMP_RUN_LSAPI'.format(ctl=ctl_path))
    try:
        return dict([l.lower().split(' ') for l in apache_conf_data])
    except:
        return dict()


def dump_loaded_modules(ctl_path='/usr/sbin/httpd'):
    """
    Get `httpd -M`
    For httpd24 this default path is `/opt/rh/httpd24/root/usr/sbin/httpd`, generated in make_from_templates.sh script
    :param ctl_path: path to httpd (also apachectl may be used)
    :return: dict(
                apache_module: value
            )
    """
    apache_modules = exec_command.exec_command('{ctl} -M'.format(ctl=ctl_path))
    try:
        return dict([l.lower().split(' ') for l in apache_modules])
    except:
        return dict()


def liblsapi_path():
    """
    Retrieve path to liblsapi, depends on arch
    :return: path to liblsapi
    """
    is_64bits = sys.maxsize > 2**32
    return '/usr/lib{a}/liblscapi.so'.format(a='64' if is_64bits else '')


def rpm_query(pkg):
    """
    Get version-release from rpm -q `pkg`
    :param pkg: package name to query
    :return: version-release
    """
    try:
        ver, rel = ''.join(exec_command.exec_command('/bin/rpm -q ' + pkg + ' --qf %{v}-%{r}')).split('-')
        return '{ver}-{rel}'.format(ver=ver, rel=rel.split('.')[0])
    except ValueError:
        return None


def query_strings(fname, template):
    """
    Filter strings by given template
    Also split string upon given template
    :param fname: path to file
    :param template: template to find in string
    :return: first template occurrence splitted by template
    """
    try:
        return [l for l in strings(fname) if template in l][0].split(template)[1].strip()
    except (IndexError, IOError, OSError):
        return None


def strings(fname, n=6):
    """
    Strings utility analog.
    Finds printable strings in executable
    :param fname: path to file
    :param n: minimum string length
    :return: generator, yeilds string
    """
    with open(fname, errors='ignore') as f:
        result = ""
        for c in f.read():
            if c in string.printable:
                result += c
                continue
            if len(result) >= n:
                yield result
            result = ""
        if len(result) >= n:  # catch result at EOF
            yield result


def count_domains(handler_struct, default_keys, only_lsapi=True):
    """
    Count domains
    :param handler_struct: handler: version: set_of_domains structure
    :param default_keys: sequence of keys to add as default if no `lsapi` found
    :param only_lsapi: return only lsapi statistics
    :return: statistics - number of lsapi domains per version if only_lsapi=True
                          number of lsapi domains per version per handler otherwise
    """
    result_stat = dict()
    for h, data in handler_struct.items():
        result_stat[h] = dict((k, len(v)) for k, v in data.items())

    # if there are no lsapi handler domains, return 0 per each installed version
    try:
        return result_stat['lsapi'] if only_lsapi else result_stat
    except KeyError:
        return dict.fromkeys([x for x in default_keys if x != 'no'], 0)
