<?php
/**
 * CleverReach WordPress Integration.
 *
 * @package CleverReach
 */

namespace CleverReach\WordPress\Components;

use CleverReach\WordPress\Components\BusinessLogicServices\Recipient_Service;
use CleverReach\WordPress\Components\InfrastructureServices\Config_Service;
use CleverReach\WordPress\Components\Repositories\Recipient_Repository;
use CleverReach\WordPress\Components\Utility\Database;
use CleverReach\WordPress\Components\Utility\Helper;
use CleverReach\WordPress\Components\Utility\Initializer;
use CleverReach\WordPress\Components\Utility\Task_Queue;
use CleverReach\WordPress\IntegrationCore\BusinessLogic\Entity\Tag;
use CleverReach\WordPress\IntegrationCore\BusinessLogic\Entity\TagCollection;
use CleverReach\WordPress\IntegrationCore\BusinessLogic\Interfaces\Proxy as ProxyInterface;
use CleverReach\WordPress\IntegrationCore\BusinessLogic\Interfaces\Recipients;
use CleverReach\WordPress\IntegrationCore\BusinessLogic\Proxy;
use CleverReach\WordPress\IntegrationCore\BusinessLogic\Proxy\AuthProxy;
use CleverReach\WordPress\IntegrationCore\BusinessLogic\Sync\FilterSyncTask;
use CleverReach\WordPress\IntegrationCore\BusinessLogic\Sync\RecipientDeactivateSyncTask;
use CleverReach\WordPress\IntegrationCore\BusinessLogic\Sync\RecipientSyncTask;
use CleverReach\WordPress\IntegrationCore\Infrastructure\Exceptions\InvalidConfigurationException;
use CleverReach\WordPress\IntegrationCore\Infrastructure\Interfaces\Required\Configuration;
use CleverReach\WordPress\IntegrationCore\Infrastructure\Interfaces\Required\TaskQueueStorage;
use CleverReach\WordPress\IntegrationCore\Infrastructure\Logger\Logger;
use CleverReach\WordPress\IntegrationCore\Infrastructure\ORM\Exceptions\RepositoryClassException;
use CleverReach\WordPress\IntegrationCore\Infrastructure\ServiceRegister;
use CleverReach\WordPress\IntegrationCore\Infrastructure\TaskExecution\Exceptions\QueueStorageUnavailableException;
use CleverReach\WordPress\IntegrationCore\Infrastructure\Utility\Exceptions\HttpAuthenticationException;
use CleverReach\WordPress\IntegrationCore\Infrastructure\Utility\Exceptions\HttpCommunicationException;
use CleverReach\WordPress\IntegrationCore\Infrastructure\Utility\Exceptions\HttpRequestException;
use CleverReach\WordPress\IntegrationCore\Infrastructure\Utility\Exceptions\RefreshTokenExpiredException;
use CleverReach\WordPress\Plugin;

/**
 * Class Hook_Handler
 *
 * @package CleverReach\WordPress\Components
 */
class Hook_Handler {

	/**
	 * Recipient repository
	 *
	 * @var Recipient_Repository
	 */
	private $recipient_repository;

	/**
	 * Proxy service
	 *
	 * @var Proxy
	 */
	private $proxy;

	/**
	 * Config service
	 *
	 * @var Config_Service
	 */
	private $config_service;

	/**
	 * Register all hooks
	 */
	public function register_hooks() {
		if ( !$this->get_config_service()->is_recipient_sync_enabled() ) {
			return false;
		}

		if ( is_multisite() ) {
			add_action( 'add_user_to_blog', array( $this, 'cleverreach_add_user' ), 100, 1 );
			add_action( 'profile_update', array( $this, 'cleverreach_update_multisite_user' ), 100, 2 );
			add_action( 'remove_user_from_blog', array( $this, 'cleverreach_delete_user' ), 100, 1 );
			add_action( 'grant_super_admin', array( $this, 'cleverreach_add_super_admin_multisite' ), 100, 1 );
			add_action( 'revoke_super_admin', array( $this, 'cleverreach_revoke_super_admin_multisite' ), 100, 1 );
			add_action( 'delete_blog', array( $this, 'cleverreach_delete_site' ), 100, 1 );
		} else {
			add_action( 'user_register', array( $this, 'cleverreach_add_user' ), 100, 1 );
			add_action( 'profile_update', array( $this, 'cleverreach_update_user' ), 100, 2 );
			add_action( 'delete_user', array( $this, 'cleverreach_delete_user' ), 100, 1 );
		}

		add_action( 'ure_user_permissions_update', array( $this, 'cleverreach_add_user_role' ), 100, 1 );
		add_action( 'set_user_role', array( $this, 'cleverreach_add_user_role' ), 100, 2 );
		add_action( 'updated_option', array( $this, 'cleverreach_options_hook' ), 100, 3 );

		return true;
	}

