<?php
/**
 * Options et validation de version.
 *
 * @package CVS
 * @license GPL-2.0-or-later
 */

defined( 'ABSPATH' ) || exit;

class CVS_Settings {

	const OPTION_KEY           = 'cvs_settings';
	const NETWORK_OPTION_KEY   = 'cvs_network_settings';
	const SOURCE_OPTION_KEY    = 'cvs_settings_source';
	const LOG_KEY              = 'cvs_switch_log';
	const PREVIOUS_VERSION_KEY = 'cvs_previous_core_version';
	const PIN_SET_AT_KEY       = 'cvs_pin_set_at';
	const EXPORT_VERSION       = '1.0.0';

	/** @var CVS_Plugin */
	private $plugin;

	public function __construct( CVS_Plugin $plugin ) {
		$this->plugin = $plugin;
	}

	public static function defaults() {
		return array(
			'target_version'          => '',
			'use_custom_url'          => false,
			'custom_download_url'     => '',
			'auto_clear_target'       => false,
			'show_warnings'           => true,
			'block_auto_core_updates' => true,
			'package_locale'          => '',
			'package_type'            => 'full',
			'allow_insecure_targets'  => false,
			'require_backup_ack'      => false,
			'pin_expires_days'        => 0,
			'email_on_switch'         => true,
			'show_insecure_in_picker' => false,
		);
	}

	public function uses_network_source() {
		if ( ! is_multisite() ) {
			return false;
		}
		return 'local' !== get_option( self::SOURCE_OPTION_KEY, 'network' );
	}

	public function is_site_readonly() {
		return is_multisite() && ! is_network_admin() && $this->uses_network_source();
	}

	public function set_settings_source( $source ) {
		$source = ( 'local' === $source ) ? 'local' : 'network';
		update_option( self::SOURCE_OPTION_KEY, $source );
		return $source;
	}

	public function get_settings_source() {
		if ( ! is_multisite() ) {
			return 'local';
		}
		$default = is_main_site() ? 'local' : 'network';
		return get_option( self::SOURCE_OPTION_KEY, $default );
	}

	private function read_raw_settings() {
		if ( $this->uses_network_source() ) {
			$network = get_site_option( self::NETWORK_OPTION_KEY, array() );
			if ( ! empty( $network ) ) {
				return $network;
			}
		}
		return get_option( self::OPTION_KEY, array() );
	}

	public function all() {
		return wp_parse_args( $this->read_raw_settings(), self::defaults() );
	}

	public function all_network() {
		return wp_parse_args( get_site_option( self::NETWORK_OPTION_KEY, array() ), self::defaults() );
	}

	public function get( $key, $default = null ) {
		$all = $this->all();
		return $all[ $key ] ?? $default;
	}

	public function save( array $input, $context = 'site' ) {
		$clean = $this->sanitize( $input );

		if ( 'network' === $context && is_multisite() ) {
			update_site_option( self::NETWORK_OPTION_KEY, $clean );
			return $clean;
		}

		update_option( self::OPTION_KEY, $clean );
		if ( is_multisite() ) {
			$this->set_settings_source( 'local' );
		}
		return $clean;
	}

	public function clear_target( $context = 'site' ) {
		$settings                        = ( 'network' === $context ) ? $this->all_network() : $this->all();
		$settings['target_version']      = '';
		$settings['use_custom_url']      = false;
		$settings['custom_download_url'] = '';
		$saved                           = $this->save( $settings, $context );
		delete_option( self::PIN_SET_AT_KEY );
		return $saved;
	}

	public function mark_pin_set() {
		update_option( self::PIN_SET_AT_KEY, time(), false );
	}

	public function maybe_clear_expired_pin() {
		$days = (int) $this->get( 'pin_expires_days', 0 );
		if ( $days <= 0 ) {
			return false;
		}

		$target = self::sanitize_version( $this->get( 'target_version' ) );
		if ( ! $target ) {
			return false;
		}

		$set_at = (int) get_option( self::PIN_SET_AT_KEY, 0 );
		if ( ! $set_at ) {
			$this->mark_pin_set();
			return false;
		}

		if ( time() < ( $set_at + ( $days * DAY_IN_SECONDS ) ) ) {
			return false;
		}

		$this->clear_target( is_multisite() && $this->uses_network_source() ? 'network' : 'site' );
		$this->append_log(
			array(
				'action' => 'pin_expired',
				'target' => $target,
				'days'   => $days,
			)
		);
		return true;
	}

