<?php
/**
 * Per-site lifecycle operations for single-site and multisite installations.
 *
 * @package SubmissionInbox
 */

namespace SUBIN\Lifecycle;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Provisions, deactivates, and removes plugin data for the current site.
 */
class SiteProvisioner {
	/**
	 * Provision the current site's schema, defaults, identity, and cron workers.
	 *
	 * @return true|\WP_Error
	 */
	public static function provision_current_site() {
		$ready = \SUBIN_Database::install_or_upgrade();
		if ( is_wp_error( $ready ) ) {
			return $ready;
		}
		$capabilities_ready = \SUBIN\Security\Capabilities::install_or_upgrade();
		if ( is_wp_error( $capabilities_ready ) ) {
			return $capabilities_ready;
		}

		\SUBIN_SettingsRepository::ensure_defaults();
		( new \SUBIN\Cloud\SiteIdentity() )->get();
		\SUBIN\Retention\RetentionService::schedule();
		\SUBIN\Cloud\CloudSyncService::schedule();

		return true;
	}

	/**
	 * Stop every recurring and legacy single-event worker for the current site.
	 *
	 * @return true|\WP_Error
	 */
	public static function deactivate_current_site() {
		\SUBIN\Retention\RetentionService::unschedule();
		\SUBIN\Cloud\CloudSyncService::unschedule();
		wp_clear_scheduled_hook( \SUBIN_Database::NOTE_ACTIVITY_REDACTION_HOOK );
		wp_clear_scheduled_hook( \SUBIN_Database::MAIL_EVENT_AUDIT_HOOK );
		delete_option( \SUBIN_SettingsRepository::LOCK_OPTION_KEY );
		$export_cleanup = self::email_log_exports()->purge_site_exports( get_current_blog_id() );

		$notification_hooks = array(
			'subin_process_notification_jobs',
			'subin_process_due_notification_jobs',
			'subin_send_lead_notification_async',
			'subin_send_form_notification_payload_async',
		);
		$cron_cleanup_error = null;
		foreach ( $notification_hooks as $hook ) {
			$unscheduled = wp_unschedule_hook( $hook, true );
			if ( is_wp_error( $unscheduled ) && null === $cron_cleanup_error ) {
				$cron_cleanup_error = $unscheduled;
			}
		}

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

		return $cron_cleanup_error ?: true;
	}

	/**
	 * Remove current-site data when requested and always stop workers.
	 *
	 * @param bool $delete_data Whether plugin data should be deleted.
	 * @return true|\WP_Error
	 */
	public static function remove_current_site( $delete_data ) {
		$deactivated = self::deactivate_current_site();
		$cleanup_errors = array();
		if ( is_wp_error( $deactivated ) ) {
			$cleanup_errors['email_log_exports'] = array(
				'code'    => $deactivated->get_error_code(),
				'message' => $deactivated->get_error_message(),
			);
		}
		$team_cleanup   = \SUBIN\Security\TeamAccessService::remove_current_site();
		if ( is_wp_error( $team_cleanup ) ) {
			$cleanup_errors['team_access'] = array(
				'code'    => $team_cleanup->get_error_code(),
				'message' => $team_cleanup->get_error_message(),
			);
		}

		$capability_cleanup = \SUBIN\Security\Capabilities::remove_current_site();
		if ( is_wp_error( $capability_cleanup ) ) {
			$cleanup_errors['roles'] = array(
				'code'    => $capability_cleanup->get_error_code(),
				'message' => $capability_cleanup->get_error_message(),
			);
		}
		delete_option( \SUBIN_Database::LOCK_OPTION );

		if ( ! $delete_data ) {
			return self::access_cleanup_result( $cleanup_errors );
		}

		\SUBIN_Database::drop_tables();
		delete_option( \SUBIN_SettingsRepository::OPTION_KEY );
		delete_option( \SUBIN_Database::OPTION_KEY );
		delete_option( \SUBIN_Database::LOCK_OPTION );
		delete_option( \SUBIN_Database::LAST_ERROR_OPTION );
		delete_option( \SUBIN_Database::NOTE_ACTIVITY_REDACTION_CURSOR_OPTION );
		delete_option( \SUBIN_Database::MAIL_EVENT_AUDIT_CURSOR_OPTION );
		delete_option( \SUBIN_Database::MAIL_EVENT_AUDIT_COMPLETE_OPTION );
		delete_option( \SUBIN_Database::LEGACY_NOTE_DELETE_CURSOR_OPTION );
		delete_option( \SUBIN_Database::LEGACY_NOTE_DELETE_COMPLETE_OPTION );
		delete_option( \SUBIN\Cloud\SiteIdentity::OPTION_KEY );
		delete_option( \SUBIN\Retention\RetentionService::LOCK_OPTION );
		delete_option( \SUBIN\Retention\RetentionService::POLICY_OPTION );

		return self::access_cleanup_result( $cleanup_errors );
	}

	/** @return \SUBIN\MailLog\EmailLogExportService */
	private static function email_log_exports() {
		return new \SUBIN\MailLog\EmailLogExportService(
			new \SUBIN\MailLog\MailEventRepository(),
			new \SUBIN\MailLog\MailEventPresenter()
		);
	}

	/**
	 * Convert per-site access cleanup failures into one lifecycle error.
	 *
	 * @param array<string,array<string,string>> $errors Cleanup failures.
	 * @return true|\WP_Error
	 */
	private static function access_cleanup_result( array $errors ) {
		if ( empty( $errors ) ) {
			return true;
		}

		return new \WP_Error(
			'subin_access_cleanup_failed',
			__( 'InboxMend could not remove every plugin-managed access grant.', 'submission-inbox' ),
			array( 'errors' => $errors )
		);
	}

	/**
	 * Execute a callback for every site while always restoring blog context.
	 *
	 * @param callable $callback Callback executed in each site context.
	 * @return true|\WP_Error
	 */
	public static function for_each_site( $callback ) {
		if ( ! is_multisite() ) {
			$result = call_user_func( $callback );

			return is_wp_error( $result ) ? $result : true;
		}

		$site_ids = get_sites(
			array(
				'fields' => 'ids',
				'number' => 0,
			)
		);
		$errors = array();

		foreach ( $site_ids as $site_id ) {
			$result = self::for_site( absint( $site_id ), $callback );
			if ( is_wp_error( $result ) ) {
				$errors[ absint( $site_id ) ] = array(
					'code'    => $result->get_error_code(),
					'message' => $result->get_error_message(),
				);
			}
		}

		if ( ! empty( $errors ) ) {
			return new \WP_Error(
				'subin_multisite_lifecycle_failed',
				__( 'Submission Inbox could not complete a lifecycle operation on every site.', 'submission-inbox' ),
				array( 'sites' => $errors )
			);
		}

		return true;
	}

	/**
	 * Execute a callback in one site's context.
	 *
	 * @param int      $site_id Site id.
	 * @param callable $callback Callback.
	 * @return mixed
	 */
	public static function for_site( $site_id, $callback ) {
		$site_id = absint( $site_id );
		if ( $site_id <= 0 ) {
			return new \WP_Error( 'subin_invalid_site', __( 'Invalid WordPress site id.', 'submission-inbox' ) );
		}

		$switched = get_current_blog_id() !== $site_id;
		if ( $switched && ! switch_to_blog( $site_id ) ) {
			return new \WP_Error( 'subin_site_switch_failed', __( 'Could not switch WordPress site context.', 'submission-inbox' ) );
		}

		try {
			return call_user_func( $callback );
		} finally {
			if ( $switched ) {
				restore_current_blog();
			}
		}
	}
}
