# HP-UX OS parameter collector (SERVER-1794)
#
# Kernel tunables : kctune (preferred) / kmtune (fallback), one shot parse.
# Network tunables: ndd -get <device> <key>.
# Default keys are managed here in code; extra keys can be requested by the
# user through the custom option.

import platform
import re
import threading
import whatap.util.process_util as process_util
from whatap.osinfo.os_parameter_common import OsParameter, BackgroundCollector

OS_NAME = platform.system() or 'HP-UX'

# kernel tunable keys (see hpux.sh reference)
_KERNEL_KEYS = [
    'nproc', 'maxuprc', 'nkthread', 'max_thread_proc', 'ncallout', 'ksi_alloc_max',
    'executable_stack', 'max_async_ports', 'maxfiles', 'maxfiles_lim', 'nfile',
    'ninode', 'nflocks', 'npty', 'nstrpty', 'nstrtel', 'NSTREVENT', 'NSTRPUSH',
    'dbc_min_pct', 'dbc_max_pct', 'maxdsiz', 'maxdsiz_64bit', 'maxssiz',
    'maxssiz_64bit', 'maxtsiz', 'maxtsiz_64bit', 'maxswapchunks', 'nswapdev',
    'swapmem_on', 'unlockable_mem', 'shmmax', 'shmmni', 'shmseg', 'shmmax_64bit',
    'shmem', 'shm_mni', 'semmni', 'semmns', 'semmnu', 'semmsl', 'semume', 'semvmx',
    'semaem', 'semmap', 'semopm', 'msgmax', 'msgmnb', 'msgmni', 'msgseg', 'msgssz',
    'msgtql', 'msgmap', 'STRMSGSZ',
]

# network tunables per device (see hpux.sh reference)
_NDD_KEYS = {
    '/dev/tcp': [
        'tcp_conn_request_max', 'tcp_xmit_hiwater_def', 'tcp_recv_hiwater_def',
        'tcp_time_wait_interval', 'tcp_keepalive_interval', 'tcp_ip_abort_interval',
        'tcp_rexmit_interval_initial', 'tcp_rexmit_interval_min',
        'tcp_rexmit_interval_max', 'tcp_smallest_anon_port',
        'tcp_largest_anon_port', 'tcp_naglim_def',
    ],
    '/dev/udp': [
        'udp_xmit_hiwater_def', 'udp_recv_hiwater_def', 'udp_smallest_anon_port',
        'udp_largest_anon_port',
    ],
    '/dev/ip': [
        'ip_forwarding', 'ip_forward_directed_broadcasts',
        'ip_respond_to_echo_broadcast', 'ip_ire_gw_probe', 'ip_pmtu_strategy',
    ],
}

_NDD_DEVICES = ['/dev/tcp', '/dev/udp', '/dev/ip']

_NDD_ERROR_RE = re.compile(
    r'non-existent|invalid|unknown|no such|not supported|'
    r'for a list of valid names|operation failed|permission denied',
    re.IGNORECASE)

_custom_params = []
_custom_lock = threading.Lock()


def SetCustomParams(params):
    global _custom_params
    with _custom_lock:
        _custom_params = list(params or [])


def _get_custom_params():
    with _custom_lock:
        return list(_custom_params)


def _kernel_command():
    for cmd in ('kctune', 'kmtune'):
        try:
            if process_util.executeCommandShellWithTimeout(
                    'command -v %s 2>/dev/null' % cmd, timeout=5):
                return cmd
        except Exception:
            continue
    return None


def _read_kernel_tunables():
    """Return {key: value} for all kernel tunables (single command run)."""
    values = {}
    cmd = _kernel_command()
    if not cmd:
        return values
    try:
        out = process_util.executeCommandShellWithTimeout('%s 2>/dev/null' % cmd, timeout=20)
    except Exception:
        return values
    for line in out.split('\n'):
        parts = line.split()
        if len(parts) >= 2:
            values[parts[0]] = parts[1].replace(',', ' ')
    return values


def _is_valid_ndd_value(value):
    if not value:
        return False
    if _NDD_ERROR_RE.search(value):
        return False
    if len(value.split('\n')) > 1:
        return False
    return True


def _get_ndd_value(device, key):
    try:
        out = process_util.executeCommandShellWithTimeout(
            'ndd -get %s %s 2>&1' % (device, key), timeout=10)
    except Exception:
        return None
    out = out.strip() if out else out
    if _is_valid_ndd_value(out):
        return out.replace(',', ' ')
    return None


def _collect():
    result = []
    seen = set()

    kernel_values = _read_kernel_tunables()
    for key in _KERNEL_KEYS:
        if key in kernel_values and kernel_values[key] != '':
            result.append(OsParameter(key, 'kernel', OS_NAME, kernel_values[key], custom=False))
            seen.add((key, 'kernel'))

    have_ndd = False
    try:
        have_ndd = bool(process_util.executeCommandShellWithTimeout(
            'command -v ndd 2>/dev/null', timeout=5))
    except Exception:
        have_ndd = False

    if have_ndd:
        for device, keys in _NDD_KEYS.items():
            for key in keys:
                value = _get_ndd_value(device, key)
                if value is None:
                    continue
                result.append(OsParameter(key, 'network', OS_NAME, value, custom=False))
                seen.add((key, 'network'))

    for param, category_hint in _get_custom_params():
        value = None
        found_category = None

        if param in kernel_values and kernel_values[param] != '':
            value = kernel_values[param]
            found_category = 'kernel'
        elif have_ndd:
            for device in _NDD_DEVICES:
                value = _get_ndd_value(device, param)
                if value is not None:
                    found_category = 'network'
                    break

        if value is None:
            continue
        category = category_hint or found_category or 'custom'
        if (param, category) in seen:
            continue
        result.append(OsParameter(param, category, OS_NAME, value, custom=True))
        seen.add((param, category))

    return result


_collector = BackgroundCollector('os_parameter_hpux', _collect, interval=300)


def GetOsParameterList(custom_params=None):
    if custom_params is not None:
        SetCustomParams(custom_params)
    return _collector.get()