	public function pin_expires_at() {
		$days = (int) $this->get( 'pin_expires_days', 0 );
		if ( $days <= 0 ) {
			return 0;
		}
		$set_at = (int) get_option( self::PIN_SET_AT_KEY, 0 );
		return $set_at ? ( $set_at + ( $days * DAY_IN_SECONDS ) ) : 0;
	}

	/**
	 * @param string $version
	 */
	public static function sanitize_version( $version ) {
		$version = trim( (string) $version );
		if ( '' === $version ) {
			return '';
		}
		if ( ! preg_match( '/^\d+\.\d+(\.\d+)?(-[a-zA-Z0-9.]+)?$/', $version ) ) {
			return '';
		}
		return $version;
	}

	public function sanitize( array $input ) {
		$out = self::defaults();

		$out['target_version'] = self::sanitize_version( $input['target_version'] ?? '' );
		$out['show_warnings']  = ! empty( $input['show_warnings'] );

		$can_custom            = $this->plugin->has_feature( 'custom_download_url' );
		$out['use_custom_url'] = $can_custom && ! empty( $input['use_custom_url'] );

		if ( $can_custom && $out['use_custom_url'] ) {
			$url = esc_url_raw( trim( (string) ( $input['custom_download_url'] ?? '' ) ) );
			if ( $url && $this->is_allowed_download_host( $url ) ) {
				$out['custom_download_url'] = $url;
			}
		} else {
			$out['custom_download_url'] = '';
		}

		$out['auto_clear_target']       = $this->plugin->has_feature( 'auto_clear_target' ) && ! empty( $input['auto_clear_target'] );
		$out['block_auto_core_updates'] = ! empty( $input['block_auto_core_updates'] );

		$locale                = sanitize_text_field( (string) ( $input['package_locale'] ?? '' ) );
		$out['package_locale'] = preg_match( '/^[a-z]{2}_[A-Z]{2}$/', $locale ) ? $locale : '';

		$package_type        = sanitize_key( (string) ( $input['package_type'] ?? 'full' ) );
		$out['package_type'] = in_array( $package_type, array( 'full', 'no_content' ), true ) ? $package_type : 'full';

		$out['allow_insecure_targets']  = ! empty( $input['allow_insecure_targets'] );
		$out['require_backup_ack']      = ! empty( $input['require_backup_ack'] );
		$out['email_on_switch']         = ! empty( $input['email_on_switch'] );
		$out['show_insecure_in_picker'] = ! empty( $input['show_insecure_in_picker'] );

		$days                    = (int) ( $input['pin_expires_days'] ?? 0 );
		$out['pin_expires_days'] = max( 0, min( 365, $days ) );

		return $out;
	}

	/**
	 * @param string $target
	 * @param array  $posted
	 * @return true|WP_Error
	 */
	public function validate_target( $target, array $posted = array() ) {
		$preview = $this->sanitize( array_merge( $this->all(), $posted ) );
		$errors  = CVS_Preflight::validate_target_save( $target, $preview, $posted );
		if ( ! empty( $errors ) ) {
			return new WP_Error( 'cvs_preflight', implode( ' ', $errors ) );
		}
		return true;
	}

	public function remember_previous_version() {
		$current = get_bloginfo( 'version' );
		if ( $current ) {
			update_option( self::PREVIOUS_VERSION_KEY, self::sanitize_version( $current ), false );
		}
	}

	public function get_package_locale() {
		$locale = $this->get( 'package_locale', '' );
		if ( $locale ) {
			return $locale;
		}
		return get_locale();
	}

	public function is_allowed_download_host( $url ) {
		$host = wp_parse_url( $url, PHP_URL_HOST );
		if ( ! $host ) {
			return false;
		}
		$allowed = array( 'downloads.wordpress.org', 'wordpress.org' );
		return in_array( strtolower( $host ), $allowed, true );
	}

