"""
utils.py — Shared utility helpers used across modules.
Includes dependency checks, subprocess wrappers, OS detection, and env helpers.
"""

VERSION = '1.0.3'

import os
import re
import shutil
import socket
import subprocess
import sys
from datetime import date, datetime

# ── Dependency checking ───────────────────────────────────────────────────────

def check_bin(name):
    """Return True if a binary exists on PATH (or known locations)."""
    return shutil.which(name) is not None


def require_bin(name, context="this command"):
    """
    Warn and return False if a required binary is missing.
    Callers should return early if this returns False.
    """
    from modules.output import warn
    if not check_bin(name):
        warn(f"'{name}' not found — skipping {context}.")
        return False
    return True


def require_file(path, context="this command"):
    """
    Warn and return False if a required file is missing.
    """
    from modules.output import warn
    if not os.path.isfile(path):
        warn(f"'{path}' not found — skipping {context}.")
        return False
    return True


# ── Subprocess helpers ────────────────────────────────────────────────────────

def run(cmd, shell=False, capture=True, input_data=None, cwd=None):
    """
    Run a command. Returns (stdout, stderr, returncode).
    cmd can be a list or a string (use shell=True for strings with pipes etc).
    """
    try:
        kwargs = dict(shell=shell, cwd=cwd)
        if capture:
            kwargs['stdout'] = subprocess.PIPE
            kwargs['stderr'] = subprocess.PIPE
        if input_data is not None:
            encoded = input_data.encode('utf-8') if isinstance(input_data, str) else input_data
            kwargs['input'] = encoded
        result = subprocess.run(cmd, **kwargs)
        if capture:
            stdout = result.stdout.decode('utf-8', errors='replace') if result.stdout else ''
            stderr = result.stderr.decode('utf-8', errors='replace') if result.stderr else ''
        else:
            stdout = stderr = ''
        return stdout, stderr, result.returncode
    except FileNotFoundError as e:
        return "", str(e), 127
    except Exception as e:
        return "", str(e), 1


def run_passthrough(cmd, shell=False, cwd=None):
    """
    Run a command, streaming output directly to stdout/stderr.
    Returns the exit code.
    """
    try:
        result = subprocess.run(cmd, shell=shell, cwd=cwd)
        return result.returncode
    except FileNotFoundError:
        from modules.output import warn
        warn(f"Command not found: {cmd}")
        return 127


def pipe(cmds):
    """
    Run a list of commands as a shell pipeline.
    Returns the final stdout as a string.
    cmds: list of lists, e.g. [['grep', 'foo'], ['awk', '{print $1}']]
    """
    procs = []
    for i, cmd in enumerate(cmds):
        stdin = procs[-1].stdout if procs else None
        p = subprocess.Popen(cmd, stdin=stdin, stdout=subprocess.PIPE,
                             stderr=subprocess.DEVNULL, text=True)
        if procs:
            procs[-1].stdout.close()
        procs.append(p)
    out, _ = procs[-1].communicate()
    return out


# ── OS / environment detection ────────────────────────────────────────────────

def get_rhel_version():
    """
    Return the RHEL major version as an int, or None if not determinable.
    Reads from /etc/redhat-release.
    """
    try:
        out, _, _ = run(['rpm', '-E', '%{rhel}'])
        ver = out.strip()
        return int(ver) if ver.isdigit() else None
    except Exception:
        return None


def get_os_release():
    """Return the contents of /etc/redhat-release, or a fallback string."""
    try:
        with open('/etc/redhat-release', 'r') as f:
            return f.read().strip()
    except FileNotFoundError:
        return "Unknown OS"


def get_hostname():
    """Return the system FQDN."""
    return socket.getfqdn()


def get_primary_ip():
    """Return the primary IP address via hostname -i."""
    out, _, _ = run(['hostname', '-i'])
    return out.strip()


def get_vz_ctid():
    """Return the OpenVZ container ID from /proc/vz/veinfo, or 'N/A'."""
    try:
        with open('/proc/vz/veinfo', 'r') as f:
            line = f.readline().strip()
            return line.split()[0] if line else 'N/A'
    except FileNotFoundError:
        return 'N/A'


def is_cpanel():
    """Return True if this is a cPanel server."""
    return os.path.isdir('/usr/local/cpanel')


# ── PHP EOL data ──────────────────────────────────────────────────────────────

PHP_EOL = {
    "5.2": "2011-06-01",
    "5.3": "2014-08-14",
    "5.4": "2015-09-03",
    "5.5": "2016-07-10",
    "5.6": "2018-12-31",
    "7.0": "2018-12-03",
    "7.1": "2019-12-01",
    "7.2": "2020-11-30",
    "7.3": "2021-12-06",
    "7.4": "2022-11-28",
    "8.0": "2023-11-26",
    "8.1": "2023-11-25",
    "8.2": "2024-12-31",
    "8.3": "2025-12-31",
    "8.4": "2026-12-31",
}


def php_eol(version):
    """Return the EOL date string for a PHP version, or the version itself if unknown."""
    return PHP_EOL.get(version, version)


def date_check(date_str):
    """
    Compare date_str (YYYY-MM-DD) to today.
    Returns a colored string: green if future, red if past.
    """
    from modules.output import COLORAMA_AVAILABLE
    try:
        eol = datetime.strptime(date_str, "%Y-%m-%d").date()
        today = date.today()
        if COLORAMA_AVAILABLE:
            from colorama import Fore, Style
            if eol > today:
                return Fore.GREEN + date_str + Style.RESET_ALL
            else:
                return Fore.RED + date_str + Style.RESET_ALL
        else:
            status = "OK" if eol > today else "EOL"
            return f"{date_str} [{status}]"
    except ValueError:
        return date_str


def get_dir_owner(path='.'):
    """Return the username owning a directory (equivalent to stat -c %U .)."""
    import pwd
    try:
        uid = os.stat(path).st_uid
        return pwd.getpwuid(uid).pw_name
    except Exception:
        return None


def generate_random_string(length=10):
    """Generate a random alphanumeric string of given length."""
    import random
    import string
    chars = string.ascii_letters + string.digits
    return ''.join(random.choice(chars) for _ in range(length))


def term_width(default=220):
    """Return current terminal width, falling back to default."""
    return shutil.get_terminal_size((default, 40)).columns
