<?php

namespace KaizenCoders\MagicLink;

use KaizenCoders\MagicLink\Option;

/**
 * Plugin_Name
 *
 * @link      https://kaizencoders.com
 * @author    KaizenCoders <hello@kaizencoders.com>
 * @package   Magic Link
 */

/**
 * Helper Class
 */
class Helper {

	/**
	 * Whether given user is an administrator.
	 *
	 * @param  \WP_User  $user  The given user.
	 *
	 * @return bool
	 */
	public static function is_user_admin( $user = null ) {
		if ( is_null( $user ) ) {
			$user = wp_get_current_user();
		}

		if ( ! $user instanceof \WP_User ) {
			_doing_it_wrong( __METHOD__, 'To check if the user is admin is required a WP_User object.', '1.0.0' );
		}

		return is_multisite() ? user_can( $user, 'manage_network' ) : user_can( $user, 'manage_options' );
	}

	/**
	 * What type of request is this?
	 *
	 * @param  string  $type  admin, ajax, cron, cli or frontend.
	 *
	 * @return bool
	 * @since 1.0.0
	 *
	 */
	public function request( $type ) {
		switch ( $type ) {
			case 'admin_backend':
				return $this->is_admin_backend();
			case 'ajax':
				return $this->is_ajax();
			case 'installing_wp':
				return $this->is_installing_wp();
			case 'rest':
				return $this->is_rest();
			case 'cron':
				return $this->is_cron();
			case 'frontend':
				return $this->is_frontend();
			case 'cli':
				return $this->is_cli();
			default:
				_doing_it_wrong( __METHOD__, esc_html( sprintf( 'Unknown request type: %s', $type ) ), '1.0.0' );

				return false;
		}
	}

	/**
	 * Is installing WP
	 *
	 * @return boolean
	 */
	public function is_installing_wp() {
		return defined( 'WP_INSTALLING' );
	}

	/**
	 * Is admin
	 *
	 * @return boolean
	 * @since 1.0.0
	 *
	 */
	public function is_admin_backend() {
		return is_user_logged_in() && is_admin();
	}

	/**
	 * Is ajax
	 *
	 * @return boolean
	 * @since 1.0.0
	 *
	 */
	public function is_ajax() {
		return ( function_exists( 'wp_doing_ajax' ) && wp_doing_ajax() ) || defined( 'DOING_AJAX' );
	}

	/**
	 * Is rest
	 *
	 * @return boolean
	 * @since 1.0.0
	 *
	 */
	public function is_rest() {
		return defined( 'REST_REQUEST' );
	}

	/**
	 * Is cron
	 *
	 * @return boolean
	 * @since 1.0.0
	 *
	 */
	public function is_cron() {
		return ( function_exists( 'wp_doing_cron' ) && wp_doing_cron() ) || defined( 'DOING_CRON' );
	}

	/**
	 * Is frontend
	 *
	 * @return boolean
	 * @since 1.0.0
	 *
	 */
	public function is_frontend() {
		return ( ! $this->is_admin_backend() || ! $this->is_ajax() ) && ! $this->is_cron() && ! $this->is_rest();
	}

	/**
	 * Is cli
	 *
	 * @return boolean
	 * @since 1.0.0
	 *
	 */
	public function is_cli() {
		return defined( 'WP_CLI' ) && \WP_CLI;
	}

	/**
	 * Define constant
	 *
	 * @param $value
	 *
	 * @param $name
	 *
	 * @since 1.0.0
	 *
	 */
	public static function maybe_define_constant( $name, $value ) {
		if ( ! defined( $name ) ) {
			define( $name, $value );
		}
	}

	/**
	 * Get current date time
	 *
	 * @return false|string
	 */
	public static function get_current_date_time() {
		return gmdate( 'Y-m-d H:i:s' );
	}


	/**
	 * Get current date time
	 *
	 * @return false|string
	 *
	 */
	public static function get_current_gmt_timestamp() {
		return strtotime( gmdate( 'Y-m-d H:i:s' ) );
	}

	/**
	 * Get current date
	 *
	 * @return false|string
	 *
	 */
	public static function get_current_date() {
		return gmdate( 'Y-m-d' );
	}

