<?php
/**
 * Geo-blocking for Security Pack.
 *
 * @package ArkHost_Security_Pack
 */

// Prevent direct access.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Geo-blocking class using IP2Location Lite database.
 */
class ARKSP_Geo_Blocking {

	/**
	 * IP2Location database file path.
	 *
	 * @var string
	 */
	private $db_path = '';

	/**
	 * IP2Location database object.
	 *
	 * @var object|null
	 */
	private $db = null;

	/**
	 * Country lookup cache.
	 *
	 * @var array
	 */
	private $cache = array();

	/**
	 * Constructor.
	 */
	public function __construct() {
		$this->db_path = ARKSP_Plugin::get_setting( 'geo_database_path', '' );

		if ( empty( $this->db_path ) ) {
			// Default path in uploads directory.
			$upload_dir    = wp_upload_dir();
			$this->db_path = $upload_dir['basedir'] . '/arkhost-security-pack/IP2LOCATION-LITE-DB1.BIN';
		}

		// Check geo-blocking immediately (constructor runs during init).
		// This blocks access to the entire website for blocked countries.
		if ( ARKSP_Plugin::get_setting( 'geo_blocking_enabled', false ) ) {
			$this->check_geo_access();
		}
	}

	/**
	 * Check if geo-blocking should apply.
	 */
	public function check_geo_access() {
		$ip = ARKSP_Helper::get_client_ip();

		if ( ! $ip ) {
			return;
		}

		// Check if IP is whitelisted.
		$ip_control = arksp()->get_component( 'ip_control' );
		if ( $ip_control && $ip_control->is_whitelisted( $ip ) ) {
			return;
		}

		// Get country code.
		$country_code = $this->get_country_code( $ip );

		if ( ! $country_code ) {
			return; // Can't determine country, allow access.
		}

		// Check if country is blocked.
		$blocked_countries = ARKSP_Plugin::get_setting( 'geo_blocked_countries', array() );

		if ( ! empty( $blocked_countries ) && in_array( $country_code, $blocked_countries, true ) ) {
			ARKSP_Activity_Log::log(
				ARKSP_Activity_Log::EVENT_GEO_BLOCKED,
				$ip,
				null,
				sprintf(
					/* translators: %s: Country code */
					__( 'Blocked country: %s', 'arkhost-security-pack' ),
					$country_code
				)
			);

			$this->block_access( $country_code );
		}
	}

	/**
	 * Get country code for an IP address.
	 *
	 * @param string $ip IP address.
	 * @return string|null Two-letter country code or null.
	 */
	public function get_country_code( $ip ) {
		// Check cache first.
		if ( isset( $this->cache[ $ip ] ) ) {
			return $this->cache[ $ip ];
		}

		// Check transient cache.
		$cache_key = 'arksp_geo_' . md5( $ip );
		$cached    = get_transient( $cache_key );

		if ( false !== $cached ) {
			$this->cache[ $ip ] = $cached;
			return $cached;
		}

		$country_code = null;

		// Try IP2Location database first.
		$country_code = $this->lookup_ip2location( $ip );

		// Fallback to PHP geoip extension.
		if ( ! $country_code && function_exists( 'geoip_country_code_by_name' ) ) {
			// phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.geoip_country_code_by_nameDeprecated
			$country_code = @geoip_country_code_by_name( $ip );
		}

		// Cache the result.
		if ( $country_code ) {
			$country_code             = strtoupper( $country_code );
			$this->cache[ $ip ]       = $country_code;
			set_transient( $cache_key, $country_code, HOUR_IN_SECONDS );
		}

		return $country_code;
	}

	/**
	 * Lookup IP using IP2Location database.
	 *
	 * @param string $ip IP address.
	 * @return string|null
	 */
	private function lookup_ip2location( $ip ) {
		if ( ! file_exists( $this->db_path ) ) {
			return null;
		}

		// Load the database reader.
		if ( null === $this->db ) {
			require_once ARKSP_PLUGIN_DIR . 'includes/class-arksp-ip2location.php';
			$this->db = new ARKSP_IP2Location( $this->db_path );
		}

		if ( ! $this->db ) {
			return null;
		}

		$record = $this->db->lookup( $ip );

		if ( $record && ! empty( $record['country_code'] ) && '-' !== $record['country_code'] ) {
			return $record['country_code'];
		}

		return null;
	}