	/**
	 * Hook handler for update user
	 *
	 * @param int $user_id User id.
	 *
	 * @throws QueueStorageUnavailableException Queue storage unavailable exception.
	 * @throws RepositoryClassException
	 */
	public function cleverreach_add_user( $user_id ) {
		Initializer::register();
		if ( ! Helper::is_plugin_enabled() ) {
			return;
		}

		$this->save_extra_register_fields( $user_id );

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

		if ( ! is_super_admin( $user_id ) ) {
			Logger::logInfo( 'User change event detected. User id: ' . $user_id, 'Integration' );
			Task_Queue::enqueue( new RecipientSyncTask( array( Recipient_Service::USER_ID_PREFIX . $user_id ) ) );
		}
	}

	/**
	 * Hook handler for update user
	 *
	 * @param int   $user_id       User id.
	 * @param array $old_user_data Old user data.
	 *
	 * @throws QueueStorageUnavailableException Queue storage unavailable exception.
	 * @throws InvalidConfigurationException Invalid configuration exception.
	 * @throws HttpAuthenticationException Http authentication exception.
	 * @throws HttpCommunicationException Http communication exception.
	 * @throws RefreshTokenExpiredException Refresh token expired exception
	 * @throws RepositoryClassException
	 */
	public function cleverreach_update_user( $user_id, $old_user_data ) {
		Initializer::register();
		if ( ! Helper::is_plugin_enabled() ) {
			return;
		}

		$this->save_extra_register_fields( $user_id );

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

		if ( is_super_admin( $user_id ) ) {
			if ( ! is_multisite() ) {
				$this->cleverreach_add_super_admin( $user_id );
			}

			return;
		}

		$new_user_data = $this->get_recipient_repository()->get_user_data_by_id( $user_id );
		if ( ! empty( $old_user_data->user_email ) && $old_user_data->user_email !== $new_user_data->user_email ) {
			Task_Queue::enqueue( new RecipientDeactivateSyncTask( array( $old_user_data->user_email ) ) );
		}

		Logger::logInfo( 'User change event detected. User id: ' . $user_id, 'Integration' );
		Task_Queue::enqueue( new RecipientSyncTask( array( Recipient_Service::USER_ID_PREFIX . $user_id ) ) );
	}

	/**
	 * Hook handler for update user on multisite
	 *
	 * @param int       $user_id User id.
	 * @param /stdClass $old_user_data Old user data.
	 */
	public function cleverreach_update_multisite_user( $user_id, $old_user_data ) {
		$this->handle_multisite_users( 'cleverreach_update_user', $user_id, $old_user_data );
	}

	/**
	 * Hook handler for deleting user
	 *
	 * @param int $user_id User id.
	 *
	 * @throws QueueStorageUnavailableException Queue storage unavailable exception.
	 * @throws RepositoryClassException
	 */
	public function cleverreach_delete_user( $user_id ) {
		Initializer::register();
		if ( ! Helper::is_plugin_enabled() || ! $this->is_initial_sync_queued() || is_super_admin( $user_id ) ) {
			return;
		}

		$user = $this->get_recipient_repository()->get_user_data_by_id( $user_id );

		if ( ! empty( $user->user_email ) ) {
			Logger::logInfo( 'User role change event detected. User id: ' . $user_id, 'Integration' );
			Task_Queue::enqueue( new RecipientDeactivateSyncTask( array( $user->user_email ) ) );
		}
	}