	/**
	 * Format date time
	 *
	 * @param $date
	 *
	 * @return string
	 *
	 * @since 1.0.0
	 *
	 */
	public static function format_date_time( $date ) {
		$convert_date_format = get_option( 'date_format' );
		$convert_time_format = get_option( 'time_format' );

		$local_timestamp = ( $date !== '0000-00-00 00:00:00' ) ? date_i18n(
			"$convert_date_format $convert_time_format",
			strtotime( get_date_from_gmt( $date ) )
		) : '<i class="dashicons dashicons-es dashicons-minus"></i>';

		return $local_timestamp;
	}

	/**
	 * Clean String or array using sanitize_text_field
	 *
	 * @param $variable Data to sanitize
	 *
	 * @return array|string
	 *
	 * @since 1.0.0
	 *
	 */
	public static function clean( $var ) {
		if ( is_array( $var ) ) {
			return array_map( 'clean', $var );
		} else {
			return is_scalar( $var ) ? sanitize_text_field( $var ) : $var;
		}
	}

	/**
	 * Append UTM tracking parameters to a URL.
	 *
	 * Keys can be supplied in short form (source, medium, campaign, term,
	 * content) or already-prefixed form (utm_source, ...) — either way
	 * the output uses the `utm_` prefix. Existing UTM values on the URL
	 * are overwritten by the passed ones. Fragments (#anchor) are
	 * preserved and always placed after the query string, so callers
	 * that hand in malformed URLs like `example.com#pricing?utm_foo=1`
	 * come back correctly reordered.
	 *
	 * @param  string $url    Base URL.
	 * @param  array  $params source/medium/campaign/term/content pairs.
	 *
	 * @return string
	 *
	 * @since 1.7.0
	 */
	public static function add_utm_params( $url, $params ) {
		if ( '' === (string) $url || empty( $params ) ) {
			return (string) $url;
		}

		$utm = array();
		foreach ( (array) $params as $key => $value ) {
			$value = (string) $value;
			if ( '' === $value ) {
				continue;
			}
			$key = (string) $key;
			if ( 0 !== strpos( $key, 'utm_' ) ) {
				$key = 'utm_' . $key;
			}
			$utm[ $key ] = $value;
		}
		if ( empty( $utm ) ) {
			return (string) $url;
		}

		$parts = function_exists( 'wp_parse_url' ) ? wp_parse_url( $url ) : parse_url( $url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url

		$scheme   = isset( $parts['scheme'] )   ? $parts['scheme'] . '://' : '';
		$user     = isset( $parts['user'] )     ? $parts['user'] . ( isset( $parts['pass'] ) ? ':' . $parts['pass'] : '' ) . '@' : '';
		$host     = isset( $parts['host'] )     ? $parts['host'] : '';
		$port     = isset( $parts['port'] )     ? ':' . $parts['port'] : '';
		$path     = isset( $parts['path'] )     ? $parts['path'] : '';
		$query    = isset( $parts['query'] )    ? $parts['query'] : '';
		$fragment = isset( $parts['fragment'] ) ? '#' . $parts['fragment'] : '';

		$existing = array();
		if ( '' !== $query ) {
			parse_str( $query, $existing );
		}

		$merged = array_merge( is_array( $existing ) ? $existing : array(), $utm );
		$rebuilt_query = http_build_query( $merged );

		return $scheme . $user . $host . $port . $path
			. ( '' !== $rebuilt_query ? '?' . $rebuilt_query : '' )
			. $fragment;
	}

	/**
	 * Get IP
	 *
	 * @return mixed|string|void
	 *
	 * @since 1.0.0
	 */
	public static function get_ip() {
		// Get real visitor IP behind CloudFlare network
		if ( isset( $_SERVER['HTTP_CF_CONNECTING_IP'] ) ) {
			$ip = $_SERVER['HTTP_CF_CONNECTING_IP'];
		} elseif ( isset( $_SERVER['HTTP_X_REAL_IP'] ) ) {
			$ip = $_SERVER['HTTP_X_REAL_IP'];
		} elseif ( isset( $_SERVER['HTTP_CLIENT_IP'] ) ) {
			$ip = $_SERVER['HTTP_CLIENT_IP'];
		} elseif ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
			$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
		} elseif ( isset( $_SERVER['HTTP_X_FORWARDED'] ) ) {
			$ip = $_SERVER['HTTP_X_FORWARDED'];
		} elseif ( isset( $_SERVER['HTTP_FORWARDED_FOR'] ) ) {
			$ip = $_SERVER['HTTP_FORWARDED_FOR'];
		} elseif ( isset( $_SERVER['HTTP_FORWARDED'] ) ) {
			$ip = $_SERVER['HTTP_FORWARDED'];
		} else {
			$ip = isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : 'UNKNOWN';
		}

		return $ip;
	}

