<?php
/**
 * Shared Helper Functions
 *
 * Lightweight utility functions used across the plugin.
 * Loaded before any class is instantiated.
 *
 * @package GuardianShield
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

// ── Option Wrapper ───────────────────────────────
/**
 * Get a Guardian Shield option with a default fallback.
 * Strips the gs_ prefix from the key for convenience.
 *
 * Usage: gs_get_option( 'firewall_enabled', true )
 *        retrieves option 'gs_firewall_enabled'.
 */
function gs_get_option( string $key, mixed $default = null ): mixed {
	// Allow callers to pass either 'gs_foo' or 'foo'.
	$full_key = str_starts_with( $key, 'gs_' ) ? $key : "gs_{$key}";
	$value    = get_option( $full_key );
	return ( false === $value ) ? $default : $value;
}

function gs_update_option( string $key, mixed $value ): bool {
	$full_key = str_starts_with( $key, 'gs_' ) ? $key : "gs_{$key}";
	return update_option( $full_key, $value, false );
}

// ── IP Resolution ────────────────────────────────
/**
 * Return the real client IP address.
 *
 * Supports:
 *  - Standard REMOTE_ADDR
 *  - Cloudflare (CF-Connecting-IP)
 *  - Generic reverse proxies (X-Forwarded-For, X-Real-IP)
 *
 * When Cloudflare mode is off, we NEVER trust X-Forwarded-For
 * from unknown sources to prevent IP spoofing.
 */
function gs_get_client_ip(): string {
	static $cached_ip = null;

	if ( null !== $cached_ip ) {
		return $cached_ip;
	}

	$cloudflare = (bool) gs_get_option( 'cloudflare_mode', false );

	// Cloudflare puts the real IP in CF-Connecting-IP.
	if ( $cloudflare && ! empty( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) {
		$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
	} elseif ( $cloudflare && ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
		// X-Forwarded-For can be a comma-list: client, proxy1, proxy2
		$ips = explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] );
		$ip  = trim( $ips[0] );
	} elseif ( ! empty( $_SERVER['HTTP_X_REAL_IP'] ) && $cloudflare ) {
		$ip = $_SERVER['HTTP_X_REAL_IP'];
	} else {
		$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
	}

	// Validate — fallback to REMOTE_ADDR on invalid.
	if ( ! filter_var( $ip, FILTER_VALIDATE_IP ) ) {
		$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
	}

	$cached_ip = $ip;
	return $cached_ip;
}

// ── IP Version ───────────────────────────────────
function gs_is_ipv6( string $ip ): bool {
	return str_contains( $ip, ':' );
}

// ── CIDR Match ───────────────────────────────────
/**
 * Test if $ip falls within $cidr (e.g. "192.168.1.0/24").
 * Supports both IPv4 and IPv6 CIDR ranges.
 */
function gs_ip_in_cidr( string $ip, string $cidr ): bool {
	if ( ! str_contains( $cidr, '/' ) ) {
		return $ip === $cidr;
	}

	[ $subnet, $prefix ] = explode( '/', $cidr, 2 );
	$prefix = (int) $prefix;

	if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
		if ( ! filter_var( $subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
			return false;
		}
		$ip_long     = ip2long( $ip );
		$subnet_long = ip2long( $subnet );
		$mask        = -1 << ( 32 - $prefix );
		return ( $ip_long & $mask ) === ( $subnet_long & $mask );
	}

	// IPv6 — use inet_pton for packed binary comparison.
	$ip_bin     = inet_pton( $ip );
	$subnet_bin = inet_pton( $subnet );

	if ( false === $ip_bin || false === $subnet_bin ) {
		return false;
	}

	$full_bytes = intdiv( $prefix, 8 );
	$rem_bits   = $prefix % 8;

	// Compare full bytes.
	if ( strncmp( $ip_bin, $subnet_bin, $full_bytes ) !== 0 ) {
		return false;
	}

	// Compare remaining bits.
	if ( $rem_bits > 0 ) {
		$mask     = 0xFF & ( 0xFF << ( 8 - $rem_bits ) );
		$ip_byte  = ord( $ip_bin[ $full_bytes ] );
		$sub_byte = ord( $subnet_bin[ $full_bytes ] );
		return ( $ip_byte & $mask ) === ( $sub_byte & $mask );
	}

	return true;
}

