<?php

namespace WP_Defender\Component\IP;

use Exception;
use WP_Defender\Behavior\WPMUDEV;
use WP_Defender\Component;
use WP_Defender\Controller\Firewall;
use WP_Defender\Model\Setting\Global_Ip_Lockout;
use WP_Defender\Traits\Defender_Dashboard_Client;
use WP_Error;

class Global_IP extends Component {
	use Defender_Dashboard_Client;

	/**
	 * Global IP list key.
	 */
	public const LIST_KEY = 'wpdef_global_ip_list';
	public const DASHBOARD_NOTICE_REMINDER = 'wpdef_global_ip_dashoard_notice_reminder';

	/**
	 * @var array
	 */
	private $allow_list = [];

	/**
	 * @var array
	 */
	private $block_list = [];

	/**
	 * @var array Fetches data from HUB API service method.
	 */
	private $global_list = [];

	/**
	 * @var WPMUDEV
	 */
	private $wpmudev;

	/**
	 * @var Global_Ip_Lockout
	 */
	protected $model;

	public function __construct() {
		/**
		 * @var WPMUDEV
		 */
		$this->wpmudev = wd_di()->get( WPMUDEV::class );

		/**
		 * @var Global_Ip_Lockout
		 */
		$this->model = wd_di()->get( Global_Ip_Lockout::class );

		$this->init();
	}

	/**
	 * Initialize
	 *
	 * @return void
	 */
	public function init(): void {
		$global_ip_list = $this->get_global_ip_list();

		$this->global_list = is_array( $global_ip_list ) ? $global_ip_list : [];
		$this->allow_list = $global_ip_list['allow_list'] ?? [];
		$this->block_list = $global_ip_list['block_list'] ?? [];
	}

	/**
	 * Return global ip allow list from HUB.
	 */
	public function allow_list(): array {
		return $this->allow_list;
	}

	/**
	 * Return global ip block list from HUB.
	 */
	public function block_list(): array {
		return $this->block_list;
	}

	/**
	 * Verify is given ip exists in global IP allow list.
	 *
	 * @param string $ip ip address.
	 *
	 * @return bool if exists return true else false.
	 */
	public function is_ip_allowed( string $ip ): bool {
		return $this->is_ip_in_format( $ip, $this->allow_list() );
	}

	/**
	 * Verify is given ip exists in global IP block list.
	 *
	 * @param string $ip ip address.
	 *
	 * @return bool if exists return true else false.
	 */
	public function is_ip_blocked( string $ip ): bool {
		return $this->is_ip_in_format( $ip, $this->block_list() );
	}

	/**
	 * Check if Global IP is enabled.
	 *
	 * @return bool True for enabled or false for disabled.
	 */
	public function is_global_ip_enabled(): bool {
		return $this->model->enabled;
	}

	/**
	 * Check if Blocklist Autosync is enabled.
	 *
	 * @return bool True for enabled or false for disabled.
	 */
	public function is_blocklist_autosync_enabled(): bool {
		return $this->model->blocklist_autosync;
	}

	/**
	 * Check if permanently blocked IPs can be synced with HUB.
	 *
	 * @return bool
	 */
	public function can_blocklist_autosync(): bool {
		return $this->wpmudev->is_dash_activated() &&
			$this->wpmudev->is_site_connected_to_hub() &&
			$this->is_global_ip_enabled() &&
			$this->is_blocklist_autosync_enabled();
	}

	/**
	 * Get Global IP list from DB.
	 *
	 * @return mixed
	 */
	public function get_global_ip_list() {
		return get_site_transient( self::LIST_KEY );
	}