	/**
	 * Get GMT Offset
	 *
	 * @param  null  $timestamp
	 *
	 * @param  bool  $in_seconds
	 *
	 * @return float|int
	 *
	 * @since 1.0.0
	 *
	 */
	public static function get_gmt_offset( $in_seconds = false, $timestamp = null ) {
		$offset = get_option( 'gmt_offset' );

		if ( $offset == '' ) {
			$tzstring = get_option( 'timezone_string' );
			$current  = date_default_timezone_get();
			date_default_timezone_set( $tzstring );
			$offset = date( 'Z' ) / 3600;
			date_default_timezone_set( $current );
		}

		// check if timestamp has DST
		if ( ! is_null( $timestamp ) ) {
			$l = localtime( $timestamp, true );
			if ( $l['tm_isdst'] ) {
				$offset ++;
			}
		}

		return $in_seconds ? $offset * 3600 : (int) $offset;
	}

	/**
	 * Insert $new in $array after $key
	 *
	 * @param $key
	 * @param $new
	 *
	 * @param $array
	 *
	 * @return array
	 *
	 * @since 1.0.0
	 *
	 */
	public static function array_insert_after( $array, $key, $new ) {
		$keys  = array_keys( $array );
		$index = array_search( $key, $keys );
		$pos   = false === $index ? count( $array ) : $index + 1;

		return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) );
	}

	/**
	 * Insert a value or key/value pair before a specific key in an array.  If key doesn't exist, value is prepended
	 * to the beginning of the array.
	 *
	 * @param  string  $key
	 * @param  array   $new
	 *
	 * @param  array   $array
	 *
	 * @return array
	 *
	 * @since 1.0.0
	 *
	 */
	public static function array_insert_before( array $array, $key, array $new ) {
		$keys = array_keys( $array );
		$pos  = (int) array_search( $key, $keys );

		return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) );
	}

	/**
	 * Insert $new in $array after $key
	 *
	 * @param $array
	 *
	 * @return boolean
	 *
	 * @since 1.0.0
	 *
	 */
	public static function is_forechable( $array = [] ) {
		if ( ! is_array( $array ) ) {
			return false;
		}

		if ( empty( $array ) ) {
			return false;
		}

		if ( count( $array ) <= 0 ) {
			return false;
		}

		return true;
	}

	/**
	 * Get current db version
	 *
	 * @since 1.0.0
	 */
	public static function get_db_version() {
		return Option::get( 'db_version', null );
	}

	/**
	 * Get data from array
	 *
	 * @param  string  $var
	 * @param  string  $default
	 * @param  bool    $clean
	 *
	 * @param  array   $array
	 *
	 * @return array|string
	 *
	 * @since 1.0.0
	 *
	 */
	public static function get_data( $array = [], $var = '', $default = '', $clean = false ) {
		if ( empty( $array ) ) {
			return $default;
		}

		if ( ! empty( $var ) || ( 0 === $var ) ) {
			if ( strpos( $var, '|' ) > 0 ) {
				$vars = array_map( 'trim', explode( '|', $var ) );
				foreach ( $vars as $var ) {
					if ( isset( $array[ $var ] ) ) {
						$array = $array[ $var ];
					} else {
						return $default;
					}
				}

				return wp_unslash( $array );
			} else {
				$value = isset( $array[ $var ] ) ? wp_unslash( $array[ $var ] ) : $default;
			}
		} else {
			$value = wp_unslash( $array );
		}

		if ( $clean ) {
			$value = self::clean( $value );
		}

		return $value;
	}

	/**
	 * Remove key from array by path.
	 *
	 * @param $array
	 * @param $path
	 *
	 * @return void
	 *
	 * @since 1.1.9
	 */
	public static function remove_key_by_path( &$array, $path ) {
		if ( is_string( $path ) ) {
			$path = explode( '|', $path );
		}
		$ref  =& $array;
		$last = array_pop( $path );
		foreach ( $path as $key ) {
			if ( ! isset( $ref[ $key ] ) || ! is_array( $ref[ $key ] ) ) {
				return; // Path not found
			}
			$ref =& $ref[ $key ];
		}
		unset( $ref[ $last ] );
	}

	/**
	 * Get POST | GET data from $_REQUEST
	 *
	 * @param  string  $default
	 * @param  bool    $clean
	 *
	 * @param  string  $var
	 *
	 * @return array|string
	 *
	 * @since 1.0.0
	 *
	 */
	public static function get_request_data( $var = '', $default = '', $clean = true ) {
		return self::get_data( $_REQUEST, $var, $default, $clean );
	}

	/**
	 * Get POST data from $_POST
	 *
	 * @param  string  $default
	 * @param  bool    $clean
	 *
	 * @param  string  $var
	 *
	 * @return array|string
	 *
	 * @since 1.0.0
	 *
	 */
	public static function get_post_data( $var = '', $default = '', $clean = true ) {
		return self::get_data( $_POST, $var, $default, $clean );
	}

	/**
	 * Get Current Screen Id
	 *
	 * @return string
	 *
	 * @since 1.0.0
	 */
	public static function get_current_screen_id() {
		$current_screen = function_exists( 'get_current_screen' ) ? get_current_screen() : false;

		if ( ! $current_screen instanceof \WP_Screen ) {
			return '';
		}

		$current_screen = get_current_screen();

		return ( $current_screen ? $current_screen->id : '' );
	}


	/**
	 * Get all Plugin admin screens
	 *
	 * @return array|mixed|void
	 *
	 * @since 1.0.0
	 */
	public static function get_plugin_admin_screens() {
		// TODO: Can be updated with a version check when https://core.trac.wordpress.org/ticket/18857 is fixed
		$prefix = sanitize_title( __( 'Magic Link', 'magic-link' ) );

		$screens = [
			'toplevel_page_magic-link',
			$prefix . '_page_kc-ml-settings',
			$prefix . '_page_magic-link-dashboard',
			$prefix . '_page_magic-link-other-products',
			$prefix . '_page_magic-link-links',
			$prefix . '_page_magic-link-audit-log',
			$prefix . '_page_magic-link-health',
		];

		$screens = apply_filters( 'kc_ml_admin_screens', $screens );

		return $screens;
	}

	/**
	 * Is es admin screen?
	 *
	 * @param  string  $screen_id  Admin screen id
	 *
	 * @return bool
	 *
	 * @since 1.0.0
	 *
	 */
	public static function is_plugin_admin_screen( $screen_id = '' ) {
		$current_screen_id = self::get_current_screen_id();
		// Check for specific admin screen id if passed.
		if ( ! empty( $screen_id ) ) {
			if ( $current_screen_id === $screen_id ) {
				return true;
			} else {
				return false;
			}
		}

		$plugin_admin_screens = self::get_plugin_admin_screens();

		if ( in_array( $current_screen_id, $plugin_admin_screens ) ) {
			return true;
		}

		return false;
	}

	public static function create_user_token( $user ) {
		$new_token    = sha1( wp_generate_password() );
		$hashed_token = hash_hmac( 'sha256', $new_token, wp_salt() );

		$tokens[] = [
			'h_token' => $hashed_token,
			'token'   => $new_token,
			'time'    => time(),
		];

		update_user_meta( $user->ID, '_magic_link_token', $tokens );

		return $new_token;
	}

	public static function get_user_tokens( $user_id ) {
		$tokens = get_user_meta( $user_id, '_magic_link_token', true );
		$tokens = is_array( $tokens ) ? $tokens : [];

		return $tokens;
	}

	public static function validate_user_token( $user_token, $token ) {
		if ( hash_equals( $token, hash_hmac( 'sha256', $user_token, wp_salt() ) ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Revoke every active magic link for a given user. Clears both the user-meta
	 * token store (admin-generated links) and any DB-table rows (shortcode/REST links).
	 *
	 * @param  int $user_id User to revoke.
	 *
	 * @return int  Number of rows/entries removed.
	 *
	 * @since 1.4.0
	 */
	public static function revoke_user_tokens( $user_id ) {
		$user_id = (int) $user_id;
		if ( $user_id <= 0 ) {
			return 0;
		}

		$removed = 0;

		$existing = get_user_meta( $user_id, '_magic_link_token', true );
		if ( is_array( $existing ) && ! empty( $existing ) ) {
			$removed += count( $existing );
		}
		delete_user_meta( $user_id, '_magic_link_token' );

		if ( isset( $GLOBALS['wpdb'] ) && is_object( $GLOBALS['wpdb'] ) ) {
			global $wpdb;
			$table   = $wpdb->prefix . 'kc_ml_magic_links';
			$deleted = $wpdb->delete( $table, array( 'user_id' => $user_id ), array( '%d' ) );
			if ( false !== $deleted ) {
				$removed += (int) $deleted;
			}
		}

		do_action( 'kc_ml_user_tokens_revoked', $user_id, $removed );

		return $removed;
	}

	public static function create_login_link( $user ) {
		$token = self::create_user_token( $user );

		$query_args = [
			'user_id'     => $user->ID,
			'magic-token' => $token,
			'magic-link'  => 1,
		];

		return esc_url_raw( add_query_arg( $query_args, wp_login_url() ) );
	}

	public static function get_login_link( $user ) {
		if ( $user instanceof \WP_User ) {
			$user_id = $user->ID;
		} else {
			$user_id = (int) $user;
		}

		$tokens = self::get_user_tokens( $user_id );

		if ( ! empty( $tokens ) ) {
			foreach ( $tokens as $token_data ) {
				if ( empty( $token_data ) ) {
					continue;
				}

				$query_args = [
					'user_id'     => $user_id,
					'magic-token' => $token_data['token'],
					'magic-link'  => 1,
				];

				return esc_url_raw( add_query_arg( $query_args, wp_login_url() ) );
			}
		}

		return '';
	}

	public static function get_user_default_redirect( $user ) {
		// Plugin-configured redirect takes precedence over WP role-based defaults.
		// Without this, the URL saved in Settings → General → "Login Redirect"
		// is never honoured on actual sign-in.
		$settings   = function_exists( 'get_option' ) ? get_option( 'kc_ml_settings', array() ) : array();
		$configured = trim( (string) self::get_data( $settings, 'general|settings|login_redirect', '' ) );
		if ( '' !== $configured ) {
			return $configured;
		}

		if ( is_multisite() && ! get_active_blog_for_user( $user->ID ) && ! is_super_admin( $user->ID ) ) {
			$redirect_to = user_admin_url();
		} elseif ( is_multisite() && ! $user->has_cap( 'read' ) ) {
			$redirect_to = get_dashboard_url( $user->ID );
		} elseif ( ! $user->has_cap( 'edit_posts' ) ) {
			$redirect_to = $user->has_cap( 'read' ) ? admin_url( 'profile.php' ) : home_url();
		} else {
			$redirect_to = admin_url();
		}

		return $redirect_to;
	}

	public static function get_wp_login_url() {
		$url = site_url( 'wp-login.php?action=magic_link', 'login_post' );

		$url = apply_filters( 'magic_link_get_wp_login_url', $url );

		return esc_url_raw( $url );
	}

	public static function get_user_by_log_input( $input ) {
		$user = \get_user_by( 'login', $input );

		if ( ! defined( 'MAGIC_LINK_USERNAME_ONLY' ) || false === MAGIC_LINK_USERNAME_ONLY ) {
			if ( ! $user && strpos( $input, '@' ) ) {
				$user = \get_user_by( 'email', $input );
			}
		}

		return $user;
	}

	/**
	 * Prepare Tooltip html
	 *
	 * @param  string  $tooltip_text
	 *
	 * @return string
	 *
	 * @since 1.4.7
	 *
	 */
	public static function get_tooltip_html( $tooltip_text = '' ) {
		$tooltip_html = '';
		if ( ! empty( $tooltip_text ) ) {
			$tooltip_html = '<div class="inline-block kc-us-tooltip relative align-middle cursor-pointer ml-1 mb-1">
				<svg class="w-4 h-4 text-gray-600" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"></path></svg>
				<span class="break-words invisible rounded-lg h-auto lg:w-48 xl:w-64 tracking-wide absolute z-70 kc-us-tooltip-text bg-black text-gray-300 text-xs rounded p-3 py-2">
					' . $tooltip_text . '
					<svg class="absolute mt-2 text-black text-opacity-100 h-2.5 left-0" x="0px" y="0px" viewBox="0 0 255 255" xml:space="preserve">
						<polygon class="fill-current" points="0,0 127.5,127.5 255,0"/>
					</svg>
				</span>
			</div>';
		}

		return $tooltip_html;
	}

	public static function generate_and_send_magic_link( $data ) {
		$magic_link = self::generate_magic_link( $data );

		if ( is_wp_error( $magic_link ) ) {
			return $magic_link;
		}

		$email = self::get_data( $data, 'email', '' );

		$user = get_user_by( 'email', $email );

		do_action( 'kc_ml_link_generated', $user ? (int) $user->ID : 0, array(
			'email' => $email,
		) );

		$sent = self::send_magic_link_email( $user, $magic_link );

		if ( ! $sent ) {
			return new \WP_Error(
				'kc_ml_email_error',
				__( 'Could not send email. Please try again.', 'magic-link' )
			);
		}

		return (object) [
			'success' => true,
			'message' => __( 'Magic link sent to your email!', 'magic-link' ),
		];
	}


	public static function maybe_create_trusted_domain_user( $email ) {
		if ( ! KC_ML()->is_pro() ) {
			return new \WP_Error( 'kc_ml_no_user', __( 'No account found with this email address.', 'magic-link' ) );
		}

		$settings = KC_ML()->get_settings();

		$enabled = (bool) self::get_data( $settings, 'security|trusted_domains|trusted_domains_enabled', false );
		if ( ! $enabled ) {
			return new \WP_Error( 'kc_ml_no_user', __( 'No account found with this email address.', 'magic-link' ) );
		}

		$domain       = strtolower( substr( strrchr( $email, '@' ), 1 ) );
		$domains_raw  = (string) self::get_data( $settings, 'security|trusted_domains|trusted_domains', '' );
		$trusted_list = array_filter( array_map( 'trim', explode( "\n", $domains_raw ) ) );

		if ( ! in_array( $domain, $trusted_list, true ) ) {
			return new \WP_Error( 'kc_ml_no_user', __( 'No account found with this email address.', 'magic-link' ) );
		}

		$username = self::generate_username_from_email( $email );
		$user_id  = \wp_create_user( $username, \wp_generate_password(), $email );

		if ( is_wp_error( $user_id ) ) {
			return new \WP_Error( 'kc_ml_email_error', __( 'Could not create user account.', 'magic-link' ) );
		}

		$role = self::get_data( $settings, 'security|trusted_domains|trusted_domains_new_user_role', 'subscriber' );
		$new_user = new \WP_User( $user_id );
		$new_user->set_role( $role );

		do_action( 'kc_ml_trusted_domain_user_created', $user_id, $email, $role );

		return get_user_by( 'id', $user_id );
	}

	public static function generate_username_from_email( $email ) {
		$username = sanitize_user( substr( $email, 0, strpos( $email, '@' ) ) );

		// Ensure username is unique
		$counter           = 1;
		$original_username = $username;
		while ( username_exists( $username ) ) {
			$username = $original_username . $counter;
			$counter ++;
		}

		return $username;
	}

	public static function generate_token() {
		if ( function_exists( 'random_bytes' ) ) {
			return bin2hex( random_bytes( 32 ) );
		}
		if ( function_exists( 'openssl_random_pseudo_bytes' ) ) {
			return bin2hex( openssl_random_pseudo_bytes( 32 ) );
		}
		// Last-resort fallback for environments without a CSPRNG.
		$bytes = '';
		for ( $i = 0; $i < 32; $i++ ) {
			$bytes .= chr( mt_rand( 0, 255 ) );
		}
		return bin2hex( $bytes );
	}

	public static function get_user_ip() {
		$ip_keys = [ 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR' ];

		foreach ( $ip_keys as $key ) {
			if ( isset( $_SERVER[ $key ] ) && ! empty( $_SERVER[ $key ] ) ) {
				$ips = explode( ',', $_SERVER[ $key ] );

				return trim( $ips[0] );
			}
		}

		return isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '';
	}

	public static function generate_magic_link( $data ) {
		$pre_check = apply_filters( 'kc_ml_pre_generate_magic_link', null );
		if ( is_wp_error( $pre_check ) ) {
			return $pre_check;
		}

		$email           = self::get_data( $data, 'email', '' );
		$should_register = self::get_data( $data, 'register', false );

		$email_validation = apply_filters( 'kc_ml_validate_email_for_magic_link', null, $email );
		if ( is_wp_error( $email_validation ) ) {
			return $email_validation;
		}

		$user = get_user_by( 'email', $email );

		if ( ! $user && $should_register ) {
			// Create new user
			$username = self::generate_username_from_email( $email );
			$user_id  = \wp_create_user( $username, \wp_generate_password(), $email );

			if ( is_wp_error( $user_id ) ) {
				return new \WP_Error( 'kc_ml_email_error', __( 'Could not create user account.', 'magic-link' ) );
			}

			$new_user_role = apply_filters( 'kc_ml_new_user_default_role', '' );
			if ( ! empty( $new_user_role ) ) {
				$new_user = new \WP_User( $user_id );
				$new_user->set_role( $new_user_role );
			}

			$user = get_user_by( 'id', $user_id );
		} elseif ( ! $user ) {
			$user = self::maybe_create_trusted_domain_user( $email );
			if ( is_wp_error( $user ) ) {
				return $user;
			}
		}

		// Generate magic link
		$token   = self::generate_token();
		$expires = self::calculate_expire_time();
		$gmt_now = gmdate( 'Y-m-d H:i:s' );

		$ip_address = self::get_user_ip();

		global $wpdb;

		$result = $wpdb->insert(
			$wpdb->prefix . 'kc_ml_magic_links',
			[
				'user_id'    => $user->ID,
				'token'      => $token,
				'expires'    => $expires,
				'ip_address' => $ip_address,
				'created_at' => $gmt_now,
			],
			[ '%d', '%s', '%s', '%s', '%s' ]
		);

		if ( ! $result ) {
			return new \WP_Error(
				'kc_ml_generate_link_error',
				__( 'Could not generate magic link.', 'magic-link' )
			);
		}

		// Send email
		$magic_link = add_query_arg( [
			'magic_link'  => '1',
			'magic_token' => $token,
		], home_url() );

		return $magic_link;
	}

	/**
	 * Create a magic link for a given user ID without running pre-check filters.
	 *
	 * Used for system-initiated links (e.g. outgoing email injection) where
	 * brute force, throttling, and domain restriction filters should not apply.
	 *
	 * @param int $user_id The user ID.
	 *
	 * @return string|\WP_Error The magic link URL or WP_Error on failure.
	 *
	 * @since 1.2.0
	 */
	public static function create_magic_link_for_user( $user_id ) {
		$user = get_user_by( 'id', $user_id );

		if ( ! $user ) {
			return new \WP_Error(
				'kc_ml_invalid_user',
				__( 'Invalid user.', 'magic-link' )
			);
		}

		$token   = self::generate_token();
		$expires = self::calculate_expire_time();
		$gmt_now = gmdate( 'Y-m-d H:i:s' );

		global $wpdb;

		$result = $wpdb->insert(
			$wpdb->prefix . 'kc_ml_magic_links',
			[
				'user_id'    => $user->ID,
				'token'      => $token,
				'expires'    => $expires,
				'ip_address' => '',
				'created_at' => $gmt_now,
			],
			[ '%d', '%s', '%s', '%s', '%s' ]
		);

		if ( ! $result ) {
			return new \WP_Error(
				'kc_ml_generate_link_error',
				__( 'Could not generate magic link.', 'magic-link' )
			);
		}

		return add_query_arg( [
			'magic_link'  => '1',
			'magic_token' => $token,
		], home_url() );
	}

	public static function send_magic_link_email( $user, $magic_link ) {
		if ( ! $user instanceof \WP_User ) {
			return false;
		}

		if ( ! $magic_link ) {
			return false;
		}
		$settings = Option::get( 'settings' );

		// Read from new email tab path; fall back to old general|settings path for backward compat.
		$subject = Helper::get_data( $settings, 'email|settings|email_subject' )
			?: Helper::get_data( $settings, 'general|settings|email_subject' );
		$template = Helper::get_data( $settings, 'email|settings|email_body' )
			?: Helper::get_data( $settings, 'general|settings|email_body' );
		$expiry      = Helper::get_data( $settings, 'general|settings|token_lifespan' );
		$expiry_unit = Helper::get_data( $settings, 'general|settings|token_lifespan_unit' );

		$replacements = [
			'{site_name}'   => get_bloginfo( 'name' ),
			'{user_name}'   => $user->display_name,
			'{user_email}'  => $user->user_email,
			'{magic_link}'  => $magic_link,
			'{magic-link}'  => $magic_link,
			'{expiry_time}' => $expiry,
			'{expiry_unit}' => $expiry_unit,
		];


		$subject = str_replace( array_keys( $replacements ), array_values( $replacements ), $subject );
		$message = str_replace( array_keys( $replacements ), array_values( $replacements ), $template );

		$headers = [
			'Content-Type: text/html; charset=UTF-8',
			'From: ' . get_bloginfo( 'name' ) . ' <' . get_option( 'admin_email' ) . '>',
		];

		if ( did_action( 'magic_link_send_magic_link' ) ) {
			return true;
		}

		do_action( 'magic_link_send_magic_link', $user );

		$sent = \wp_mail( $user->user_email, $subject, $message, $headers );

		if ( $sent ) {
			do_action( 'kc_ml_login_link_sent' );
		}

		return $sent;
	}

	/**
	 * Is show on login form enabled
	 *
	 * @return bool
	 *
	 * @since 1.1.2
	 */
	public static function is_show_on_login_form() {
		$settings = KC_ML()->get_settings();

		$show_on_login = Helper::get_data( $settings, 'general|settings|show_on_login_form', 0 );

		return (bool) $show_on_login;
	}

	public static function is_magic_link_enabled() {
		$settings = KC_ML()->get_settings();

		$magic_link_enabled = Helper::get_data( $settings, 'general|settings|enable_magic_link', 1 );

		return (bool) $magic_link_enabled;
	}

	/**
	 * Calculate expire time.
	 *
	 * @return string
	 *
	 * @since 1.1.2
	 */
	public static function calculate_expire_time() {
		$settings            = KC_ML()->get_settings();
		$token_lifespan      = Helper::get_data( $settings, 'general|settings|token_lifespan', 5 );
		$token_lifespan_unit = Helper::get_data( $settings, 'general|settings|token_lifespan_unit', 'minutes' );
		$expire_time         = time() + ( $token_lifespan * 60 );
		if ( 'hours' === $token_lifespan_unit ) {
			$expire_time = time() + ( $token_lifespan * 60 * 60 );
		} elseif ( 'days' === $token_lifespan_unit ) {
			$expire_time = time() + ( $token_lifespan * 60 * 60 * 24 );
		}

		return gmdate( 'Y-m-d H:i:s', $expire_time );
	}


	public static function get_kc_plugins_info( $force = false ) {
		// Get cached data
		$plugins_info = get_transient( 'kc_plugins_info' );

		if ( $force || false === $plugins_info ) {
			// Base plugin data
			$plugins = [
				'url-shortify' => [
					'name'         => 'url-shortify/url-shortify.php',
					'is_premium'   => true,
					'premium_slug' => 'url-shortify-premium/url-shortify.php',
					'premium_url'  => 'https://kaizencoders.com/url-shortify',
				],

				'update-urls' => [
					'name'         => 'update-urls/update-urls.php',
					'is_premium'   => true,
					'premium_slug' => 'update-urls-premium/update-urls.php',
					'premium_url'  => 'https://kaizencoders.com/update-urls',

				],

				'logify' => [
					'name'       => 'logify/logify.php',
					'is_premium' => true,
					'premium_slug' => 'logify-premium/logify.php',
					'premium_url'  => 'https://kaizencoders.com/logify',
				],

				'magic-link' => [
					'name'       => 'magic-link/magic-link.php',
					'is_premium' => false,
				],


				'social-linkz' => [
					'name'       => 'social-linkz/social-linkz.php',
					'is_premium' => false,
				],

//				'zapify'    => [
//					'name'       => 'zapify/zapify.php',
//					'is_premium' => false,
//				],
//
				'utility-kit' => [
					'name'       => 'utility-kit/utility-kit.php',
					'is_premium' => false,
				],
			];

			$plugins_info = [];
			require_once( ABSPATH . 'wp-admin/includes/plugin-install.php' );

			foreach ( $plugins as $slug => $base_data ) {
				$api = plugins_api( 'plugin_information', [
					'slug'   => $slug,
					'fields' => [
						'short_description' => true,
						'icons'             => true,
					],
				] );

				if ( ! is_wp_error( $api ) ) {
					$data = [
						'title'      => $api->name,
						'logo'       => isset( $api->icons['2x'] ) ? $api->icons['2x'] : ( isset( $api->icons['1x'] ) ? $api->icons['1x'] : ( isset( $api->icons['default'] ) ? $api->icons['default'] : '' ) ),
						'desc'       => $api->short_description,
						'plugin_url' => "https://wordpress.org/plugins/{$slug}/",
						'slug'       => $slug,
					];

					$plugins_info[ $slug ] = array_merge( $data, $base_data );
				}
			}

			// Cache for 30 days
			set_transient( 'kc_plugins_info', $plugins_info, 7 * DAY_IN_SECONDS );
		}

		return $plugins_info;
	}

}