	/**
	 * Block access for geo-blocked countries.
	 *
	 * @param string $country_code Country code.
	 */
	private function block_access( $country_code ) {
		status_header( 403 );
		nocache_headers();

		$countries   = ARKSP_Helper::get_countries();
		$country_name = isset( $countries[ $country_code ] ) ? $countries[ $country_code ] : $country_code;

		$message = sprintf(
			/* translators: %s: Country name */
			__( 'Access from %s is not permitted.', 'arkhost-security-pack' ),
			$country_name
		);

		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
			wp_send_json_error( array( 'message' => $message ), 403 );
		}

		wp_die(
			esc_html( $message ),
			esc_html__( 'Access Denied', 'arkhost-security-pack' ),
			array(
				'response'  => 403,
				'back_link' => false,
			)
		);
	}

	/**
	 * Check if geo-database exists.
	 *
	 * @return bool
	 */
	public function database_exists() {
		return file_exists( $this->db_path );
	}

	/**
	 * Get database info.
	 *
	 * @return array
	 */
	public function get_database_info() {
		$info = array(
			'exists'    => false,
			'path'      => $this->db_path,
			'size'      => 0,
			'modified'  => null,
			'type'      => '',
		);

		if ( file_exists( $this->db_path ) ) {
			$info['exists']   = true;
			$info['size']     = filesize( $this->db_path );
			$info['modified'] = filemtime( $this->db_path );

			// Try to get database type.
			if ( null === $this->db ) {
				require_once ARKSP_PLUGIN_DIR . 'includes/class-arksp-ip2location.php';
				$this->db = new ARKSP_IP2Location( $this->db_path );
			}

			if ( $this->db ) {
				$info['type'] = $this->db->get_database_type();
			}
		}

		return $info;
	}

	/**
	 * Download IP2Location Lite database.
	 *
	 * @param string $download_token IP2Location download token (optional for LITE).
	 * @return bool|WP_Error
	 */
	public function download_database( $download_token = '' ) {
		// IP2Location LITE DB1 (Country only) - free, no token required.
		$download_url = 'https://download.ip2location.com/lite/IP2LOCATION-LITE-DB1.BIN.ZIP';

		// If token provided, use the authenticated endpoint.
		if ( ! empty( $download_token ) ) {
			$download_url = 'https://www.ip2location.com/download/?token=' . urlencode( $download_token ) . '&file=DB1LITEBIN';
		}

		// Create data directory in uploads.
		$upload_dir = wp_upload_dir();
		$data_dir   = $upload_dir['basedir'] . '/arkhost-security-pack';
		if ( ! file_exists( $data_dir ) ) {
			wp_mkdir_p( $data_dir );
		}

		// Download the file.
		$tmp_file = download_url( $download_url, 300 );

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

		// Check if it's a ZIP file.
		$finfo = finfo_open( FILEINFO_MIME_TYPE );
		$mime  = finfo_file( $finfo, $tmp_file );
		finfo_close( $finfo );

		$target_path = $data_dir . '/IP2LOCATION-LITE-DB1.BIN';

		if ( 'application/zip' === $mime || 'application/x-zip-compressed' === $mime ) {
			// Extract ZIP.
			$zip = new ZipArchive();
			if ( true === $zip->open( $tmp_file ) ) {
				// Find the BIN file.
				for ( $i = 0; $i < $zip->numFiles; $i++ ) {
					$filename = $zip->getNameIndex( $i );
					if ( preg_match( '/\.BIN$/i', $filename ) ) {
						$content = $zip->getFromIndex( $i );
						// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
						file_put_contents( $target_path, $content );
						break;
					}
				}
				$zip->close();
			}
			// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
			unlink( $tmp_file );
		} else {
			// Direct BIN file.
			// phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename
			rename( $tmp_file, $target_path );
		}

		if ( file_exists( $target_path ) ) {
			// Update database path setting.
			ARKSP_Plugin::update_setting( 'geo_database_path', $target_path );
			return true;
		}

		return new WP_Error( 'download_failed', __( 'Failed to download or extract database.', 'arkhost-security-pack' ) );
	}
}