	/**
	 * Set Global IP list.
	 *
	 * @param array $data
	 *
	 * @return bool|WP_Error
	 */
	public function set_global_ip_list( array $data ) {
		if (
			! isset( $data['allow_list'] ) ||
			! isset( $data['block_list'] ) ||
			! isset( $data['last_update_time'] ) ||
			! isset( $data['last_update_time_utc'] )
		) {
			return new WP_Error( 'defender_hub_api_missing_params', __( 'Missing parameter(s)', 'defender-security' ) );
		}

		$allow_list = [];
		$block_list = [];
		$errors = [
			'allow_list' => [],
			'block_list' => [],
		];

		if ( is_array( $data['allow_list'] ) ) {
			foreach ( $data['allow_list'] as $key => $ip ) {
				$error = $this->display_validation_message( $ip );

				if ( ! empty( $error ) ) {
					$errors['allow_list'] = array_merge( $error, $errors['allow_list'] );
					unset( $data['allow_list'][ $key ] );
				}
			}
			$allow_list = $data['allow_list'];
		}

		if ( is_array( $data['block_list'] ) ) {
			foreach ( $data['block_list'] as $key => $ip ) {
				$error = $this->display_validation_message( $ip );

				if ( ! empty( $error ) ) {
					$errors['block_list'] = array_merge( $error, $errors['block_list'] );
					unset( $data['block_list'][ $key ] );
				}
			}
			$block_list = $data['block_list'];
		}

		$value = [
			'allow_list' => $allow_list,
			'block_list' => $block_list,
			'last_update_time' => $data['last_update_time'] ?? '',
			'last_update_time_utc' => $data['last_update_time_utc'] ?? '',
		];
		$ret = set_site_transient( self::LIST_KEY, $value );
		$this->global_list = $value;
		$this->allow_list = $value['allow_list'];
		$this->block_list = $value['block_list'];

		if ( ! empty( $errors['allow_list'] ) || ! empty( $errors['block_list'] ) ) {
			return new WP_Error( 'defender_hub_api_invalid_ips', __( 'Invalid IP(s)', 'defender-security' ), $errors );
		} else {
			return $ret;
		}
	}

	/**
	 * Format Global IP list for frontend.
	 *
	 * @return array
	 */
	public function get_formated_global_ip_list( ): array {
		$data = $this->global_list;

		return [
			'allow_list' => ! empty( $data['allow_list'] ) && is_array( $data['allow_list'] ) ?
				implode( '<br>', $data['allow_list'] ) :
				'',
			'block_list' => ! empty( $data['block_list'] ) && is_array( $data['block_list'] ) ?
				implode( '<br>', $data['block_list'] ) :
				'',
			'last_update_time_utc' => ! empty( $data['last_update_time_utc'] ) ?
				$this->format_date_time( $data['last_update_time_utc'] ) :
				__( 'Never', 'defender-security' ),
			'last_update_time' => ! empty( $data['last_update_time'] ) ?
				$this->format_date_time( $data['last_update_time'] ) :
				__( 'Never', 'defender-security' ),
			'is_synced_before' => ! empty( $data )
		];
	}

	/**
	 * Fetch Global IP list from HUB if DB has old data.
	 *
	 * @return array|WP_Error On success return global ip list or on failure return WP_Error object.
	 */
	public function fetch_global_ip_list() {
		$data = $this->get_global_ip_list();

		if ( ! is_array( $data ) ) {
			$this->attach_behavior( WPMUDEV::class, WPMUDEV::class );

			try {
				$data = $this->make_wpmu_free_request( WPMUDEV::API_GLOBAL_IP_LIST );
			} catch ( Exception $e ) {
				return new WP_Error( 'defender_hub_api_invalid_returned', $e->getMessage() );
			}

			if ( is_array( $data ) ) {
				$this->set_global_ip_list( $data );
			}
		} else {
			$updated_time = $this->fetch_global_ip_list_updated_time();

			if (
				is_wp_error( $updated_time ) ||
				empty( $updated_time['last_update_time_utc'] ) ||
				empty( $data['last_update_time_utc'] ) ||
				strtotime( $updated_time['last_update_time_utc'] ) > strtotime( $data['last_update_time_utc'] )
			) {
				$this->attach_behavior( WPMUDEV::class, WPMUDEV::class );

				try {
					$data = $this->make_wpmu_free_request( WPMUDEV::API_GLOBAL_IP_LIST );
				} catch ( Exception $e ) {
					return new WP_Error( 'defender_hub_api_invalid_returned', $e->getMessage() );
				}

				if ( is_array( $data ) ) {
					$this->set_global_ip_list( $data );
				}
			}
		}

		if ( ! is_array( $data ) ) {
			$this->log( 'Global IP API Error: Fetch Global IP list', Firewall::FIREWALL_LOG );
			$this->log( var_export( $data, true ), Firewall::FIREWALL_LOG );

			return new WP_Error( 'defender_hub_api_invalid_returned', __( 'API returned invalid data format.', 'defender-security' ) );
		}

		return $data;
	}

