<?php
/**
 * Encrypted OAuth token persistence for Email Delivery providers.
 *
 * @package EmailSmtp
 * @since 1.3.0
 */

namespace EmailSmtp\Includes\OAuth;

use EmailSmtp\Includes\ConnectionStore;
use EmailSmtp\Includes\CredentialManager;
use EmailSmtp\Includes\SettingsSchema;
use WP_PowerSuite\Core\OptionStoreModuleSettingsRepository;

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

final class OAuthTokenStore {

	/**
	 * Setting key prefixes per provider.
	 *
	 * @return array<string, string>
	 */
	public static function prefixes(): array {
		return [
			'google'    => 'google',
			'microsoft' => 'microsoft',
		];
	}

	/**
	 * @return list<string>
	 */
	public static function secret_keys_for( string $provider_id ): array {
		$prefix = self::prefixes()[ $provider_id ] ?? '';
		if ( $prefix === '' ) {
			return [];
		}
		return [
			$prefix . '_client_secret',
			$prefix . '_access_token',
			$prefix . '_refresh_token',
		];
	}

	/**
	 * @param array<string, mixed> $settings
	 * @return array{
	 *   client_id:string,
	 *   client_secret:string,
	 *   access_token:string,
	 *   refresh_token:string,
	 *   expires_at:int,
	 *   connected_email:string,
	 *   tenant?:string
	 * }
	 */
	public static function read( array $settings, string $provider_id ): array {
		$prefix = self::prefixes()[ $provider_id ] ?? '';
		if ( $prefix === '' ) {
			return [
				'client_id'       => '',
				'client_secret'   => '',
				'access_token'    => '',
				'refresh_token'   => '',
				'expires_at'      => 0,
				'connected_email' => '',
			];
		}

		$constant_secret = match ( $provider_id ) {
			'google'    => 'WP_POWERSUITE_GOOGLE_CLIENT_SECRET',
			'microsoft' => 'WP_POWERSUITE_MICROSOFT_CLIENT_SECRET',
			default     => '',
		};
		$constant_id = match ( $provider_id ) {
			'google'    => 'WP_POWERSUITE_GOOGLE_CLIENT_ID',
			'microsoft' => 'WP_POWERSUITE_MICROSOFT_CLIENT_ID',
			default     => '',
		};

		$out = [
			'client_id'       => CredentialManager::is_from_constant( $constant_id )
				? (string) constant( $constant_id )
				: sanitize_text_field( (string) ( $settings[ $prefix . '_client_id' ] ?? '' ) ),
			'client_secret'   => CredentialManager::resolve( $settings, $prefix . '_client_secret', $constant_secret, $constant_secret ),
			'access_token'    => CredentialManager::resolve( $settings, $prefix . '_access_token' ),
			'refresh_token'   => CredentialManager::resolve( $settings, $prefix . '_refresh_token' ),
			'expires_at'      => (int) ( $settings[ $prefix . '_token_expires' ] ?? 0 ),
			'connected_email' => sanitize_email( (string) ( $settings[ $prefix . '_connected_email' ] ?? '' ) ),
		];

		if ( $provider_id === 'microsoft' ) {
			$tenant = sanitize_text_field( (string) ( $settings['microsoft_tenant'] ?? 'common' ) );
			$out['tenant'] = $tenant !== '' ? $tenant : 'common';
		}

		return $out;
	}

	/**
	 * Persist token fields (encrypts secrets). Does not wipe unrelated settings.
	 *
	 * @param array<string, mixed> $token_data
	 * @return array<string, mixed> Updated full settings
	 */
	public static function write( string $provider_id, array $token_data, string $connection_id = '' ): array {
		$prefix = self::prefixes()[ $provider_id ] ?? '';
		if ( $prefix === '' ) {
			return [];
		}

		$repo     = OptionStoreModuleSettingsRepository::forModule( 'email-smtp' );
		$settings = $repo->all( [] );
		$settings = is_array( $settings ) ? $settings : [];
		$settings = ConnectionStore::ensure( $settings );

		$updates = [];
		if ( isset( $token_data['access_token'] ) ) {
			$plain = (string) $token_data['access_token'];
			$updates[ $prefix . '_access_token' ] = $plain !== '' ? CredentialManager::encrypt( $plain ) : '';
		}
		if ( array_key_exists( 'refresh_token', $token_data ) ) {
			$plain = (string) $token_data['refresh_token'];
			// Preserve existing refresh token when provider omits it on refresh.
			if ( $plain !== '' ) {
				$updates[ $prefix . '_refresh_token' ] = CredentialManager::encrypt( $plain );
			}
		}
		if ( isset( $token_data['expires_at'] ) ) {
			$updates[ $prefix . '_token_expires' ] = (string) max( 0, (int) $token_data['expires_at'] );
		}
		if ( array_key_exists( 'connected_email', $token_data ) ) {
			$email = sanitize_email( (string) $token_data['connected_email'] );
			$updates[ $prefix . '_connected_email' ] = is_email( $email ) ? $email : '';
		}

		$target_id  = sanitize_key( $connection_id );
		$default_id = sanitize_key( (string) ( $settings['default_connection_id'] ?? '' ) );
		if ( $target_id === '' || ! isset( $settings['connections'][ $target_id ] ) ) {
			$target_id = $default_id;
		}

		if ( $target_id !== '' && isset( $settings['connections'][ $target_id ] ) && is_array( $settings['connections'][ $target_id ] ) ) {
			foreach ( $updates as $key => $value ) {
				$settings['connections'][ $target_id ][ $key ] = $value;
			}
		}

		// Keep flat aliases in sync only when updating the default connection.
		if ( $target_id !== '' && $target_id === $default_id ) {
			foreach ( $updates as $key => $value ) {
				$settings[ $key ] = $value;
			}
		}

		$settings = SettingsSchema::normalize( $settings );
		unset( $settings['_migration_dirty'] );
		$repo->save( $settings );

		return $settings;
	}

	/**
	 * Clear tokens but keep client credentials.
	 *
	 * @param array<string, mixed> $settings
	 * @return array<string, mixed>
	 */
	public static function clear_tokens( array $settings, string $provider_id ): array {
		$prefix = self::prefixes()[ $provider_id ] ?? '';
		if ( $prefix === '' ) {
			return $settings;
		}
		$settings[ $prefix . '_access_token' ]    = '';
		$settings[ $prefix . '_refresh_token' ]   = '';
		$settings[ $prefix . '_token_expires' ]   = '0';
		$settings[ $prefix . '_connected_email' ] = '';
		return $settings;
	}

	public static function is_connected( array $settings, string $provider_id ): bool {
		$data = self::read( $settings, $provider_id );
		return $data['refresh_token'] !== '' || $data['access_token'] !== '';
	}

	public static function access_token_expired( array $settings, string $provider_id, int $skew = 60 ): bool {
		$data = self::read( $settings, $provider_id );
		if ( $data['access_token'] === '' ) {
			return true;
		}
		$expires = (int) $data['expires_at'];
		if ( $expires <= 0 ) {
			return false;
		}
		return time() >= ( $expires - $skew );
	}
}
