<?php

declare(strict_types=1);

namespace SecurWP\Modules\Multisite;

use SecurWP\Includes\SettingsStore;
use SecurWP\Modules\Firewall\FirewallConfigService;

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

/**
 * Shared network settings and propagation service for multisite mode.
 */
class NetworkSettingsService {

	private const SETTINGS_OPTION = 'securwp_network_settings';
	private const FIREWALL_OPTION = 'securwp_firewall_settings';
	private const HARDENING_OPTION = 'securwp_hardening_settings';

	/**
	 * @return array<string,bool>
	 */
	public static function get_defaults(): array {
		return [
			'enforce_firewall'  => false,
			'enforce_hardening' => false,
			'network_lockout'   => false,
		];
	}

	/**
	 * @return array<string,bool>
	 */
	public static function get_settings(): array {
		$stored = get_site_option( self::SETTINGS_OPTION, [] );
		if ( ! is_array( $stored ) ) {
			$stored = [];
		}

		return array_merge( self::get_defaults(), self::sanitize_settings( $stored ) );
	}

	/**
	 * @param array<string,mixed> $input
	 * @return array<string,bool>
	 */
	public static function sanitize_settings( array $input ): array {
		$defaults = self::get_defaults();

		return [
			'enforce_firewall'  => rest_sanitize_boolean( $input['enforce_firewall'] ?? $defaults['enforce_firewall'] ),
			'enforce_hardening' => rest_sanitize_boolean( $input['enforce_hardening'] ?? $defaults['enforce_hardening'] ),
			'network_lockout'   => rest_sanitize_boolean( $input['network_lockout'] ?? $defaults['network_lockout'] ),
		];
	}

	/**
	 * @param array<string,mixed> $input
	 */
	public static function save_settings( array $input ): bool {
		return update_site_option( self::SETTINGS_OPTION, self::sanitize_settings( $input ) );
	}

	/**
	 * @return array<int,array<string,mixed>>
	 */
	public static function list_sites( int $limit = 100 ): array {
		$sites   = get_sites( [ 'number' => max( 1, $limit ) ] );
		$results = [];

		foreach ( $sites as $site ) {
			$blog_id = absint( $site->blog_id );
			$details = $blog_id > 0 ? get_blog_details( $blog_id ) : false;

			$results[] = [
				'blog_id'    => $blog_id,
				'domain'     => sanitize_text_field( (string) $site->domain ),
				'path'       => sanitize_text_field( (string) $site->path ),
				'blogname'   => ( $details && ! is_wp_error( $details ) ) ? wp_strip_all_tags( (string) $details->blogname ) : '',
				'registered' => sanitize_text_field( (string) $site->registered ),
			];
		}

		return $results;
	}

	/**
	 * Propagate network module templates to subsites.
	 *
	 * Each site is treated as an independent batch unit. If one module write
	 * fails after another already succeeded for that site, earlier changes are
	 * rolled back and the site result records whether restore completed cleanly.
	 *
	 * @return array<string,mixed>|\WP_Error
	 */
	public static function propagate( string $module ) {
		if ( ! is_multisite() ) {
			return new \WP_Error( 'not_multisite', 'Multisite mode is not active.', [ 'status' => 400 ] );
		}

		$module = sanitize_key( $module );
		if ( ! in_array( $module, [ 'firewall', 'hardening', 'all' ], true ) ) {
			return new \WP_Error( 'invalid_module', 'Invalid propagation target.', [ 'status' => 400 ] );
		}

		$templates = [
			'firewall'  => ( 'firewall' === $module || 'all' === $module ) ? self::get_network_template( SettingsStore::COL_FIREWALL, self::FIREWALL_OPTION ) : [],
			'hardening' => ( 'hardening' === $module || 'all' === $module ) ? self::get_network_template( SettingsStore::COL_HARDENING, self::HARDENING_OPTION ) : [],
		];
		$applied_modules = array_keys(
			array_filter(
				$templates,
				static fn( $template ): bool => is_array( $template ) && ! empty( $template )
			)
		);

		if ( empty( $applied_modules ) ) {
			return new \WP_Error(
				'missing_template',
				'No network template is available to propagate yet. Save firewall or hardening settings on the main site first.',
				[ 'status' => 400 ]
			);
		}

		$sites = get_sites( [ 'number' => 500 ] );
		$propagated = 0;
		$failed_sites = [];
		$rollback_failed_sites = [];
		$site_results = [];

		foreach ( $sites as $site ) {
			$blog_id = (int) $site->blog_id;
			$rollback_stack = [];
			$site_result = [
				'blog_id' => $blog_id,
				'status' => 'success',
				'modules' => [],
				'rollback_failed' => false,
				'rollback_failures' => [],
			];

			switch_to_blog( $blog_id );
			try {
				foreach ( $applied_modules as $applied_module ) {
					$apply_result = self::apply_module_template( $applied_module, $templates[ $applied_module ] ?? [] );
					$site_result['modules'][ $applied_module ] = [
						'status' => ! empty( $apply_result['success'] ) ? 'success' : 'failed',
						'message' => (string) ( $apply_result['message'] ?? '' ),
					];

					if ( empty( $apply_result['success'] ) ) {
						$site_result['status'] = 'failed';
						break;
					}

					$rollback_stack[] = [
						'module' => $applied_module,
						'previous' => is_array( $apply_result['previous'] ?? null ) ? $apply_result['previous'] : [],
					];
				}

				if ( $site_result['status'] !== 'success' ) {
					foreach ( $applied_modules as $applied_module ) {
						if ( isset( $site_result['modules'][ $applied_module ] ) ) {
							continue;
						}

						$site_result['modules'][ $applied_module ] = [
							'status' => 'skipped',
							'message' => 'Skipped after an earlier module failed for this site.',
						];
					}

					$rollback_result = self::rollback_site_modules( $rollback_stack );
					foreach ( $rollback_stack as $rollback_operation ) {
						$rolled_back_module = (string) ( $rollback_operation['module'] ?? '' );
						if ( $rolled_back_module === '' || ! isset( $site_result['modules'][ $rolled_back_module ] ) ) {
							continue;
						}

						$module_rollback_failed = in_array( $rolled_back_module, $rollback_result['failed_modules'], true );
						$site_result['modules'][ $rolled_back_module ]['status'] = $module_rollback_failed ? 'rollback_failed' : 'rolled_back';
						$site_result['modules'][ $rolled_back_module ]['message'] = $module_rollback_failed
							? 'Restore failed after a later module update failed for this site.'
							: 'Restored after a later module update failed for this site.';
					}

					if ( ! empty( $rollback_result['failed_modules'] ) ) {
						$site_result['status'] = 'rollback_failed';
						$site_result['rollback_failed'] = true;
						$site_result['rollback_failures'] = $rollback_result['failed_modules'];
						$rollback_failed_sites[] = $blog_id;
					}
				}
			} finally {
				restore_current_blog();
			}

			if ( $site_result['status'] === 'success' ) {
				$propagated++;
			} else {
				$failed_sites[] = $blog_id;
			}

			$site_results[] = $site_result;
		}

		$status = self::determine_batch_status( $propagated, count( $sites ) );

		return [
			'ok' => $status === 'success',
			'status' => $status,
			'module' => $module,
			'applied_modules' => $applied_modules,
			'propagated_sites' => $propagated,
			'failed_sites' => $failed_sites,
			'rollback_failed_sites' => $rollback_failed_sites,
			'site_results' => $site_results,
		];
	}

