<?php

declare( strict_types=1 );

namespace StrykeWP\Modules\Backup;

/**
 * Manages registration, storage and validation of all Backup plugin settings.
 *
 * Backward compat: option name 'stryke_backup_options' preserved.
 * DB table suffix 'stryke_backups_log' (full name: wp_stryke_backups_log) preserved.
 * Cron hook 'stryke_scheduled_backup_event' preserved.
 * Migrated from STRYKE_Settings (procedural / global-constant approach → namespaced constants).
 *
 * @package StrykeWP\Modules\Backup
 */
class BackupSettings {

	// Preserved for backward compat with existing installations.
	public const LOG_TABLE     = 'stryke_backups_log';
	public const CRON_HOOK     = 'stryke_scheduled_backup_event';
	public const OPTION_GROUP  = 'stryke_backup_options';
	public const SETTINGS_PAGE = 'stryke-wp-backup';

	private const SETTINGS_GROUP = 'stryke_backup_settings_group';

	/**
	 * Prefix marker for encrypted values — allows detecting legacy plaintext.
	 */
	private const ENCRYPTION_PREFIX = '$stryke$';

	/**
	 * Fields that must be encrypted at rest.
	 */
	private const ENCRYPTED_FIELDS = array( 'client_secret', 'refresh_token' );

	private const DEFAULT_OPTIONS = array(
		'backup_destination' => 'local',
		'client_id'          => '',
		'client_secret'      => '',
		'refresh_token'      => '',
		'folder_id'          => '',
		'schedule_frequency' => 'weekly',
		'schedule_time'      => '03:00',
		'last_run_status'    => 'never',
		'email_enabled'      => true,
		'email_recipient'    => '',
	);

	// -----------------------------------------------------------------------
	// Encryption Helpers (DSGVO Art. 32 — protect secrets at rest)
	// -----------------------------------------------------------------------

	/**
	 * Derives a 32-byte encryption key from AUTH_KEY.
	 */
	private static function get_encryption_key(): string {
		return hash( 'sha256', AUTH_KEY, true );
	}

