"""
security.py — Security-related functions.
"""

import glob
import os
import re
import subprocess
from datetime import date

from modules.output import good, bad, warn, info
from modules.utils import require_bin, run, require_file

# Shared state for security policy (mirrors $secpol in the original)
_secpol = 'N'


def set_secpol(val):
    global _secpol
    _secpol = val


def get_secpol():
    return _secpol


def enable_root():
    """
    Enable root SSH access with password authentication.
    Edits /etc/ssh/sshd_config and restarts sshd.
    """
    sshd_config = '/etc/ssh/sshd_config'
    if not require_file(sshd_config, 'enable-root'):
        return

    _sshd_replace(sshd_config,
                  'PasswordAuthentication no', 'PasswordAuthentication yes')
    _sshd_replace(sshd_config,
                  'PermitRootLogin without-password', 'PermitRootLogin yes')

    run(['service', 'sshd', 'restart'])
    print("Allow root SSH password login  [Warning] — root access enabled.")


def disable_root():
    """
    Disable root SSH password authentication.
    Edits /etc/ssh/sshd_config and restarts sshd.
    """
    sshd_config = '/etc/ssh/sshd_config'
    if not require_file(sshd_config, 'disable-root'):
        return

    _sshd_replace(sshd_config,
                  'PasswordAuthentication yes', 'PasswordAuthentication no')
    _sshd_replace(sshd_config,
                  'PermitRootLogin yes', 'PermitRootLogin without-password')

    run(['service', 'sshd', 'restart'])
    print("Disable root SSH password login (key required) — root password login disabled.")


def _sshd_replace(path, old, new):
    try:
        with open(path, 'r') as f:
            content = f.read()
        content = content.replace(old, new)
        with open(path, 'w') as f:
            f.write(content)
    except PermissionError:
        warn(f"Permission denied writing to {path}. Are you root?")


def pass_security(email, password):
    """
    Verify a mailbox password against the stored SHA512-CRYPT shadow hash.
    Usage: pass_security('user@domain.tld', 'thepassword')
    """
    if not require_bin('doveadm', 'pass-security'):
        return
    if not require_file('/scripts/whoowns', 'pass-security'):
        return

    if '@' not in email:
        warn("Invalid email address format.")
        return

    username, domain = email.rsplit('@', 1)

    # Find cPanel user who owns the domain
    out, _, rc = run(['/scripts/whoowns', domain])
    if rc != 0 or not out.strip():
        warn(f"Could not determine cPanel owner for domain: {domain}")
        return
    cpuser = out.strip()

    shadow_path = f'/home/{cpuser}/etc/{domain}/shadow'
    if not require_file(shadow_path, 'pass-security'):
        return

    with open(shadow_path, 'r') as f:
        for line in f:
            if line.startswith(username + ':'):
                hash_val = line.split(':', 1)[1].strip()
                enc_pass = f'{{SHA512-CRYPT}}{hash_val}'
                out2, _, rc2 = run(['doveadm', 'pw', '-t', enc_pass, '-p', password])
                print(out2.strip() if out2.strip() else ("Password matches." if rc2 == 0 else "Password does NOT match."))
                return

    warn(f"User '{username}' not found in shadow file.")


def sec_enable():
    """Re-enable cPanel Security Questions (SourceIPCheck)."""
    global _secpol
    config = '/var/cpanel/cpanel.config'

    if _secpol == 'N':
        print("Security Questions is not setup.")
    elif _secpol == '1':
        print("Security Questions already enabled.")
    elif _secpol == '0':
        _replace_in_file(config,
                         'SecurityPolicy::SourceIPCheck=0',
                         'SecurityPolicy::SourceIPCheck=1')
        run(['/scripts/restartsrv', 'cpanel'])
        _secpol = '1'
        print("Security Questions have been enabled.")
    else:
        warn("Unknown security policy state.")