	/**
	 * @param string $version
	 */
	public function build_download_url( $version ) {
		$settings = $this->all();
		if ( ! empty( $settings['use_custom_url'] ) && ! empty( $settings['custom_download_url'] ) && $this->plugin->has_feature( 'custom_download_url' ) ) {
			return $settings['custom_download_url'];
		}

		$version = self::sanitize_version( $version );
		if ( ! $version ) {
			return '';
		}

		$locale       = $this->get_package_locale();
		$package_type = $this->get( 'package_type', 'full' );
		$suffix       = ( 'no_content' === $package_type ) ? '-no-content' : '';
		$base         = 'https://downloads.wordpress.org/release/';
		$filename     = 'wordpress-' . $version . $suffix . '.zip';

		if ( $locale && 'en_US' !== $locale ) {
			return $base . rawurlencode( $locale ) . '/' . $filename;
		}

		return $base . $filename;
	}

	/**
	 * @param string $version
	 */
	public function get_target_insights( $version ) {
		$version = self::sanitize_version( $version );
		if ( ! $version ) {
			return array();
		}
		return array(
			'security'   => CVS_Releases::get_security_status( $version ),
			'php_ok'     => CVS_Releases::php_compatible_with_target( $version ),
			'php_min'    => CVS_Releases::estimated_min_php( $version ),
			'zip_url'    => $this->build_download_url( $version ),
			'zip_exists' => $this->zip_exists( $this->build_download_url( $version ) ),
		);
	}

	/**
	 * @param string $url
	 */
	public function zip_exists( $url ) {
		if ( ! $url ) {
			return false;
		}
		$response = wp_remote_head(
			$url,
			array(
				'timeout'   => 15,
				'sslverify' => true,
			)
		);
		if ( is_wp_error( $response ) ) {
			return false;
		}
		$code = (int) wp_remote_retrieve_response_code( $response );
		return 200 === $code || 302 === $code;
	}

	public function log_limit() {
		return $this->plugin->has_feature( 'switch_log' ) ? 50 : 10;
	}

	public function append_log( $entry ) {
		$log   = get_option( self::LOG_KEY, array() );
		$log[] = array_merge(
			array( 'time' => current_time( 'mysql' ) ),
			$entry
		);
		$log   = array_slice( $log, -1 * $this->log_limit() );
		update_option( self::LOG_KEY, $log, false );
	}

	public function get_log() {
		$log = get_option( self::LOG_KEY, array() );
		return is_array( $log ) ? $log : array();
	}

	public function can_export_import() {
		return true;
	}

	public function clear_log() {
		delete_option( self::LOG_KEY );
	}

	/**
	 * @return array{version:string,plugin:string,exported_at:string,settings:array}
	 */
	public function export_payload( $context = 'site' ) {
		$settings = ( 'network' === $context ) ? $this->all_network() : $this->all();
		return array(
			'version'     => self::EXPORT_VERSION,
			'plugin'      => CVS_SLUG,
			'exported_at' => gmdate( 'c' ),
			'settings'    => $settings,
		);
	}

	public function export_json( $context = 'site' ) {
		return wp_json_encode( $this->export_payload( $context ), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE );
	}

	/**
	 * @param string|array $data
	 */
	public function import_data( $data, $context = 'site' ) {
		if ( is_string( $data ) ) {
			$data = json_decode( $data, true );
		}
		if ( ! is_array( $data ) || empty( $data['settings'] ) || ! is_array( $data['settings'] ) ) {
			return new WP_Error( 'invalid_import', __( 'Invalid import file.', 'core-version-switcher-by-volade' ) );
		}
		if ( ! empty( $data['plugin'] ) && CVS_SLUG !== $data['plugin'] ) {
			return new WP_Error( 'wrong_plugin', __( 'This file is from another plugin.', 'core-version-switcher-by-volade' ) );
		}
		return $this->save( $data['settings'], $context );
	}

	/**
	 * @return array<int, array{name:string,slug:string}>
	 */
	public static function detect_conflicts() {
		if ( ! function_exists( 'is_plugin_active' ) ) {
			require_once ABSPATH . 'wp-admin/includes/plugin.php';
		}

		$checks = array(
			'wp-downgrade/wp-downgrade.php' => 'WP Downgrade',
		);

		$conflicts = array();
		foreach ( $checks as $slug => $name ) {
			if ( is_plugin_active( $slug ) ) {
				$conflicts[] = array(
					'name' => $name,
					'slug' => $slug,
				);
			}
		}

		return $conflicts;
	}
}