	/**
	 * @param string               $module
	 * @param array<string,mixed>  $template
	 * @return array{success: bool, message: string, previous?: array<string,mixed>}
	 */
	private static function apply_module_template( string $module, array $template ): array {
		if ( $module === 'firewall' ) {
			try {
				$result = FirewallConfigService::prepare_imported_settings(
					$template,
					FirewallConfigService::CONTEXT_NON_INTERACTIVE
				);
			} catch ( \InvalidArgumentException $exception ) {
				return [
					'success' => false,
					'message' => sanitize_text_field( $exception->getMessage() ),
				];
			}

			$previous = SettingsStore::get( SettingsStore::COL_FIREWALL );
			if ( ! FirewallConfigService::persist_non_interactive_settings( $result['settings'] ) ) {
				return [
					'success' => false,
					'message' => 'Failed to save firewall settings.',
				];
			}

			return [
				'success' => true,
				'message' => '',
				'previous' => is_array( $previous ) ? $previous : [],
			];
		}

		if ( $module === 'hardening' ) {
			$previous = SettingsStore::get( SettingsStore::COL_HARDENING );
			if ( ! SettingsStore::save( SettingsStore::COL_HARDENING, $template ) ) {
				return [
					'success' => false,
					'message' => 'Failed to save hardening settings.',
				];
			}

			return [
				'success' => true,
				'message' => '',
				'previous' => is_array( $previous ) ? $previous : [],
			];
		}

		return [
			'success' => false,
			'message' => 'Unsupported module propagation target.',
		];
	}

	/**
	 * @param array<int,array{module:string,previous:array<string,mixed>}> $rollback_stack
	 * @return array{failed_modules: array<int,string>}
	 */
	private static function rollback_site_modules( array $rollback_stack ): array {
		$failed_modules = [];

		foreach ( array_reverse( $rollback_stack ) as $rollback_operation ) {
			$module = (string) ( $rollback_operation['module'] ?? '' );
			$previous = is_array( $rollback_operation['previous'] ?? null ) ? $rollback_operation['previous'] : [];

			$restored = $module === 'firewall'
				? FirewallConfigService::persist_non_interactive_settings( $previous )
				: SettingsStore::save( SettingsStore::COL_HARDENING, $previous );

			if ( ! $restored ) {
				$failed_modules[] = $module;
			}
		}

		return [
			'failed_modules' => array_values( array_unique( $failed_modules ) ),
		];
	}

	private static function determine_batch_status( int $success_count, int $total_count ): string {
		if ( $total_count <= 0 ) {
			return 'failed';
		}

		if ( $success_count >= $total_count ) {
			return 'success';
		}

		if ( $success_count <= 0 ) {
			return 'failed';
		}

		return 'partial';
	}

	/**
	 * @return array<string,mixed>
	 */
	private static function get_network_template( string $column, string $option_name ): array {
		$stored = get_site_option( $option_name, [] );
		if ( is_array( $stored ) && ! empty( $stored ) ) {
			return $stored;
		}

		$main_site_id = function_exists( 'get_main_site_id' ) ? (int) get_main_site_id() : 0;
		if ( $main_site_id <= 0 ) {
			return [];
		}

		switch_to_blog( $main_site_id );
		try {
			$template = SettingsStore::get( $column );
		} finally {
			restore_current_blog();
		}

		return is_array( $template ) ? $template : [];
	}
}
