<?php
/**
 * Config Service
 *
 * Handles plugin-wide configuration export, import, and preset management.
 * Preset configurations for save, load, export, and import.
 *
 * @package WP_PowerSuite\Core
 * @since 1.0.0
 */

namespace WP_PowerSuite\Core;

/**
 * ConfigService Class
 *
 * @since 1.0.0
 */
class ConfigService {

	private const PRESETS_OPTION = 'wp_powersuite_config_presets';
	private const PRESET_PREFIX = 'wp_powersuite_preset_';

	/**
	 * Export full plugin configuration.
	 *
	 * @return array{version: string, exported_at: string, config: array}
	 */
	public function exportConfig(): array {
		global $wpdb;

		$config = [];

		// Main settings (raw stored value, not merged with defaults)
		$config['wp_powersuite_settings'] = $this->redactSensitiveValue(
			'wp_powersuite_settings',
			NetworkScope::usesNetworkStorage() ? get_site_option( 'wp_powersuite_settings', [] ) : get_option( 'wp_powersuite_settings', [] )
		);

		// All wp_powersuite_* options (excluding presets, preset list, and main settings)
		$like = $wpdb->esc_like( 'wp_powersuite_' ) . '%';
		$preset_like = $wpdb->esc_like( self::PRESET_PREFIX ) . '%';
		$results = $wpdb->get_results(
			$wpdb->prepare(
				"SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s AND option_name NOT IN (%s, %s) AND option_name NOT LIKE %s",
				$like,
				'wp_powersuite_settings',
				self::PRESETS_OPTION,
				$preset_like
			),
			ARRAY_A
		);

		foreach ( $results as $row ) {
			if ( $this->isSensitiveKey( (string) $row['option_name'] ) ) {
				continue;
			}
			$value = maybe_unserialize( $row['option_value'] );
			$config[ $row['option_name'] ] = $this->redactSensitiveValue( (string) $row['option_name'], $value );
		}

		// Redirect Manager: export from custom table (stored there, not in options)
		$redirects_table = $wpdb->prefix . 'wpps_redirects';
		if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $redirects_table ) ) === $redirects_table ) {
			$rows = $wpdb->get_results( "SELECT * FROM {$redirects_table}", ARRAY_A );
			$config['_wpps_redirects_table'] = is_array( $rows ) ? $rows : [];
		}

		// Link Shortener: export from custom tables
		$short_links_table = $wpdb->prefix . 'wpps_short_links';
		if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $short_links_table ) ) === $short_links_table ) {
			$rows = $wpdb->get_results( "SELECT * FROM {$short_links_table}", ARRAY_A );
			$config['_wpps_short_links_table'] = is_array( $rows ) ? $rows : [];
		}

		return [
			'version'     => defined( 'WP_POWERSUITE_VERSION' ) ? WP_POWERSUITE_VERSION : '1.0.0',
			'exported_at' => gmdate( 'c' ),
			'site_url'    => get_site_url(),
			'config'      => $config,
		];
	}

	/**
	 * Import configuration. Merge or replace.
	 *
	 * @param array $data Exported config data.
	 * @param bool  $merge True to merge with existing, false to replace.
	 * @return array{success: bool, message: string, errors?: array}
	 */
	public function importConfig( array $data, bool $merge = true ): array {
		global $wpdb;

		$config = $data['config'] ?? [];
		if ( empty( $config ) ) {
			return [ 'success' => false, 'message' => __( 'Invalid configuration file. No config data found.', 'wp-powersuite' ) ];
		}

		$option_store = OptionStore::getInstance();
		$errors       = [];
		$stripped_pro = [];

		// Main settings
		if ( isset( $config['wp_powersuite_settings'] ) ) {
			$settings = $config['wp_powersuite_settings'];
			if ( is_array( $settings ) ) {
				$settings = ModuleSettingsSanitizer::sanitizeSettingsDocument( $settings, 'import' );

				$license_check = $this->validateImportLicenseForSettings( $settings, $merge );
				if ( ! $license_check['allowed'] ) {
					return [
						'success' => false,
						'message' => $license_check['message'],
					];
				}

				$filter_result = $this->filterProModulesFromSettings( $settings );
				$settings      = $filter_result['settings'];
				$stripped_pro  = $filter_result['stripped'];
				if ( $merge ) {
					$current = $option_store->getAllSettings();
					$settings = array_replace_recursive( $current, $settings );
				}
				$option_store->clearCache();
				if ( NetworkScope::usesNetworkStorage() ) {
					update_site_option( 'wp_powersuite_settings', $settings );
				} else {
					update_option( 'wp_powersuite_settings', $settings, false );
				}
				$option_store->clearCache();
			}
			unset( $config['wp_powersuite_settings'] );
		}

		// Redirect Manager: import into custom table
		if ( isset( $config['_wpps_redirects_table'] ) && is_array( $config['_wpps_redirects_table'] ) ) {
			$redirects_table = $wpdb->prefix . 'wpps_redirects';
			if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $redirects_table ) ) === $redirects_table ) {
				if ( ! $merge ) {
					$wpdb->query( "TRUNCATE TABLE `{$redirects_table}`" );
				}
				foreach ( $config['_wpps_redirects_table'] as $row ) {
					if ( ! is_array( $row ) || empty( $row['id'] ) || empty( $row['source_url'] ) ) {
						continue;
					}
					$row = [
						'id'              => sanitize_text_field( (string) $row['id'] ),
						'source_url'      => sanitize_text_field( (string) $row['source_url'] ),
						'target_url'      => esc_url_raw( (string) ( $row['target_url'] ?? '' ) ),
						'redirect_type'   => in_array( (string) ( $row['redirect_type'] ?? '301' ), [ '301', '302', '307', '308' ], true ) ? (string) $row['redirect_type'] : '301',
						'match_type'      => in_array( (string) ( $row['match_type'] ?? 'exact' ), [ 'exact', 'wildcard', 'regex' ], true ) ? (string) $row['match_type'] : 'exact',
						'is_active'       => ! empty( $row['is_active'] ) ? 1 : 0,
						'priority'        => (int) ( $row['priority'] ?? 10 ),
						'notes'           => sanitize_textarea_field( (string) ( $row['notes'] ?? '' ) ),
						'strip_params'    => ! empty( $row['strip_params'] ) ? 1 : 0,
						'custom_message'  => sanitize_text_field( (string) ( $row['custom_message'] ?? '' ) ),
						'conditions_json' => is_string( $row['conditions_json'] ?? null ) ? wp_kses_post( (string) $row['conditions_json'] ) : '[]',
						'hit_count'       => max( 0, (int) ( $row['hit_count'] ?? 0 ) ),
						'last_hit'        => ! empty( $row['last_hit'] ) ? sanitize_text_field( (string) $row['last_hit'] ) : null,
						'created_at'      => ! empty( $row['created_at'] ) ? sanitize_text_field( (string) $row['created_at'] ) : current_time( 'mysql' ),
					];
					$wpdb->replace( $redirects_table, $row, [
						'%s', '%s', '%s', '%s', '%s', '%d', '%d', '%s', '%d', '%s', '%s', '%d', '%s', '%s',
					] );
				}
				delete_transient( 'wp_powersuite_redirect_rules' );
			}
			unset( $config['_wpps_redirects_table'] );
		}

		// Link Shortener: import into custom table
		if ( isset( $config['_wpps_short_links_table'] ) && is_array( $config['_wpps_short_links_table'] ) ) {
			$short_links_table = $wpdb->prefix . 'wpps_short_links';
			if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $short_links_table ) ) === $short_links_table ) {
				if ( ! $merge ) {
					$wpdb->query( "TRUNCATE TABLE `{$short_links_table}`" );
					$analytics_table = $wpdb->prefix . 'wpps_short_links_analytics';
					if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $analytics_table ) ) === $analytics_table ) {
						$wpdb->query( "TRUNCATE TABLE `{$analytics_table}`" );
					}
				}
				$existing_codes = [];
				if ( $merge ) {
					$existing = $wpdb->get_results( "SELECT short_code FROM {$short_links_table}", ARRAY_A );
					$existing_codes = is_array( $existing ) ? array_column( $existing, 'short_code' ) : [];
				}
				$has_redirect_type = ! empty( $wpdb->get_results( $wpdb->prepare( "SHOW COLUMNS FROM {$short_links_table} LIKE %s", 'redirect_type' ) ) );
				foreach ( $config['_wpps_short_links_table'] as $row ) {
					if ( ! is_array( $row ) || empty( $row['short_code'] ) || empty( $row['destination_url'] ) ) {
						continue;
					}
					if ( $merge && in_array( $row['short_code'], $existing_codes, true ) ) {
						continue;
					}
					$insert = [
						'short_code'          => sanitize_key( (string) $row['short_code'] ),
						'destination_url'     => esc_url_raw( (string) $row['destination_url'] ),
						'active'              => isset( $row['active'] ) ? (int) $row['active'] : 1,
						'expires_at'          => ! empty( $row['expires_at'] ) ? sanitize_text_field( (string) $row['expires_at'] ) : null,
						'expire_redirect_url' => ! empty( $row['expire_redirect_url'] ) ? esc_url_raw( (string) $row['expire_redirect_url'] ) : null,
						'password_hash'       => ! empty( $row['password_hash'] ) ? sanitize_text_field( (string) $row['password_hash'] ) : null,
						'created_at'          => ! empty( $row['created_at'] ) ? sanitize_text_field( (string) $row['created_at'] ) : current_time( 'mysql' ),
						'last_clicked_at'     => null,
						'click_count'         => 0,
					];
					$format = [ '%s', '%s', '%d', '%s', '%s', '%s', '%s', '%s', '%d' ];
					if ( $has_redirect_type ) {
						$insert['redirect_type'] = isset( $row['redirect_type'] ) && in_array( (int) $row['redirect_type'], [ 301, 302 ], true ) ? (int) $row['redirect_type'] : 301;
						$format[] = '%d';
					}
					$wpdb->insert( $short_links_table, $insert, $format );
				}
				wp_cache_delete( 'wpps_short_links_table_exists', '' );
			}
			unset( $config['_wpps_short_links_table'] );
		}

		// Other options
		foreach ( $config as $option_name => $value ) {
			if ( ! is_string( $option_name ) || strpos( $option_name, 'wp_powersuite_' ) !== 0 || $this->isSensitiveKey( $option_name ) ) {
				continue;
			}
			// Skip preset payloads and preset index: import does not include blob data, so applying
			// only the list would show ghost presets (metadata without saved config).
			if ( strpos( $option_name, self::PRESET_PREFIX ) === 0 || $option_name === self::PRESETS_OPTION ) {
				continue;
			}
			if ( in_array( $option_name, [ '_wpps_redirects_table', '_wpps_short_links_table' ], true ) ) {
				continue;
			}
			try {
				if ( $merge && $option_name !== 'wp_powersuite_settings' ) {
					$current = get_option( $option_name, null );
					if ( $current !== null && is_array( $current ) && is_array( $value ) ) {
						$value = array_replace_recursive( $current, $value );
					}
				}
				if ( is_array( $value ) ) {
					$module_key = preg_replace( '/^wp_powersuite_/', '', $option_name ) ?? $option_name;
					$value      = ModuleSettingsSanitizer::sanitizeModuleSettings( (string) $module_key, $value, 'import' );
				}
				update_option( $option_name, $value, false );
			} catch ( \Throwable $e ) {
				$errors[] = sprintf( '%s: %s', $option_name, $e->getMessage() );
			}
		}

		ModuleRegistry::getInstance()->refreshCache();

		$message = __( 'Configuration imported successfully.', 'wp-powersuite' );
		if ( $stripped_pro !== [] ) {
			$message = __( 'Configuration imported successfully. Premium modules were skipped because no valid license is active.', 'wp-powersuite' );
		}

		return [
			'success' => true,
			'message'  => $message,
			'errors'   => $errors,
			'warnings' => $stripped_pro !== []
				? [
					'stripped_pro_modules' => $stripped_pro,
				]
				: [],
		];
	}

	private function isSensitiveKey( string $key ): bool {
		return (bool) preg_match( '/(?:license(?:_key)?|secret|api[_-]?key|private[_-]?key|token|password|passphrase|client[_-]?secret)/i', $key );
	}

	/**
	 * Strip premium modules from imported settings when the license is invalid.
	 *
	 * @param array<string, mixed> $settings Sanitized settings document.
	 * @return array{settings: array<string, mixed>, stripped: array<string>}
	 */
	private function filterProModulesFromSettings( array $settings ): array {
		if ( function_exists( 'wp_powersuite_is_license_valid' ) && wp_powersuite_is_license_valid() ) {
			return [
				'settings' => $settings,
				'stripped' => [],
			];
		}

		if ( ! isset( $settings['enabled_modules'] ) || ! is_array( $settings['enabled_modules'] ) ) {
			return [
				'settings' => $settings,
				'stripped' => [],
			];
		}

		$registry = ModuleRegistry::getInstance();
		$registry->init();
		$stripped = [];

		$settings['enabled_modules'] = array_values(
			array_filter(
				$settings['enabled_modules'],
				static function ( $module_id ) use ( $registry, &$stripped ): bool {
					if ( ! is_string( $module_id ) || $module_id === '' ) {
						return false;
					}
					$meta = $registry->getModuleMetadata( $module_id );
					if ( ! empty( $meta['is_pro'] ) ) {
						$stripped[] = $module_id;
						return false;
					}
					return true;
				}
			)
		);

		return [
			'settings' => $settings,
			'stripped' => $stripped,
		];
	}

	/**
	 * Block replace imports that would enable premium modules without a license.
	 *
	 * @param array<string, mixed> $settings Sanitized settings document.
	 * @param bool                 $merge    Whether this is a merge import.
	 * @return array{allowed: bool, message: string}
	 */
	private function validateImportLicenseForSettings( array $settings, bool $merge ): array {
		if ( $merge || ! function_exists( 'wp_powersuite_is_license_valid' ) || wp_powersuite_is_license_valid() ) {
			return [
				'allowed' => true,
				'message' => '',
			];
		}

		if ( ! isset( $settings['enabled_modules'] ) || ! is_array( $settings['enabled_modules'] ) ) {
			return [
				'allowed' => true,
				'message' => '',
			];
		}

		$registry = ModuleRegistry::getInstance();
		$registry->init();
		$pro_modules = [];

		foreach ( $settings['enabled_modules'] as $module_id ) {
			if ( ! is_string( $module_id ) || $module_id === '' ) {
				continue;
			}
			$meta = $registry->getModuleMetadata( $module_id );
			if ( ! empty( $meta['is_pro'] ) ) {
				$pro_modules[] = $meta['name'] ?? $module_id;
			}
		}

		if ( $pro_modules === [] ) {
			return [
				'allowed' => true,
				'message' => '',
			];
		}

		return [
			'allowed' => false,
			'message' => sprintf(
				/* translators: %s: comma-separated premium module names */
				__( 'Import rejected: a valid license is required to enable premium modules (%s).', 'wp-powersuite' ),
				implode( ', ', $pro_modules )
			),
		];
	}

	private function redactSensitiveValue( string $key, mixed $value ): mixed {
		if ( $this->isSensitiveKey( $key ) ) {
			return null;
		}

		if ( ! is_array( $value ) ) {
			return $value;
		}

		$redacted = [];
		foreach ( $value as $child_key => $child_value ) {
			$child_key_string = is_string( $child_key ) ? $child_key : (string) $child_key;
			if ( $this->isSensitiveKey( $child_key_string ) ) {
				continue;
			}
			$redacted[ $child_key ] = $this->redactSensitiveValue( $child_key_string, $child_value );
		}

		return $redacted;
	}

	/**
	 * Get saved presets.
	 *
	 * @return array<array{id: string, name: string, created_at: string}>
	 */
	public function getPresets(): array {
		$presets = get_option( self::PRESETS_OPTION, [] );
		if ( ! is_array( $presets ) ) {
			return [];
		}
		$out = [];
		foreach ( $presets as $id => $meta ) {
			$id = (string) $id;
			if ( $id === '' ) {
				continue;
			}
			// List entries without stored snapshot (e.g. after partial import) are not loadable.
			$blob = get_option( self::PRESET_PREFIX . $id, null );
			if ( ! is_array( $blob ) || empty( $blob['config'] ) || ! is_array( $blob['config'] ) ) {
				continue;
			}
			$name = is_array( $meta ) ? ( $meta['name'] ?? $id ) : (string) $meta;
			$created = is_array( $meta ) ? ( $meta['created_at'] ?? '' ) : '';
			$out[] = [
				'id'         => $id,
				'name'       => $name,
				'created_at' => $created,
			];
		}
		// Self-heal: drop orphan keys from the index so counts and UI stay accurate.
		$indexed = [];
		foreach ( $out as $row ) {
			$indexed[ $row['id'] ] = [
				'name'       => $row['name'],
				'created_at' => $row['created_at'],
			];
		}
		if ( count( $indexed ) !== count( $presets ) ) {
			update_option( self::PRESETS_OPTION, $indexed, false );
		}
		return $out;
	}

	/**
	 * Save current config as preset.
	 *
	 * @param string $name Preset name.
	 * @return array{success: bool, id?: string, message: string}
	 */
	public function savePreset( string $name ): array {
		$name = sanitize_text_field( $name );
		if ( empty( $name ) ) {
			return [ 'success' => false, 'message' => __( 'Preset name is required.', 'wp-powersuite' ) ];
		}

		$data = $this->exportConfig();
		$id = sanitize_key( $name ) . '_' . wp_generate_password( 6, false );

		$presets = get_option( self::PRESETS_OPTION, [] );
		if ( ! is_array( $presets ) ) {
			$presets = [];
		}
		$presets[ $id ] = [
			'name'       => $name,
			'created_at' => gmdate( 'c' ),
		];
		update_option( self::PRESETS_OPTION, $presets, false );
		update_option( self::PRESET_PREFIX . $id, $data, false );

		return [
			'success' => true,
			'id'      => $id,
			'message'  => __( 'Preset saved successfully.', 'wp-powersuite' ),
		];
	}

	/**
	 * Load a preset by ID.
	 *
	 * @param string $id Preset ID.
	 * @param bool   $merge True to merge, false to replace.
	 * @return array{success: bool, message: string, data?: array}
	 */
	public function loadPreset( string $id, bool $merge = false ): array {
		$id = sanitize_key( $id );
		$data = get_option( self::PRESET_PREFIX . $id, null );
		if ( $data === null || ! is_array( $data ) ) {
			return [ 'success' => false, 'message' => __( 'Preset not found.', 'wp-powersuite' ) ];
		}
		return array_merge(
			$this->importConfig( $data, $merge ),
			[ 'data' => $data ]
		);
	}

	/**
	 * Delete a preset.
	 *
	 * @param string $id Preset ID.
	 * @return array{success: bool, message: string}
	 */
	public function deletePreset( string $id ): array {
		$id = sanitize_key( $id );
		delete_option( self::PRESET_PREFIX . $id );
		$presets = get_option( self::PRESETS_OPTION, [] );
		if ( is_array( $presets ) && isset( $presets[ $id ] ) ) {
			unset( $presets[ $id ] );
			update_option( self::PRESETS_OPTION, $presets, false );
		}
		return [ 'success' => true, 'message' => __( 'Preset deleted.', 'wp-powersuite' ) ];
	}
}