	/**
	 * Fetch Global IP list updated time from HUB.
	 *
	 * @return array|WP_Error
	 */
	public function fetch_global_ip_list_updated_time() {
		$this->attach_behavior( WPMUDEV::class, WPMUDEV::class );

		return $this->make_wpmu_request(
			WPMUDEV::API_GLOBAL_IP_LIST,
			[
				'_fields' => 'last_update_time_utc',
			]
		);
	}

	/**
	 * Add one or more IPs to the allow_list and/or block_list.
	 *
	 * @param array $data
	 *
	 * @return array|WP_Error
	 */
	public function add_to_global_ip_list( array $params ) {
		$is_allow_list = ! empty( $params['allow_list'] );
		$is_block_list = ! empty( $params['block_list'] );
		if ( ! $is_allow_list && ! $is_block_list ) {
			return new WP_Error(
				'global_ip_invalid_params',
				__( 'Invalid Global IP parameter(s) provided.', 'defender-security' )
			);
		}

		$data = [];
		if ( $is_allow_list ) {
			$data['allow_list'] = (array) $params['allow_list'];
		}
		if ( $is_block_list ) {
			$data['block_list'] = (array) $params['block_list'];
		}

		$this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
		$ret = $this->make_wpmu_request(
			WPMUDEV::API_GLOBAL_IP_LIST,
			$data,
			[
				'method' => 'POST',
			]
		);

		if ( is_array( $ret ) ) {
			$this->set_global_ip_list( $ret );
		} else {
			$this->log( 'Global IP API Error: Add IP(s) to allow_list and/or block_list', Firewall::FIREWALL_LOG );
			$this->log( var_export( $ret, true ), Firewall::FIREWALL_LOG );
		}

		return $ret;
	}

	/**
	 * Get welcome modal closed timestamp.
	 *
	 * @return false|int
	 */
	public function get_dashboard_notice_reminder() {
		return get_site_option( self::DASHBOARD_NOTICE_REMINDER, false );
	}

	/**
	 * Delete welcome modal closed timestamp.
	 *
	 * @return void
	 */
	public function delete_dashboard_notice_reminder(): void {
		delete_site_option( self::DASHBOARD_NOTICE_REMINDER );
	}

	/**
	 * Check if notice on Defender dashboard can be displayed.
	 *
	 * @return bool
	 */
	public function is_show_dashboard_notice(): bool {
		$reminder = $this->get_dashboard_notice_reminder();
		if ( $this->is_global_ip_enabled() || empty( $reminder ) || time() < $reminder ) {
			return false;
		}

		$is_pro = $this->wpmudev->is_pro();
		$user_roles = is_user_logged_in() ? $this->get_roles( wp_get_current_user() ) : [];

		$is_show_to_user = false;
		if ( ! $is_pro && ! empty( array_intersect( $user_roles, [ $this->super_admin_slug , 'administrator'] ) ) ) {
			$is_show_to_user = true;
		} else if ( $is_pro && $this->is_wpmu_dev_admin() ) {
			$is_show_to_user = true;
		}

		return $is_show_to_user;
	}
}