	/**
	 * Hook handler for adding new role on single site user
	 *
	 * @param int $user_id User id.
	 * @throws QueueStorageUnavailableException Queue storage unavailable exception.
	 */
	public function cleverreach_add_user_role( $user_id ) {
		if ( ! Helper::is_plugin_enabled() || ! $this->is_initial_sync_queued() || is_super_admin( $user_id ) ) {
			return;
		}

		Logger::logInfo( 'Customer role changed event detected.' );
		Task_Queue::enqueue( new RecipientSyncTask( array( Recipient_Service::USER_ID_PREFIX . $user_id ) ) );
	}

	/**
	 * Hook handler for granting user super admin
	 *
	 * @param int $user_id User id.
	 * @throws QueueStorageUnavailableException Queue storage unavailable exception.
	 * @throws InvalidConfigurationException Invalid configuration exception.
	 * @throws HttpAuthenticationException Http authentication exception.
	 * @throws HttpCommunicationException Http communication exception.
	 * @throws RefreshTokenExpiredException Refresh token expired exception.
	 */
	public function cleverreach_add_super_admin( $user_id ) {
		if ( ! Helper::is_plugin_enabled() || ! $this->is_initial_sync_queued() ) {
			return;
		}

		$user = $this->get_recipient_repository()->get_user_data_by_id( $user_id );

		try {
			$recipient = $this->get_proxy()->getRecipient( $this->get_config_service()->getIntegrationId(), $user->user_email );
		} catch ( HttpRequestException $ex ) {
			$recipient = null;
		}

		if ( ! empty( $recipient ) ) {
			Logger::logInfo( 'User change to super admin. User id: ' . $user_id, 'Integration' );
			Task_Queue::enqueue( new RecipientDeactivateSyncTask( array( $user->user_email ) ) );
		}
	}

	/**
	 * Hook handler for granting user super admin on multisite user
	 *
	 * @param int $user_id User id.
	 */
	public function cleverreach_add_super_admin_multisite( $user_id ) {
		$this->handle_multisite_users( 'cleverreach_add_super_admin', $user_id );
	}

	/**
	 * Hook handler for revoking user super admin
	 *
	 * @param int $user_id User id.
	 * @throws QueueStorageUnavailableException Queue storage unavailable exception.
	 */
	public function cleverreach_revoke_super_admin( $user_id ) {
		if ( ! Helper::is_plugin_enabled() || ! $this->is_initial_sync_queued() ) {
			return;
		}

		Logger::logInfo( 'User change revoke from super admin. User id: ' . $user_id, 'Integration' );
		Task_Queue::enqueue( new RecipientSyncTask( array( Recipient_Service::USER_ID_PREFIX . $user_id ) ) );
	}

	/**
	 * Hook handler for revoking user super admine on multisite user
	 *
	 * @param int $user_id User id.
	 */
	public function cleverreach_revoke_super_admin_multisite( $user_id ) {
		$this->handle_multisite_users( 'cleverreach_revoke_super_admin', $user_id );
	}

	/**
	 * Hook handler for deleting site
	 *
	 * @param int $blog_id Site id.
	 */
	public function cleverreach_delete_site( $blog_id ) {
		$site_for_uninstall = get_site( $blog_id );
		$this->cleverreach_uninstall( array( $site_for_uninstall ) );
	}

	/**
	 * Plugin uninstall method
	 *
	 * @param array $sites List of sites for uninstall.
	 */
	public function cleverreach_uninstall( $sites = array() ) {
		global $wpdb;
		$installer = new Database( $wpdb );
		delete_site_option(Plugin::UPDATE_OPTION_CHECKED_KEY);
		if ( is_multisite() ) {
			if ( empty( $sites ) ) {
				$sites = get_sites();
			}

			foreach ( $sites as $site ) {
				switch_to_blog( $site->blog_id );
				$this->uninstall_handler();
				$installer->uninstall();
				restore_current_blog();
			}
		} else {
			$this->uninstall_handler();
			$installer->uninstall();
		}
	}

	/**
	 * Disconnect integration
	 *
	 * @return void
	 */
	public function cleverreach_disconnect() {
		global $wpdb;
		$installer = new Database( $wpdb );
		delete_site_option( Plugin::UPDATE_OPTION_CHECKED_KEY );
		if ( is_multisite() ) {
			$sites = get_sites();

			foreach ( $sites as $site ) {
				switch_to_blog( $site->blog_id );
				$this->uninstall_handler();
				$installer->disconnect();
				restore_current_blog();
			}
		} else {
			$this->uninstall_handler();
			$installer->disconnect();
		}
	}

