<?php
/**
 * General-purpose utility class.
 *
 * FIXES:
 *  BUG 1 — Wrong option key TOTAL_GUARD_DEVOZON_enable_cache → FENSE_BPVT_DEVOZON_enable_cache
 *  BUG 5 — IsDailyLimitReached() no longer makes a live API call; reads transient only
 *  BUG 9 — Fense_data() transient 'time' field now matches actual TTL (was hardcoded 1800s)
 *
 * @package FENSE_BPVT
 */

if ( !defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

class FENSE_BPVT_FUN
{
	
	private static bool $hasRun = false;
	public string $statusCode;
	
	// -----------------------------------------------------------------------
	// API helpers
	// -----------------------------------------------------------------------
	
	/** Validates the stored API key; result cached for the configured duration. */
	public static function IsKeyValid(): bool
	{
		$cached = FENSE_BPVT_Cache::wp_get( FENSE_BPVT_PLUGIN::DB_KEY . 'is_api_key_valid' );
		if ( is_array( $cached ) && ( $cached['status'] ?? '' ) === 'success' ) {
			return true;
		}
		
		$api_key = (string)get_option( FENSE_BPVT_PLUGIN::DB_KEY . 'licence_key', '' );
		if ( $api_key === '' ) {
			return false;
		}
		
		$body = wp_remote_retrieve_body(
			wp_remote_get( esc_url_raw( FENSE_BPVT_PLUGIN::SITE . 'wp/' . rawurlencode( $api_key ) ) )
		);
		$api = json_decode( $body );
		
		if ( is_object( $api ) && ( $api->status ?? '' ) === 'success' ) {
			// BUG 1 FIX: correct option key (was TOTAL_GUARD_DEVOZON_enable_cache)
			$hours = max( 1, (int)get_option( FENSE_BPVT_PLUGIN::DB_KEY . 'enable_cache', 1 ) );
			FENSE_BPVT_Cache::wp_set( FENSE_BPVT_PLUGIN::DB_KEY . 'is_api_key_valid', [ 'status' => 'success' ], $hours * HOUR_IN_SECONDS );
			return true;
		}
		
		return false;
	}
	
	/**
	 * Returns true if the daily limit flag transient is active.
	 *
	 * BUG 5 FIX: original made a live wp_remote_get() on every page request.
	 * Now simply reads the transient written by __saveDailyLimitReached().
	 */
	public static function IsDailyLimitReached(): bool
	{
		$t = FENSE_BPVT_Cache::wp_get( FENSE_BPVT_PLUGIN::DB_KEY . 'is_daily_limit_reached' );
		return is_array( $t ) && isset( $t['time'] ) && time() < (int)$t['time'];
	}
	
	/**
	 * Performs an IP lookup. Caches clean-IP results for the configured hours.
	 *
	 * BUG 1 FIX: correct option key for cache duration.
	 * BUG 9 FIX: 'time' in cached payload now equals the transient expiry (was hardcoded 1800s).
	 */
	public function Fense_data( string $ip, string $key ): object
	{
		$cache_key = 'bpvt_ip_' . md5( $key . '_' . $ip );
		$cached = FENSE_BPVT_Cache::wp_get( $cache_key );
		
		if ( is_array( $cached ) && isset( $cached['time'] ) && time() < (int)$cached['time'] ) {
			return $cached['data'];
		}
		
		$response = wp_remote_get( esc_url_raw( FENSE_BPVT_PLUGIN::API . rawurlencode( $key ) . '/' . rawurlencode( $ip ) ) );
		
		$this->statusCode = (int)wp_remote_retrieve_response_code( $response );
		
		$body = wp_remote_retrieve_body( $response );
		
		$result = json_decode( $body );
		
		if ( !is_object( $result ) ) {
			$err = new stdClass();
			$err->status = 'error';
			return $err;
		}
		
		// Only cache clean results to avoid stale proxy entries.
		if ( ( $result->status ?? '' ) === 'success' ) {
			// BUG 1 FIX: correct key; BUG 9 FIX: time field = transient expiry
			$hours = max( 1, (int)get_option( FENSE_BPVT_PLUGIN::DB_KEY . 'enable_cache', 1 ) );
			$ttl = $hours * HOUR_IN_SECONDS;
			FENSE_BPVT_Cache::wp_set( $cache_key, [ 'time' => time() + $ttl, 'data' => $result ], $ttl );
		}
		
		return $result;
	}
	
	// -----------------------------------------------------------------------
	// Formatting helpers
	// -----------------------------------------------------------------------
	
	/** Converts a MySQL timestamp to "X unit(s) ago". */
	public function timeAgo( string $timestamp ): string
	{
		$then = date_create( $timestamp );
		if ( !$then ) {
			return '';
		}
		$d = date_diff( new DateTime( 'now' ), $then );
		
		if ( $d->y > 0 ) {
			$n = $d->y;
			$s = 'year';
			$p = 'years';
		} elseif ( $d->m > 0 ) {
			$n = $d->m;
			$s = 'month';
			$p = 'months';
		} elseif ( $d->d > 0 ) {
			$n = $d->d;
			$s = 'day';
			$p = 'days';
		} elseif ( $d->h > 0 ) {
			$n = $d->h;
			$s = 'hour';
			$p = 'hours';
		} elseif ( $d->i > 0 ) {
			$n = $d->i;
			$s = 'minute';
			$p = 'minutes';
		} else {
			$n = $d->s;
			$s = 'second';
			$p = 'seconds';
		}
		
		$label = $n === 1
			? esc_html__( $s, 'fense-block-vpn-proxy' )
			: esc_html__( $p, 'fense-block-vpn-proxy' );
		
		/* translators: 1: number, 2: time unit */
		return sprintf( esc_html__( '%1$d %2$s ago', 'fense-block-vpn-proxy' ), $n, $label );
	}
	
	/** Returns a styled badge for an IP-type code (0=Clean,1/4=Proxy,2=VPN,3=Tor). */
	public function stats_ip_type( int $type ): string
	{
		switch ( $type ) {
			case 1:
			case 4:
				return '<span class="ms proxy">Proxy</span>';
			case 2:
				return '<span class="ms vpn">VPN</span>';
			case 3:
				return '<span class="ms tor">Tor</span>';
			default:
				return '<span class="ms clean">Clean</span>';
		}
	}
	
	/** Formats large numbers: 1500 → "2K", 2_000_000 → "2M". */
	public function visit_format( int $number ): string
	{
		if ( $number >= 1_000_000 ) {
			return round( $number / 1_000_000 ) . 'M';
		}
		if ( $number >= 1_000 ) {
			return round( $number / 1_000 ) . 'K';
		}
		return (string)$number;
	}
	
	/** Parses a comma-separated domain string into a deduplicated associative array. */
	public function banned_multi_domains_list( string $data ): array
	{
		$d = array_values( array_unique( array_filter( array_map( 'trim', explode( ',', $data ) ) ) ) );
		return $d ? array_combine( $d, $d ) : [];
	}
	
	/** Returns a full country name from a 2-letter ISO code, or 'Unknown'. */
	public function get_country_by_code( string $code ): string
	{
		static $list = [
			'AF' => 'Afghanistan', 'AX' => 'Aland Islands', 'AL' => 'Albania', 'DZ' => 'Algeria',
			'AS' => 'American Samoa', 'AD' => 'Andorra', 'AO' => 'Angola', 'AI' => 'Anguilla',
			'AQ' => 'Antarctica', 'AG' => 'Antigua and Barbuda', 'AR' => 'Argentina', 'AM' => 'Armenia',
			'AW' => 'Aruba', 'AU' => 'Australia', 'AT' => 'Austria', 'AZ' => 'Azerbaijan',
			'BS' => 'Bahamas', 'BH' => 'Bahrain', 'BD' => 'Bangladesh', 'BB' => 'Barbados',
			'BY' => 'Belarus', 'BE' => 'Belgium', 'BZ' => 'Belize', 'BJ' => 'Benin', 'BM' => 'Bermuda',
			'BT' => 'Bhutan', 'BO' => 'Bolivia', 'BQ' => 'Bonaire, Sint Eustatius and Saba',
			'BA' => 'Bosnia and Herzegovina', 'BW' => 'Botswana', 'BV' => 'Bouvet Island',
			'BR' => 'Brazil', 'IO' => 'British Indian Ocean Territory', 'BN' => 'Brunei Darussalam',
			'BG' => 'Bulgaria', 'BF' => 'Burkina Faso', 'BI' => 'Burundi', 'KH' => 'Cambodia',
			'CM' => 'Cameroon', 'CA' => 'Canada', 'CV' => 'Cape Verde', 'KY' => 'Cayman Islands',
			'CF' => 'Central African Republic', 'TD' => 'Chad', 'CL' => 'Chile', 'CN' => 'China',
			'CX' => 'Christmas Island', 'CC' => 'Cocos (Keeling) Islands', 'CO' => 'Colombia',
			'KM' => 'Comoros', 'CG' => 'Congo', 'CD' => 'Congo, the Democratic Republic of the',
			'CK' => 'Cook Islands', 'CR' => 'Costa Rica', 'CI' => "Cote D'Ivoire", 'HR' => 'Croatia',
			'CU' => 'Cuba', 'CW' => 'Curacao', 'CY' => 'Cyprus', 'CZ' => 'Czech Republic',
			'DK' => 'Denmark', 'DJ' => 'Djibouti', 'DM' => 'Dominica', 'DO' => 'Dominican Republic',
			'EC' => 'Ecuador', 'EG' => 'Egypt', 'SV' => 'El Salvador', 'GQ' => 'Equatorial Guinea',
			'ER' => 'Eritrea', 'EE' => 'Estonia', 'ET' => 'Ethiopia',
			'FK' => 'Falkland Islands (Malvinas)', 'FO' => 'Faroe Islands', 'FJ' => 'Fiji',
			'FI' => 'Finland', 'FR' => 'France', 'GF' => 'French Guiana', 'PF' => 'French Polynesia',
			'TF' => 'French Southern Territories', 'GA' => 'Gabon', 'GM' => 'Gambia',
			'GE' => 'Georgia', 'DE' => 'Germany', 'GH' => 'Ghana', 'GI' => 'Gibraltar',
			'GR' => 'Greece', 'GL' => 'Greenland', 'GD' => 'Grenada', 'GP' => 'Guadeloupe',
			'GU' => 'Guam', 'GT' => 'Guatemala', 'GG' => 'Guernsey', 'GN' => 'Guinea',
			'GW' => 'Guinea-Bissau', 'GY' => 'Guyana', 'HT' => 'Haiti',
			'HM' => 'Heard Island and Mcdonald Islands', 'VA' => 'Holy See (Vatican City State)',
			'HN' => 'Honduras', 'HK' => 'Hong Kong', 'HU' => 'Hungary', 'IS' => 'Iceland',
			'IN' => 'India', 'ID' => 'Indonesia', 'IR' => 'Iran, Islamic Republic of',
			'IQ' => 'Iraq', 'IE' => 'Ireland', 'IM' => 'Isle of Man', 'IL' => 'Israel', 'IT' => 'Italy',
			'JM' => 'Jamaica', 'JP' => 'Japan', 'JE' => 'Jersey', 'JO' => 'Jordan',
			'KZ' => 'Kazakhstan', 'KE' => 'Kenya', 'KI' => 'Kiribati',
			'KP' => "Korea, Democratic People's Republic of", 'KR' => 'Korea, Republic of',
			'XK' => 'Kosovo', 'KW' => 'Kuwait', 'KG' => 'Kyrgyzstan',
			'LA' => "Lao People's Democratic Republic", 'LV' => 'Latvia', 'LB' => 'Lebanon',
			'LS' => 'Lesotho', 'LR' => 'Liberia', 'LY' => 'Libyan Arab Jamahiriya',
			'LI' => 'Liechtenstein', 'LT' => 'Lithuania', 'LU' => 'Luxembourg', 'MO' => 'Macao',
			'MK' => 'Macedonia, the Former Yugoslav Republic of', 'MG' => 'Madagascar',
			'MW' => 'Malawi', 'MY' => 'Malaysia', 'MV' => 'Maldives', 'ML' => 'Mali', 'MT' => 'Malta',
			'MH' => 'Marshall Islands', 'MQ' => 'Martinique', 'MR' => 'Mauritania',
			'MU' => 'Mauritius', 'YT' => 'Mayotte', 'MX' => 'Mexico',
			'FM' => 'Micronesia, Federated States of', 'MD' => 'Moldova, Republic of',
			'MC' => 'Monaco', 'MN' => 'Mongolia', 'ME' => 'Montenegro', 'MS' => 'Montserrat',
			'MA' => 'Morocco', 'MZ' => 'Mozambique', 'MM' => 'Myanmar', 'NA' => 'Namibia',
			'NR' => 'Nauru', 'NP' => 'Nepal', 'NL' => 'Netherlands',
			'AN' => 'Netherlands Antilles', 'NC' => 'New Caledonia', 'NZ' => 'New Zealand',
			'NI' => 'Nicaragua', 'NE' => 'Niger', 'NG' => 'Nigeria', 'NU' => 'Niue',
			'NF' => 'Norfolk Island', 'MP' => 'Northern Mariana Islands', 'NO' => 'Norway',
			'OM' => 'Oman', 'PK' => 'Pakistan', 'PW' => 'Palau',
			'PS' => 'Palestinian Territory, Occupied', 'PA' => 'Panama',
			'PG' => 'Papua New Guinea', 'PY' => 'Paraguay', 'PE' => 'Peru',
			'PH' => 'Philippines', 'PN' => 'Pitcairn', 'PL' => 'Poland', 'PT' => 'Portugal',
			'PR' => 'Puerto Rico', 'QA' => 'Qatar', 'RE' => 'Reunion', 'RO' => 'Romania',
			'RU' => 'Russian Federation', 'RW' => 'Rwanda', 'BL' => 'Saint Barthelemy',
			'SH' => 'Saint Helena', 'KN' => 'Saint Kitts and Nevis', 'LC' => 'Saint Lucia',
			'MF' => 'Saint Martin', 'PM' => 'Saint Pierre and Miquelon',
			'VC' => 'Saint Vincent and the Grenadines', 'WS' => 'Samoa', 'SM' => 'San Marino',
			'ST' => 'Sao Tome and Principe', 'SA' => 'Saudi Arabia', 'SN' => 'Senegal',
			'RS' => 'Serbia', 'CS' => 'Serbia and Montenegro', 'SC' => 'Seychelles',
			'SL' => 'Sierra Leone', 'SG' => 'Singapore', 'SX' => 'Sint Maarten', 'SK' => 'Slovakia',
			'SI' => 'Slovenia', 'SB' => 'Solomon Islands', 'SO' => 'Somalia', 'ZA' => 'South Africa',
			'GS' => 'South Georgia and the South Sandwich Islands', 'SS' => 'South Sudan',
			'ES' => 'Spain', 'LK' => 'Sri Lanka', 'SD' => 'Sudan', 'SR' => 'Suriname',
			'SJ' => 'Svalbard and Jan Mayen', 'SZ' => 'Swaziland', 'SE' => 'Sweden',
			'CH' => 'Switzerland', 'SY' => 'Syrian Arab Republic',
			'TW' => 'Taiwan, Province of China', 'TJ' => 'Tajikistan',
			'TZ' => 'Tanzania, United Republic of', 'TH' => 'Thailand', 'TL' => 'Timor-Leste',
			'TG' => 'Togo', 'TK' => 'Tokelau', 'TO' => 'Tonga', 'TT' => 'Trinidad and Tobago',
			'TN' => 'Tunisia', 'TR' => 'Turkey', 'TM' => 'Turkmenistan',
			'TC' => 'Turks and Caicos Islands', 'TV' => 'Tuvalu', 'UG' => 'Uganda',
			'UA' => 'Ukraine', 'AE' => 'United Arab Emirates', 'GB' => 'United Kingdom',
			'US' => 'United States', 'UM' => 'United States Minor Outlying Islands',
			'UY' => 'Uruguay', 'UZ' => 'Uzbekistan', 'VU' => 'Vanuatu', 'VE' => 'Venezuela',
			'VN' => 'Viet Nam', 'VG' => 'Virgin Islands, British', 'VI' => 'Virgin Islands, U.S.',
			'WF' => 'Wallis and Futuna', 'EH' => 'Western Sahara', 'YE' => 'Yemen',
			'ZM' => 'Zambia', 'ZW' => 'Zimbabwe',
		];
		return $list[ strtoupper( $code ) ] ?? 'Unknown';
	}
	
	/**
	 * Returns the real visitor IP, validating each candidate with filter_var().
	 * Checks Cloudflare and Fastly headers before falling back to REMOTE_ADDR.
	 */
	public function get_the_user_ip(): string
	{
		foreach ( [
					  $_SERVER['HTTP_CF_CONNECTING_IP'] ?? '',
					  $_SERVER['HTTP_FASTLY_CLIENT_IP'] ?? '',
					  $_SERVER['HTTP_CLIENT_IP'] ?? '',
					  $_SERVER['REMOTE_ADDR'] ?? '',
				  ] as $candidate ) {
			$ip = sanitize_text_field( $candidate );
			if ( $ip !== '' && filter_var( $ip, FILTER_VALIDATE_IP ) ) {
				return $ip;
			}
		}
		return '';
	}
	
	/**
	 * Renders the 403 blocked page via wp_die().
	 * $mode='sanad' suppresses output (for testing).
	 */
	public function get_main_template(
		string $lang, string $message, string $showLink,
		string $link, string $mode, string $url
	): void
	{
		if ( $mode === 'sanad' ) {
			return;
		}
		
		if ( $message === '' ) {
			$message = $lang === 'Arabic'
				? 'تم اكتشاف انك تستخدم بروكسي او في بي ان . برجاء التعطيل للوصول لهذا الموقع!'
				: 'Proxy or VPN detected - Please disable to access this website!';
		}
		
		$how = $lang === 'Arabic'
			? esc_html__( 'تعلم كيف يمكنك تعطيلة', 'fense-block-vpn-proxy' )
			: esc_html__( 'Learn How to disable', 'fense-block-vpn-proxy' );
		
		$link_html = ( $showLink === 'yes' && $link !== '' )
			? ' <a href="' . esc_url( $link ) . '">' . $how . '</a>'
			: '';
		
		if ( $showLink === 'yes' && empty( $link ) ) {
			$link_html = '<a href="https://fense.in/how-it-works">' . $how . '</a>';
		}
		
		if ( !defined( 'DONOTCACHEPAGE' ) ) {
			define( 'DONOTCACHEPAGE', true );
		}
		
		wp_die(
			'<p>' . esc_html( $message ) . $link_html . '</p>',
			esc_html( $message ),
			[ 'back_link' => true, 'response' => 403 ]
		);
	}
	
	/** Outputs a hidden <span> carrying i18n strings for the settings-page JS. */
	public function loadLang(): string
	{
		return sprintf(
			'<span id="fense-bpvt-settings-page" data-s1="%s" data-s2="%s" data-s3="%s" data-s4="%s" data-s5="%s" data-s6="%s"></span>',
			esc_attr__( 'Settings error.', 'fense-block-vpn-proxy' ),
			esc_attr__( 'saving', 'fense-block-vpn-proxy' ),
			esc_url( admin_url( '/options.php' ) ),
			esc_attr__( 'Save Changes', 'fense-block-vpn-proxy' ),
			esc_attr__( 'Settings saved.', 'fense-block-vpn-proxy' ),
			esc_url( admin_url( 'admin-ajax.php' ) )
		);
	}
	
	/** Singleton guard — returns true if detection has already run this request. */
	public static function runOnce(): bool
	{
		if ( self::$hasRun ) {
			return true;
		}
		self::$hasRun = true;
		return false;
	}
	
	/** Hourly cleanup of expired transients (at most once per hour). */
	public static function cleanUP(): void
	{
		if ( FENSE_BPVT_Cache::wp_get( 'fense_bpvt_clean_up' ) ) {
			return;
		}
		// Skip DB transient sweep when Redis handles all caching.
		if ( !FENSE_BPVT_Cache::is_available() && function_exists( 'delete_expired_transients' ) ) {
			delete_expired_transients( true );
		}
		FENSE_BPVT_Cache::wp_set( 'fense_bpvt_clean_up', 'done', HOUR_IN_SECONDS );
	}
}