def sec_disable():
    """Disable cPanel Security Questions (SourceIPCheck)."""
    global _secpol
    config = '/var/cpanel/cpanel.config'

    if _secpol == 'N':
        print("Security Questions is not setup.")
    elif _secpol == '0':
        print("Security Questions already disabled.")
    elif _secpol == '1':
        _replace_in_file(config,
                         'SecurityPolicy::SourceIPCheck=1',
                         'SecurityPolicy::SourceIPCheck=0')
        run(['/scripts/restartsrv', 'cpanel'])
        _secpol = '0'
        print("Security Questions have been disabled.")
        warn("[WARNING] Please ensure you re-enable them before finalizing work. Use: sec-enable")
    else:
        warn("Unknown security policy state.")


def _replace_in_file(path, old, new):
    try:
        with open(path, 'r') as f:
            content = f.read()
        content = content.replace(old, new)
        with open(path, 'w') as f:
            f.write(content)
    except (PermissionError, FileNotFoundError) as e:
        warn(f"Could not update {path}: {e}")


def hack():
    """
    Scan Apache domlogs and current-month user logs for WP brute-force IPs.
    Reports IPs with more than 10 hits. Excludes proxy-subdomain logs.
    """
    domlog_dir = '/usr/local/apache/domlogs'
    month_year = date.today().strftime('%b-%Y')  # e.g. "Apr-2026"

    targets = [
        ('POST /xmlrpc.php', 'xmlrpc.php brute force'),
        ('POST /wp-login.php', 'wp-login.php brute force'),
        ('POST /admin-ajax.php', 'admin-ajax.php abuse'),
    ]

    for pattern, label in targets:
        print(f"Checking for {label}..")
        ip_counts = {}

        # 1. Live domlogs (exclude proxy-subdomain files)
        if os.path.isdir(domlog_dir):
            try:
                for fname in os.listdir(domlog_dir):
                    if fname.startswith('proxy-subdomain'):
                        continue
                    fpath = os.path.join(domlog_dir, fname)
                    if not os.path.isfile(fpath):
                        continue
                    try:
                        with open(fpath, 'r', errors='replace') as f:
                            for line in f:
                                if pattern in line:
                                    parts = line.split()
                                    if parts:
                                        ip_counts[parts[0]] = ip_counts.get(parts[0], 0) + 1
                    except PermissionError:
                        continue
            except PermissionError:
                warn(f"Cannot access {domlog_dir}")

        # 2. Current-month compressed user logs: /home/*/logs/*-Apr-2026.gz
        gz_pattern = f'/home/*/logs/*-{month_year}.gz'
        # Use zgrep for efficiency — avoids loading entire gz into memory
        for gz_path in glob.glob(gz_pattern):
            try:
                result = subprocess.run(
                    ['zgrep', '-a', pattern, gz_path],
                    stdout=subprocess.PIPE, stderr=subprocess.PIPE
                )
                for line in result.stdout.decode('utf-8', errors='replace').splitlines():
                    parts = line.split()
                    if parts:
                        ip_counts[parts[0]] = ip_counts.get(parts[0], 0) + 1
            except Exception:
                continue

        attackers = {ip: c for ip, c in ip_counts.items() if c > 10}
        if attackers:
            top = sorted(attackers.items(), key=lambda x: x[1], reverse=True)[:20]
            for ip, count in top:
                print(f"  {count:>6}  {ip}")
            if len(attackers) > 20:
                info(f"  ... {len(attackers) - 20} more (showing top 20)")
        else:
            info("  No attackers found (>10 hit threshold).")
        print()


def blocks():
    """Display all LFD blocks for today's date."""
    lfd_log = '/var/log/lfd.log'
    if not require_file(lfd_log, 'blocks'):
        return

    today = date.today()
    month_abbr = today.strftime('%b')
    day = today.strftime('%d').lstrip('0')  # LFD uses non-padded day

    found = False
    try:
        with open(lfd_log, 'r', errors='replace') as f:
            for line in f:
                if f"{month_abbr} {day}" in line and 'Blocked' in line:
                    print(line.rstrip())
                    found = True
    except PermissionError:
        warn("Cannot read LFD log (permission denied).")
        return

    if not found:
        info(f"No blocked IPs found for today ({month_abbr} {day}).")