	/**
	 * Hook handler for update site and role names
	 *
	 * @param array|string $option    Option name.
	 * @param array|string $old_value Option old value.
	 * @param array|string $new_value Option new value.
	 *
	 * @throws QueueStorageUnavailableException Queue storage unavailable exception.
	 * @throws RepositoryClassException
	 */
	public function cleverreach_options_hook( $option, $old_value, $new_value ) {
		Initializer::register();
		if ( ! Helper::is_plugin_enabled() || ! $this->is_initial_sync_queued() || ! is_string( $option ) ) {
			return;
		}

		$site_name_option_name = 'blogname';
		$role_option_name      = Database::table( Database::ROLES_TABLE );

		if ( $site_name_option_name === $option ) {
			$this->handle_site_hook( $old_value, $new_value );
		} elseif ( $role_option_name === $option ) {
			$this->handle_role_hook( $old_value, $new_value );
		}
	}

	/**
	 * Saves newsletter subscriber field value for user
	 *
	 * @param int $user_id User id.
	 */
	private function save_extra_register_fields( $user_id ) {
		$value = filter_input( INPUT_POST, 'cr_newsletter_status' );
		$this->get_recipient_repository()->update_users_newsletter_field( array( $user_id ), (int) $value );
	}

	/**
	 * Handle site hook
	 *
	 * @param string $old_site_name Old site name.
	 * @param string $new_site_name New site name.
	 *
	 * @throws QueueStorageUnavailableException Queue storage unavailable exception.
	 * @throws \CleverReach\WordPress\IntegrationCore\Infrastructure\ORM\Exceptions\QueryFilterInvalidParamException
	 * @throws \CleverReach\WordPress\IntegrationCore\Infrastructure\ORM\Exceptions\RepositoryNotRegisteredException
	 */
	private function handle_site_hook( $old_site_name, $new_site_name ) {
		if ( $old_site_name !== $new_site_name ) {
			/** @var Recipient_Service $recipient_service */
			$recipient_service = ServiceRegister::getService( Recipients::CLASS_NAME );
			$user_ids = $recipient_service->getAllRecipientsIds();

			$delete_tag_collection = new TagCollection();
			$tag                   = new Tag( $old_site_name, Recipient_Service::TAG_TYPE_SITE );
			$delete_tag_collection->addTag( $tag );

			Logger::logInfo( "Site name change event detected. Name changed from $old_site_name to $new_site_name.", 'Integration' );
			Task_Queue::enqueue( new FilterSyncTask() );
			Task_Queue::enqueue( new RecipientSyncTask( $user_ids, $delete_tag_collection ) );
		}
	}

	/**
	 * Handle role hook
	 *
	 * @param array $old_roles Array of old roles.
	 * @param array $new_roles Array of new roles.
	 * @throws QueueStorageUnavailableException Queue storage unavailable exception.
	 */
	private function handle_role_hook( $old_roles, $new_roles ) {
		Logger::logInfo( 'Role changed event detected.' );
		$old_role_keys = array_keys( $old_roles );
		$new_role_keys = array_keys( $new_roles );

		$changed_role_keys     = array();
		$delete_tag_collection = new TagCollection();

		// Check if there are new roles added.
		$added_roles = array_diff_assoc( $new_role_keys, $old_role_keys );

		// Check if there are roles deleted.
		$deleted_roles = array_diff_assoc( $old_role_keys, $new_role_keys );
		if ( ! empty( $deleted_roles ) ) {
			$changed_role_keys = array_merge( array_values( $deleted_roles ) );

			foreach ( $deleted_roles as $deleted_role ) {
				$tag = new Tag( $old_roles[ $deleted_role ]['name'], Recipient_Service::TAG_TYPE_ROLE );
				$delete_tag_collection->addTag( $tag );
			}
		}

		// Check if there are roles updated by name.
		foreach ( $old_roles as $old_role_key => $old_role_details ) {
			if ( ! empty( $new_roles[ $old_role_key ] ) && $old_role_details['name'] !== $new_roles[ $old_role_key ]['name'] ) {
				$changed_role_keys[] = $old_role_key;
				$tag                 = new Tag( $old_role_details['name'], Recipient_Service::TAG_TYPE_ROLE );
				$delete_tag_collection->addTag( $tag );
			}
		}

		if ( ! empty( $added_roles ) || ! empty( $changed_role_keys ) ) {
			Task_Queue::enqueue( new FilterSyncTask() );
		}

		if ( ! empty( $changed_role_keys ) ) {
			$users_ids_for_update = $this->get_recipient_repository()->get_user_ids_for_roles( $changed_role_keys );
			foreach ( $users_ids_for_update as $key => $value ) {
				$users_ids_for_update[ $key ] = Recipient_Service::USER_ID_PREFIX . $value;
			}

			if ( ! empty( $users_ids_for_update ) ) {
				Task_Queue::enqueue( new RecipientSyncTask( $users_ids_for_update, $delete_tag_collection ) );
			}
		}
	}