// ── Security Score ───────────────────────────────
/**
 * Compute a 0–100 "security score" for the dashboard.
 * Higher = more secure.
 */
function gs_calculate_security_score(): int {
	$score = 100;
	$deductions = [];

	// Each disabled critical feature deducts points.
	if ( ! gs_get_option( 'firewall_enabled', true ) )      { $score -= 20; $deductions[] = 'Firewall disabled'; }
	if ( ! gs_get_option( 'rate_limit_enabled', true ) )    { $score -= 10; $deductions[] = 'Rate limiting disabled'; }
	if ( ! gs_get_option( 'file_monitor_enabled', true ) )  { $score -= 10; $deductions[] = 'File monitoring disabled'; }
	if ( ! gs_get_option( 'scan_enabled', true ) )          { $score -= 10; $deductions[] = 'Malware scanner disabled'; }
	if ( ! gs_get_option( 'headers_enabled', true ) )       { $score -= 10; $deductions[] = 'Security headers disabled'; }
	if ( ! gs_get_option( 'email_alerts', true ) )          { $score -= 5;  $deductions[] = 'Email alerts disabled'; }
	if ( ! gs_get_option( 'block_sqli', true ) )            { $score -= 10; $deductions[] = 'SQLi protection disabled'; }
	if ( ! gs_get_option( 'block_xss', true ) )             { $score -= 10; $deductions[] = 'XSS protection disabled'; }
	if ( gs_get_option( 'max_login_attempts', 5 ) > 10 )    { $score -= 5;  $deductions[] = 'Login attempt limit too high'; }

	// Deduct for active threats.
	$stats = \GuardianShield\Plugin::get_instance()->logger->get_total_stats();
	if ( $stats['scan_threats'] > 0 )  { $score -= 15; $deductions[] = 'Active malware threats'; }
	if ( $stats['attacks_24h'] > 100 ) { $score -= 5;  $deductions[] = 'High attack volume'; }

	$score = max( 0, min( 100, $score ) );

	// Cache the deduction list for the dashboard.
	set_transient( 'gs_score_deductions', $deductions, 300 );

	return $score;
}

// ── Nonce Helpers ────────────────────────────────
function gs_nonce_field( string $action ): void {
	wp_nonce_field( "gs_{$action}", "gs_{$action}_nonce" );
}

function gs_verify_nonce( string $action ): bool {
	$nonce = $_REQUEST[ "gs_{$action}_nonce" ] ?? '';
	return (bool) wp_verify_nonce( sanitize_text_field( wp_unslash( $nonce ) ), "gs_{$action}" );
}

// ── Severity Label ───────────────────────────────
function gs_severity_label( int $level ): string {
	return match ( $level ) {
		4       => esc_html__( 'Critical', 'guardian-shield' ),
		3       => esc_html__( 'High', 'guardian-shield' ),
		2       => esc_html__( 'Medium', 'guardian-shield' ),
		default => esc_html__( 'Low', 'guardian-shield' ),
	};
}

function gs_severity_class( int $level ): string {
	return match ( $level ) {
		4       => 'gs-critical',
		3       => 'gs-high',
		2       => 'gs-medium',
		default => 'gs-low',
	};
}

// ── Time Formatter ───────────────────────────────
function gs_human_time_diff( string $datetime ): string {
	$ts = strtotime( $datetime );
	if ( ! $ts ) {
		return esc_html__( 'Unknown', 'guardian-shield' );
	}
	return sprintf(
		/* translators: %s = human time difference */
		esc_html__( '%s ago', 'guardian-shield' ),
		human_time_diff( $ts, time() )
	);
}