def wpsecurity():
    """Find WordPress security plugin installations (Wordfence and WP-Cerber)."""
    targets = [
        ('wp-cerber', 'WP-Cerber'),
        ('wordfence', 'Wordfence'),
    ]

    for dirname, label in targets:
        print(f"Looking for installations of {label}")
        found = False
        plugin_path_suffix = f'wp-content/plugins/{dirname}'
        for root, dirs, files in os.walk('/home/'):
            dirs[:] = [d for d in dirs if d != 'virtfs']  # don't descend into virtfs
            if dirname in dirs:
                path = os.path.join(root, dirname)
                if plugin_path_suffix in path:
                    print(f"  {path}")
                    found = True
        if not found:
            info(f"  No {label} installations found.")
        print()


def exim_security():
    """Show Exim TLS configuration (openssl_options and tls_require_ciphers)."""
    exim_conf = '/etc/exim.conf'
    if not require_file(exim_conf, 'exim-security'):
        return

    with open(exim_conf, 'r', errors='replace') as f:
        for line in f:
            if 'openssl_options' in line or 'tls_require_ciphers' in line:
                print(line.rstrip())


def security_policy():
    """Show all cPanel SecurityPolicy fields from cpanel.config."""
    from modules.output import header
    config = '/var/cpanel/cpanel.config'
    if not os.path.isfile(config):
        bad("cpanel.config not found.")
        return

    with open(config, 'r') as f:
        content = f.read()

    fields = [
        ('SecurityPolicy::PasswordAge',      'PasswordAge'),
        ('SecurityPolicy::PasswordStrength',  'PasswordStrength'),
        ('SecurityPolicy::SourceIPCheck',     'SourceIPCheck'),
        ('SecurityPolicy::TwoFactorAuth',     'TwoFactorAuth'),
        ('SecurityPolicy::dnsclustering',     'dnsclustering'),
        ('SecurityPolicy::xml-api',           'xml-api'),
    ]

    header("Security Policy")
    for key, label in fields:
        m = re.search(rf'{re.escape(key)}=(\d)', content)
        val = m.group(1) if m else 'N/A'
        print(f"  {label}: {val}")


def cphulk_status():
    """Show cPHulk brute-force protection status."""
    import json
    out, _, rc = run(['whmapi1', '--output=json', 'cphulk_status'])
    if rc != 0:
        warn("Could not query cPHulk status.")
        return
    try:
        data = json.loads(out)
        enabled = data['data']['is_enabled']
        state = 'Enabled' if enabled else 'Disabled'
        if enabled:
            good(f"cPHulk: {state}")
        else:
            bad(f"cPHulk: {state}")
    except (json.JSONDecodeError, KeyError):
        warn("Could not parse cPHulk status response.")


def cphulk_enable():
    """Enable cPHulk brute-force protection."""
    import json
    out, _, rc = run(['whmapi1', '--output=json', 'enable_cphulk'])
    if rc != 0:
        warn("Could not enable cPHulk.")
        return
    try:
        data = json.loads(out)
        if data.get('metadata', {}).get('result') == 1:
            good("cPHulk enabled.")
        else:
            warn(data.get('metadata', {}).get('reason', 'Unknown error'))
    except (json.JSONDecodeError, KeyError):
        warn("Could not parse cPHulk enable response.")


def cphulk_disable():
    """Disable cPHulk brute-force protection. [WARNING]"""
    import json
    out, _, rc = run(['whmapi1', '--output=json', 'disable_cphulk'])
    if rc != 0:
        warn("Could not disable cPHulk.")
        return
    try:
        data = json.loads(out)
        if data.get('metadata', {}).get('result') == 1:
            bad("cPHulk disabled.")
        else:
            warn(data.get('metadata', {}).get('reason', 'Unknown error'))
    except (json.JSONDecodeError, KeyError):
        warn("Could not parse cPHulk disable response.")