	/**
	 * Handle multisite users
	 *
	 * @param string $function Function to call.
	 * @param int    $user_id User id.
	 * @param array  $user_data User data.
	 */
	private function handle_multisite_users( $function, $user_id, $user_data = array() ) {
		$sites = get_sites();
		foreach ( $sites as $site ) {
			switch_to_blog( $site->blog_id );
			if ( is_user_member_of_blog( $user_id ) ) {
				if ( empty( $user_data ) ) {
					$this->{$function}( $user_id );
				} else {
					$this->{$function}( $user_id, $user_data );
				}
			}

			restore_current_blog();
		}
	}

	/**
	 * Check whether initial sync is queued
	 *
	 * @return bool
	 */
	private function is_initial_sync_queued() {
		if ( ! Helper::is_plugin_initialized() ) {
			return false;
		}

		$task_queue_storage = ServiceRegister::getService( TaskQueueStorage::CLASS_NAME );
		$initial_sync_task  = $task_queue_storage->findLatestByType( 'Initial_Sync_Task' );

		return ! empty( $initial_sync_task );
	}

	/**
	 * Handles app uninstall
	 */
	private function uninstall_handler() {
		try {
			$this->get_proxy()->deleteProductSearchEndpoint( $this->get_config_service()->getProductSearchContentId() );
		} catch ( \Exception $e ) {
			Logger::logError( 'Could not delete article search endpoint because: ' . $e->getMessage(), 'Integration' );
		}

		try {
			$this->get_proxy()->deleteReceiverEvent();
		} catch ( \Exception $e ) {
			Logger::logError( 'Could not delete receiver event because: ' . $e->getMessage(), 'Integration' );
		}

		try {
			$this->get_proxy()->deleteFormEvent();
		} catch ( \Exception $e ) {
			Logger::logError( 'Could not delete form event because: ' . $e->getMessage(), 'Integration' );
		}

		try {
			$this->get_proxy()->deleteGroupEvent();
		} catch ( \Exception $e ) {
			Logger::logError( 'Could not delete form event because: ' . $e->getMessage(), 'Integration' );
		}

		/** @var AuthProxy $auth_proxy */
		$auth_proxy = ServiceRegister::getService( AuthProxy::CLASS_NAME );
		$auth_proxy->revokeOAuth();
	}

	/**
	 * Gets proxy
	 *
	 * @return Proxy
	 */
	private function get_proxy() {
		if ( null === $this->proxy ) {
			$this->proxy = ServiceRegister::getService( ProxyInterface::CLASS_NAME );
		}

		return $this->proxy;
	}

	/**
	 * Gets config service
	 *
	 * @return Config_Service
	 */
	private function get_config_service() {
		if ( null === $this->config_service ) {
			$this->config_service = ServiceRegister::getService( Configuration::CLASS_NAME );
		}

		return $this->config_service;
	}

	/**
	 * Gets recipient repository
	 *
	 * @return Recipient_Repository
	 */
	private function get_recipient_repository() {
		if ( null === $this->recipient_repository ) {
			$this->recipient_repository = new Recipient_Repository();
		}

		return $this->recipient_repository;
	}
}