	/**
	 * Encrypts a plaintext string using sodium_crypto_secretbox.
	 * Returns empty string for empty input. Output is prefixed with $stryke$ marker.
	 */
	public static function encrypt( string $plaintext ): string {
		if ( '' === $plaintext ) {
			return '';
		}
		$key    = self::get_encryption_key();
		$nonce  = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
		$cipher = sodium_crypto_secretbox( $plaintext, $nonce, $key );
		return self::ENCRYPTION_PREFIX . base64_encode( $nonce . $cipher ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
	}

	/**
	 * Decrypts a value previously encrypted with encrypt().
	 * Returns the original plaintext for legacy unencrypted values (no prefix).
	 * Returns false on decryption failure.
	 */
	public static function decrypt( string $ciphertext ): string|false {
		if ( '' === $ciphertext ) {
			return '';
		}

		// Legacy plaintext — no encryption prefix.
		if ( ! str_starts_with( $ciphertext, self::ENCRYPTION_PREFIX ) ) {
			return $ciphertext;
		}

		$key     = self::get_encryption_key();
		$decoded = base64_decode( substr( $ciphertext, strlen( self::ENCRYPTION_PREFIX ) ), true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode

		if ( false === $decoded || strlen( $decoded ) < SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES ) {
			return false;
		}

		$nonce  = substr( $decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
		$cipher = substr( $decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
		$plain  = sodium_crypto_secretbox_open( $cipher, $nonce, $key );

		return false === $plain ? false : $plain;
	}

	// -----------------------------------------------------------------------
	// Static Helpers
	// -----------------------------------------------------------------------

	/**
	 * Returns the full DB table name including WP prefix.
	 */
	public static function get_table_name(): string {
		global $wpdb;
		return $wpdb->prefix . self::LOG_TABLE;
	}

	/**
	 * Creates (or upgrades) the backup log table via dbDelta.
	 * Safe to call multiple times (idempotent).
	 */
	public static function create_table(): void {
		global $wpdb;

		require_once ABSPATH . 'wp-admin/includes/upgrade.php';

		$table           = static::get_table_name();
		$charset_collate = $wpdb->get_charset_collate();

		$sql = "CREATE TABLE {$table} (
			ID BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
			timestamp DATETIME NOT NULL,
			filename VARCHAR(255) NOT NULL,
			filesize BIGINT(20) UNSIGNED NOT NULL,
			status VARCHAR(50) NOT NULL,
			google_drive_file_id VARCHAR(255),
			PRIMARY KEY (ID)
		) {$charset_collate};";

		dbDelta( $sql );
	}

	/**
	 * Returns all stored options merged with defaults.
	 *
	 * @return array<string, mixed>
	 */
	public static function get_options(): array {
		$options = get_option( self::OPTION_GROUP, array() );
		$merged  = array_merge( self::DEFAULT_OPTIONS, is_array( $options ) ? $options : array() );

		// Decrypt sensitive fields (handles legacy plaintext transparently).
		foreach ( self::ENCRYPTED_FIELDS as $field ) {
			if ( ! empty( $merged[ $field ] ) ) {
				$decrypted = self::decrypt( $merged[ $field ] );
				$merged[ $field ] = ( false !== $decrypted ) ? $decrypted : '';
			}
		}

		return $merged;
	}

	// -----------------------------------------------------------------------
	// Credential Helpers
	// -----------------------------------------------------------------------

	/**
	 * Returns the user's Google Drive credentials.
	 *
	 * @return array{client_id: string, client_secret: string, refresh_token: string}
	 */
	public static function get_active_credentials(): array {
		$options = self::get_options();

		return array(
			'client_id'     => $options['client_id'],
			'client_secret' => $options['client_secret'],
			'refresh_token' => $options['refresh_token'],
		);
	}

	/**
	 * Returns the OAuth redirect URI for Google's callback.
	 */
	public static function get_redirect_uri(): string {
		return admin_url( 'admin.php?page=' . self::SETTINGS_PAGE );
	}

	/**
	 * Whether the backup destination is Google Drive (vs. local download).
	 */
	public static function is_gdrive_mode(): bool {
		$options = self::get_options();
		return 'gdrive' === ( $options['backup_destination'] ?? 'local' );
	}

	// -----------------------------------------------------------------------
	// WordPress Settings API Registration
	// -----------------------------------------------------------------------

	/**
	 * Registers settings sections and fields for the Settings API.
	 * Page slug: stryke-wp-backup.
	 */
	public function register_settings(): void {
		register_setting(
			self::SETTINGS_GROUP,
			self::OPTION_GROUP,
			array( $this, 'sanitize_options' )
		);

		// --- Section 1: Backup Destination ---
		add_settings_section(
			'stryke_destination_section',
			'',
			'__return_null',
			self::SETTINGS_PAGE
		);

		add_settings_field(
			'backup_destination',
			__( 'Backup-Ziel', 'stryke-wp' ),
			array( $this, 'render_destination_field' ),
			self::SETTINGS_PAGE,
			'stryke_destination_section'
		);

		// --- Section 2: Schedule ---
		add_settings_section(
			'stryke_schedule_section',
			'',
			'__return_null',
			self::SETTINGS_PAGE
		);

		add_settings_field(
			'schedule_frequency',
			__( 'Häufigkeit', 'stryke-wp' ),
			array( $this, 'render_schedule_fields' ),
			self::SETTINGS_PAGE,
			'stryke_schedule_section'
		);

		// --- Section 3: Email Notification ---
		add_settings_section(
			'stryke_email_section',
			'',
			'__return_null',
			self::SETTINGS_PAGE
		);

		add_settings_field(
			'email_enabled',
			__( 'Email-Benachrichtigung', 'stryke-wp' ),
			array( $this, 'render_email_fields' ),
			self::SETTINGS_PAGE,
			'stryke_email_section'
		);
	}

	// -----------------------------------------------------------------------
	// Field Rendering
	// -----------------------------------------------------------------------

	/**
	 * Renders the backup destination selector with Google Drive sub-options.
	 */
	public function render_destination_field(): void {
		$options     = self::get_options();
		$destination = $options['backup_destination'];
		$opt         = esc_attr( self::OPTION_GROUP );
		?>
		<div class="stryke-destination-options">
			<label class="stryke-destination-option<?php echo 'local' === $destination ? ' stryke-destination-option--active' : ''; ?>">
				<input type="radio"
					   name="<?php echo $opt; ?>[backup_destination]"
					   value="local"
					   <?php checked( $destination, 'local' ); ?>
					   class="stryke-destination-radio">
				<span class="dashicons dashicons-download" aria-hidden="true"></span>
				<span class="stryke-destination-option__text">
					<strong><?php esc_html_e( 'Lokaler Download', 'stryke-wp' ); ?></strong>
					<span class="stryke-text-muted"><?php esc_html_e( 'Backups werden auf dem Server gespeichert und per Email-Link bereitgestellt.', 'stryke-wp' ); ?></span>
				</span>
			</label>

			<label class="stryke-destination-option<?php echo 'gdrive' === $destination ? ' stryke-destination-option--active' : ''; ?>">
				<input type="radio"
					   name="<?php echo $opt; ?>[backup_destination]"
					   value="gdrive"
					   <?php checked( $destination, 'gdrive' ); ?>
					   class="stryke-destination-radio">
				<span class="dashicons dashicons-cloud-upload" aria-hidden="true"></span>
				<span class="stryke-destination-option__text">
					<strong><?php esc_html_e( 'Google Drive', 'stryke-wp' ); ?></strong>
					<span class="stryke-text-muted"><?php esc_html_e( 'Backups werden automatisch auf Google Drive hochgeladen.', 'stryke-wp' ); ?></span>
				</span>
			</label>
		</div>

		<?php // Google Drive sub-options (credentials). ?>
		<div class="stryke-gdrive-options" id="stryke-gdrive-options" style="<?php echo 'gdrive' !== $destination ? 'display:none;' : ''; ?>margin-top:16px;">
			<p class="stryke-text-muted"><?php esc_html_e( 'Geben Sie Ihre Google Cloud OAuth-Credentials ein.', 'stryke-wp' ); ?></p>
			<?php
			$client_id     = $options['client_id'] ?? '';
			$client_secret = $options['client_secret'] ?? '';
			if ( '' !== $client_secret ) {
				$client_secret = '***saved***';
			}
			?>
			<p>
				<label for="client_id"><?php esc_html_e( 'Client ID', 'stryke-wp' ); ?></label><br>
				<input type="text"
					   id="client_id"
					   name="<?php echo $opt; ?>[client_id]"
					   value="<?php echo esc_attr( $client_id ); ?>"
					   placeholder="<?php esc_attr_e( 'Ihre Google Developer Client ID', 'stryke-wp' ); ?>"
					   class="regular-text">
			</p>
			<p>
				<label for="client_secret"><?php esc_html_e( 'Client Secret', 'stryke-wp' ); ?></label><br>
				<input type="password"
					   id="client_secret"
					   name="<?php echo $opt; ?>[client_secret]"
					   value="<?php echo esc_attr( $client_secret ); ?>"
					   placeholder="<?php esc_attr_e( 'Ihr Google Developer Client Secret', 'stryke-wp' ); ?>"
					   class="regular-text">
			</p>
		</div>
		<?php
	}

	/**
	 * Renders schedule frequency and time fields.
	 */
	public function render_schedule_fields(): void {
		$options    = self::get_options();
		$freq_value = esc_attr( $options['schedule_frequency'] );
		$time_value = esc_attr( $options['schedule_time'] );

		$output  = '<p><label for="schedule_frequency">' . esc_html__( 'Häufigkeit:', 'stryke-wp' ) . '</label> ';
		$output .= sprintf( '<select id="schedule_frequency" name="%s[schedule_frequency]">', esc_attr( self::OPTION_GROUP ) );
		$output .= sprintf( '<option value="daily" %s>%s</option>', selected( $freq_value, 'daily', false ), esc_html__( 'Täglich', 'stryke-wp' ) );
		$output .= sprintf( '<option value="weekly" %s>%s</option>', selected( $freq_value, 'weekly', false ), esc_html__( 'Wöchentlich', 'stryke-wp' ) );
		$output .= sprintf( '<option value="monthly" %s>%s</option>', selected( $freq_value, 'monthly', false ), esc_html__( 'Monatlich', 'stryke-wp' ) );
		$output .= '</select></p>';

		$output .= '<p><label for="schedule_time">' . esc_html__( 'Uhrzeit (HH:MM):', 'stryke-wp' ) . '</label> ';
		$output .= sprintf(
			'<input type="text" id="schedule_time" name="%s[schedule_time]" value="%s" style="max-width: 80px;"> %s</p>',
			esc_attr( self::OPTION_GROUP ),
			esc_attr( $time_value ),
			esc_html__( 'Uhr (24h-Format, z.B. 03:00)', 'stryke-wp' )
		);

		echo $output; // phpcs:ignore WordPress.Security.EscapeOutput
	}

	/**
	 * Renders email notification settings.
	 */
	public function render_email_fields(): void {
		$options        = self::get_options();
		$email_enabled  = ! empty( $options['email_enabled'] );
		$email_recipient = $options['email_recipient'] ?? '';
		$opt             = esc_attr( self::OPTION_GROUP );
		?>
		<p>
			<label class="stryke-email-toggle">
				<input type="hidden" name="<?php echo $opt; ?>[email_enabled]" value="0">
				<input type="checkbox"
					   name="<?php echo $opt; ?>[email_enabled]"
					   value="1"
					   <?php checked( $email_enabled ); ?>>
				<?php esc_html_e( 'Email nach Backup senden', 'stryke-wp' ); ?>
			</label>
		</p>
		<p>
			<label for="email_recipient"><?php esc_html_e( 'Empfänger:', 'stryke-wp' ); ?></label>
			<input type="email"
				   id="email_recipient"
				   name="<?php echo $opt; ?>[email_recipient]"
				   value="<?php echo esc_attr( $email_recipient ); ?>"
				   placeholder="<?php echo esc_attr( get_option( 'admin_email', '' ) ); ?>"
				   class="regular-text">
			<span class="stryke-text-muted"><?php esc_html_e( 'Leer = WordPress Admin-Email', 'stryke-wp' ); ?></span>
		</p>
		<?php
	}

	// -----------------------------------------------------------------------
	// Sanitization & Cron Rescheduling
	// -----------------------------------------------------------------------

	/**
	 * @param array<string, mixed> $input
	 * @return array<string, mixed>
	 */
	public function sanitize_options( array $input ): array {
		$new_options     = self::get_options();
		$old_destination = $new_options['backup_destination'];

		// --- Backup destination ---
		if ( isset( $input['backup_destination'] ) && in_array( $input['backup_destination'], array( 'local', 'gdrive' ), true ) ) {
			$new_options['backup_destination'] = $input['backup_destination'];
		}

		// Clear refresh token when switching destination.
		if ( $old_destination !== $new_options['backup_destination'] ) {
			$new_options['refresh_token'] = '';
			$new_options['folder_id']     = '';
		}

		// --- Google Drive credentials ---
		if ( isset( $input['client_id'] ) ) {
			$new_options['client_id'] = sanitize_text_field( $input['client_id'] );
		}
		if ( isset( $input['client_secret'] ) ) {
			$raw = sanitize_text_field( $input['client_secret'] );
			if ( '***saved***' === $raw || '' === $raw ) {
				$existing = get_option( self::OPTION_GROUP, array() );
				$new_options['client_secret'] = $existing['client_secret'] ?? '';
			} else {
				$new_options['client_secret'] = self::encrypt( $raw );
			}
		}

		// --- Schedule ---
		$old_freq = $new_options['schedule_frequency'];
		$old_time = $new_options['schedule_time'];

		if ( isset( $input['schedule_frequency'] ) && in_array( $input['schedule_frequency'], array( 'daily', 'weekly', 'monthly' ), true ) ) {
			$new_options['schedule_frequency'] = $input['schedule_frequency'];
		}
		if ( isset( $input['schedule_time'] ) && preg_match( '/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $input['schedule_time'] ) ) {
			$new_options['schedule_time'] = sanitize_text_field( $input['schedule_time'] );
		}

		if ( $old_freq !== $new_options['schedule_frequency'] || $old_time !== $new_options['schedule_time'] ) {
			$this->reschedule_cron( $new_options['schedule_frequency'], $new_options['schedule_time'] );
		}

		// --- Email ---
		$new_options['email_enabled']   = ! empty( $input['email_enabled'] );
		$new_options['email_recipient'] = isset( $input['email_recipient'] ) ? sanitize_email( $input['email_recipient'] ) : '';

		return $new_options;
	}

	/**
	 * Reschedules the backup cron based on new settings.
	 */
	private function reschedule_cron( string $frequency, string $time ): void {
		wp_clear_scheduled_hook( self::CRON_HOOK );

		[ $hour, $minute ] = array_map( 'intval', explode( ':', $time ) );

		$tz         = wp_timezone();
		$now        = new \DateTimeImmutable( 'now', $tz );
		$target     = $now->setTime( $hour, $minute, 0 );
		if ( $target <= $now ) {
			$target = $target->modify( '+1 day' );
		}
		$start_time = $target->getTimestamp();

		$schedule = match ( $frequency ) {
			'weekly'  => 'stryke_weekly',
			'monthly' => 'stryke_monthly',
			default   => 'daily',
		};

		wp_schedule_event( $start_time, $schedule, self::CRON_HOOK );
	}

	// -----------------------------------------------------------------------
	// Custom Cron Schedules
	// -----------------------------------------------------------------------

	/**
	 * @param array<string, array<string, mixed>> $schedules
	 * @return array<string, array<string, mixed>>
	 */
	public function add_custom_cron_schedules( array $schedules ): array {
		$schedules['stryke_weekly'] = array(
			'interval' => WEEK_IN_SECONDS,
			'display'  => esc_html__( 'Wöchentlich (STRYKE)', 'stryke-wp' ),
		);
		$schedules['stryke_monthly'] = array(
			'interval' => MONTH_IN_SECONDS,
			'display'  => esc_html__( 'Monatlich (STRYKE)', 'stryke-wp' ),
		);
		return $schedules;
	}

	/**
	 * Returns the settings group name (for settings_fields()).
	 */
	public static function get_settings_group(): string {
		return self::SETTINGS_GROUP;
	}
}
