<?php
/**
 * IP blocking system with auto-blocking, manual blocking, and whitelisting.
 *
 * @package ZA_Creative_Login_Shield
 */

defined( 'ABSPATH' ) || exit;

/**
 * Manages blocked IPs, whitelist, and auto-blocking based on failed attempts.
 */
class ZACLS_IP_Blocker {

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->register_hooks();
	}

	/**
	 * Register hooks.
	 */
	private function register_hooks() {
		add_filter( 'authenticate', array( $this, 'check_blocked_ip' ), 1, 3 );
		add_action( 'init', array( $this, 'block_wp_admin_for_blocked_ips' ) );
		add_action( 'zacls_auto_block_ip', array( $this, 'auto_block_ip' ) );
	}

	/**
	 * Check if the visitor's IP is blocked before authentication.
	 *
	 * @param mixed  $user     User object.
	 * @param string $username Username.
	 * @param string $password Password.
	 * @return mixed
	 */
	public function check_blocked_ip( $user, $username, $password ) {
		$ip = ZACLS_Main::get_ip();

		if ( $this->is_ip_blocked( $ip ) ) {
			return new WP_Error(
				'ip_blocked',
				esc_html__( 'Your IP address has been blocked due to suspicious activity.', 'za-creative-login-shield' )
			);
		}

		return $user;
	}

	/**
	 * Block wp-admin access for blocked IPs.
	 */
	public function block_wp_admin_for_blocked_ips() {
		if ( ! is_admin() || defined( 'DOING_AJAX' ) || defined( 'DOING_CRON' ) ) {
			return;
		}

		$ip = ZACLS_Main::get_ip();
		if ( $this->is_ip_blocked( $ip ) ) {
			wp_die(
				esc_html__( 'Access denied. Your IP address has been blocked.', 'za-creative-login-shield' ),
				esc_html__( 'Access Denied', 'za-creative-login-shield' ),
				array( 'response' => 403 )
			);
		}
	}

	/**
	 * Check if an IP is blocked.
	 *
	 * @param string $ip IP address.
	 * @return bool
	 */
	public function is_ip_blocked( $ip ) {
		global $wpdb;

		if ( $this->is_ip_whitelisted( $ip ) ) {
			return false;
		}

		$cache_key = 'zacls_blocked_' . $ip;
		$cached    = wp_cache_get( $cache_key, 'zacls' );
		if ( false !== $cached ) {
			return (bool) $cached;
		}

		$blocked = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT id FROM {$wpdb->prefix}zacls_blocked_ips
				WHERE ip = %s AND (expires_at IS NULL OR expires_at > %s)
				LIMIT 1",
				$ip,
				current_time( 'mysql' )
			)
		);

		$result = (bool) $blocked;
		wp_cache_set( $cache_key, (int) $result, 'zacls', 60 );

		return $result;
	}

	/**
	 * Check if an IP is whitelisted.
	 *
	 * @param string $ip IP address.
	 * @return bool
	 */
	public function is_ip_whitelisted( $ip ) {
		global $wpdb;

		$cache_key = 'zacls_whitelisted_' . $ip;
		$cached    = wp_cache_get( $cache_key, 'zacls' );
		if ( false !== $cached ) {
			return (bool) $cached;
		}

		$whitelisted = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT id FROM {$wpdb->prefix}zacls_blocked_ips
				WHERE ip = %s AND type = 'whitelist' LIMIT 1",
				$ip
			)
		);

		$result = (bool) $whitelisted;
		wp_cache_set( $cache_key, (int) $result, 'zacls', 300 );

		return $result;
	}

	/**
	 * Auto-block an IP address after threshold exceeded.
	 *
	 * @param string $ip IP address.
	 */
	public function auto_block_ip( $ip ) {
		global $wpdb;

		$duration = (int) get_option( 'zacls_auto_block_duration', 1440 );
		$expires  = gmdate( 'Y-m-d H:i:s', strtotime( '+' . $duration . ' minutes' ) );

		$existing = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT id FROM {$wpdb->prefix}zacls_blocked_ips WHERE ip = %s",
				$ip
			)
		);

		if ( $existing ) {
			$wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
				$wpdb->prefix . 'zacls_blocked_ips',
				array(
					'expires_at' => $expires,
					'reason'     => esc_html__( 'Auto-blocked: too many failed attempts', 'za-creative-login-shield' ),
				),
				array( 'ip' => $ip ),
				array( '%s', '%s' ),
				array( '%s' )
			);
		} else {
			$wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
				$wpdb->prefix . 'zacls_blocked_ips',
				array(
					'ip'         => $ip,
					'type'       => 'auto',
					'reason'     => esc_html__( 'Auto-blocked: too many failed attempts', 'za-creative-login-shield' ),
					'blocked_by' => 'system',
					'expires_at' => $expires,
				),
				array( '%s', '%s', '%s', '%s', '%s' )
			);
		}

		wp_cache_delete( 'zacls_blocked_' . $ip, 'zacls' );

		ZACLS_Audit_Trail::log( 'ip_auto_blocked', sprintf( esc_html__( 'IP %s auto-blocked for excessive failed attempts', 'za-creative-login-shield' ), $ip ) );

		do_action( 'zacls_ip_blocked_notify', $ip );
	}

	/**
	 * Manually block an IP address.
	 *
	 * @param string $ip     IP address.
	 * @param string $reason Reason for blocking.
	 */
	public static function manual_block_ip( $ip, $reason = '' ) {
		global $wpdb;

		$existing = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT id FROM {$wpdb->prefix}zacls_blocked_ips WHERE ip = %s",
				$ip
			)
		);

		if ( $existing ) {
			$wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
				$wpdb->prefix . 'zacls_blocked_ips',
				array(
					'type'   => 'manual',
					'reason' => $reason ? $reason : esc_html__( 'Manually blocked by admin', 'za-creative-login-shield' ),
				),
				array( 'ip' => $ip ),
				array( '%s', '%s' ),
				array( '%s' )
			);
		} else {
			$wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
				$wpdb->prefix . 'zacls_blocked_ips',
				array(
					'ip'         => $ip,
					'type'       => 'manual',
					'reason'     => $reason ? $reason : esc_html__( 'Manually blocked by admin', 'za-creative-login-shield' ),
					'blocked_by' => strval( get_current_user_id() ),
				),
				array( '%s', '%s', '%s', '%s' )
			);
		}

		wp_cache_delete( 'zacls_blocked_' . $ip, 'zacls' );

		ZACLS_Audit_Trail::log( 'ip_manual_blocked', sprintf( esc_html__( 'IP %1$s manually blocked: %2$s', 'za-creative-login-shield' ), $ip, $reason ) );
	}

	/**
	 * Unblock an IP address.
	 *
	 * @param string $ip IP address.
	 * @return bool
	 */
	public static function unblock_ip( $ip ) {
		global $wpdb;

		wp_cache_delete( 'zacls_blocked_' . $ip, 'zacls' );
		wp_cache_delete( 'zacls_whitelisted_' . $ip, 'zacls' );

		ZACLS_Audit_Trail::log( 'ip_unblocked', sprintf( esc_html__( 'IP %s unblocked', 'za-creative-login-shield' ), $ip ) );

		return (bool) $wpdb->delete( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prefix . 'zacls_blocked_ips',
			array( 'ip' => $ip ),
			array( '%s' )
		);
	}

	/**
	 * Whitelist an IP address.
	 *
	 * @param string $ip     IP address.
	 * @param string $reason Reason for whitelisting.
	 */
	public static function whitelist_ip( $ip, $reason = '' ) {
		global $wpdb;

		$existing = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT id FROM {$wpdb->prefix}zacls_blocked_ips WHERE ip = %s",
				$ip
			)
		);

		if ( $existing ) {
			$wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
				$wpdb->prefix . 'zacls_blocked_ips',
				array(
					'type'       => 'whitelist',
					'reason'     => $reason ? $reason : esc_html__( 'Whitelisted by admin', 'za-creative-login-shield' ),
					'expires_at' => null,
				),
				array( 'ip' => $ip ),
				array( '%s', '%s' ),
				array( '%s' )
			);
		} else {
			$wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
				$wpdb->prefix . 'zacls_blocked_ips',
				array(
					'ip'         => $ip,
					'type'       => 'whitelist',
					'reason'     => $reason ? $reason : esc_html__( 'Whitelisted by admin', 'za-creative-login-shield' ),
					'blocked_by' => strval( get_current_user_id() ),
					'expires_at' => null,
				),
				array( '%s', '%s', '%s', '%s' )
			);
		}

		wp_cache_delete( 'zacls_blocked_' . $ip, 'zacls' );
		wp_cache_delete( 'zacls_whitelisted_' . $ip, 'zacls' );

		ZACLS_Audit_Trail::log( 'ip_whitelisted', sprintf( esc_html__( 'IP %1$s whitelisted: %2$s', 'za-creative-login-shield' ), $ip, $reason ) );
	}

	/**
	 * Get blocked IPs with optional type filter.
	 *
	 * @param string $type   Filter by type (auto, manual, whitelist).
	 * @param int    $limit  Records per page.
	 * @param int    $offset Offset.
	 * @return array
	 */
	public static function get_blocked_ips( $type = '', $limit = 50, $offset = 0 ) {
		global $wpdb;

		$where  = '';
		$params = array();

		if ( ! empty( $type ) ) {
			$where    = 'WHERE type = %s';
			$params[] = $type;
		}

		$params[] = $limit;
		$params[] = $offset;

		$sql = "SELECT * FROM {$wpdb->prefix}zacls_blocked_ips {$where} ORDER BY blocked_at DESC LIMIT %d OFFSET %d"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery
		return $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,PluginCheck.Security.DirectDB.UnescapedDBParameter
			$wpdb->prepare( $sql, ...$params ), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
			ARRAY_A
		);
	}

	/**
	 * Get total count of blocked IPs.
	 *
	 * @param string $type Filter by type.
	 * @return int
	 */
	public static function get_blocked_count( $type = '' ) {
		global $wpdb;

		$cache_key = 'zacls_blocked_count_' . $type;
		$cached    = wp_cache_get( $cache_key, 'zacls' );
		if ( false !== $cached ) {
			return (int) $cached;
		}

		if ( ! empty( $type ) ) {
			$count = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
				$wpdb->prepare(
					"SELECT COUNT(*) FROM {$wpdb->prefix}zacls_blocked_ips WHERE type = %s",
					$type
				)
			);
		} else {
			$count = (int) $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
				"SELECT COUNT(*) FROM {$wpdb->prefix}zacls_blocked_ips" // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
			);
		}

		wp_cache_set( $cache_key, $count, 'zacls', 300 );

		return $count;
	}
}
