<?php
/**
 * Email Delivery (SMTP) Module
 *
 * Configures WordPress to send emails via SMTP instead of PHP mail().
 * Supports Gmail, Mailgun, SendGrid, and custom SMTP servers.
 *
 * @package EmailSmtp
 * @since 1.0.0
 */

namespace EmailSmtp;

use EmailSmtp\Includes\SmtpSettingsHelper;
use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\NetworkScope;
use WP_PowerSuite\Core\OptionStoreModuleSettingsRepository;
use WP_PowerSuite\Email\EmailComponents;
use WP_PowerSuite\Email\EmailRenderer;

class Module extends AbstractModule {

	private const MODULE_ID      = 'email-smtp';
	private const MODULE_VERSION = '1.0.1';
	private const ENCRYPT_KEY      = 'wp_powersuite_smtp_key_v1';
	private const PASSWORD_MASK    = '••••••••••••';

	private ?array $settings_cache = null;

	private string $last_mail_error = '';

	/** @var array<string, mixed>|null Temporary overlay for test sends with unsaved form values. */
	private static ?array $test_settings_overlay = null;

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Email Delivery (SMTP)', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Route site email through SMTP—better deliverability for forms, WooCommerce, and system messages.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return [ 'admin', 'frontend', 'ajax', 'cron' ];
	}

	public function getDependencies(): array {
		return [];
	}

	protected function registerHooks(): void {
		self::registerPhpMailerHook();
		add_action( 'wp_ajax_wp_powersuite_test_smtp', [ $this, 'ajaxTestSmtp' ] );
		add_action( 'wp_mail_failed', [ $this, 'captureMailError' ] );
	}

	/**
	 * Register phpmailer_init once (also called from early-bootstrap.php).
	 */
	public static function registerPhpMailerHook(): void {
		$priority = (int) apply_filters( 'wp_powersuite_smtp_phpmailer_priority', 9999 );
		if ( ! has_action( 'phpmailer_init', [ self::class, 'configurePhpMailerStatic' ] ) ) {
			add_action( 'phpmailer_init', [ self::class, 'configurePhpMailerStatic' ], $priority );
		}
		SmtpSettingsHelper::registerWpMailFilter();
	}

	/**
	 * Static PHPMailer configurator — runs without module instance.
	 * Registered via {@see registerPhpMailerHook()} and early-bootstrap.php.
	 *
	 * @param \PHPMailer\PHPMailer\PHPMailer $phpmailer PHPMailer instance.
	 */
	public static function configurePhpMailerStatic( $phpmailer ): void {
		if ( ! class_exists( OptionStoreModuleSettingsRepository::class ) ) {
			return;
		}
		if ( ! OptionStoreModuleSettingsRepository::isModuleEnabled( 'email-smtp' ) ) {
			return;
		}
		if ( ! apply_filters( 'wp_powersuite_smtp_should_configure', true, $phpmailer ) ) {
			return;
		}

		if ( self::$test_settings_overlay !== null ) {
			$settings = SmtpSettingsHelper::mergeWithDefaults( self::$test_settings_overlay );
		} else {
			$settings = OptionStoreModuleSettingsRepository::forModule( 'email-smtp' )->all( [] );
			if ( ! is_array( $settings ) ) {
				return;
			}
			$settings = SmtpSettingsHelper::mergeWithDefaults( $settings );
		}

		if ( empty( $settings['smtp_host'] ) ) {
			return;
		}

		try {
			$phpmailer->isSMTP();
			$phpmailer->Host = SmtpSettingsHelper::sanitizeHost( (string) $settings['smtp_host'] );

			$port = (int) $settings['smtp_port'];
			if ( $port < 1 || $port > 65535 ) {
				$port = 587;
			}
			$phpmailer->Port = $port;

			$smtp_auth = $settings['smtp_auth'] === '1' || $settings['smtp_auth'] === true;
			$phpmailer->SMTPAuth = $smtp_auth;

			if ( $smtp_auth ) {
				$phpmailer->Username = sanitize_text_field( (string) $settings['smtp_username'] );
				$phpmailer->Password  = isset( $settings['smtp_password'] ) ? self::decryptPasswordStatic( (string) $settings['smtp_password'] ) : '';
			}

			$encryption = (string) $settings['smtp_encryption'];
			$phpmailer->SMTPSecure = in_array( $encryption, [ 'tls', 'ssl', '' ], true ) ? $encryption : 'tls';

			// Auto TLS: enabled for STARTTLS on 587, disabled for implicit SSL on 465.
			$phpmailer->SMTPAutoTLS = ( $encryption === 'tls' || ( $encryption === '' && $port === 587 ) );

			self::maybeApplyFromIdentity( $phpmailer, $settings );
			self::maybeApplyReplyTo( $phpmailer, $settings );

			$return_path = (string) ( $settings['return_path'] ?? '' );
			if ( is_email( $return_path ) ) {
				$phpmailer->Sender = $return_path;
			} elseif ( is_email( (string) $settings['from_email'] ) && empty( $phpmailer->Sender ) ) {
				$phpmailer->Sender = (string) $settings['from_email'];
			}

			$phpmailer->Timeout = 30;

			self::maybeEnableDebug( $phpmailer, $settings );
		} catch ( \Exception $e ) {
			if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
				error_log( 'WP PowerSuite SMTP: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			}
		}
	}

	/**
	 * @param \PHPMailer\PHPMailer\PHPMailer $phpmailer
	 * @param array<string, mixed>           $settings
	 */
	private static function maybeApplyFromIdentity( $phpmailer, array $settings ): void {
		$override = $settings['override_from'] === '1' || $settings['override_from'] === true;
		if ( ! $override ) {
			return;
		}

		$from_email = sanitize_email( (string) $settings['from_email'] );
		if ( ! is_email( $from_email ) ) {
			return;
		}

		$default_from  = apply_filters( 'wp_mail_from', get_option( 'admin_email' ) );
		$current_from  = (string) $phpmailer->From;
		$already_custom = $current_from !== ''
			&& $current_from !== $default_from
			&& $current_from !== $from_email;

		if ( $already_custom && ! apply_filters( 'wp_powersuite_smtp_force_from_override', false, $phpmailer, $settings ) ) {
			return;
		}

		$from_name = SmtpSettingsHelper::sanitizeFromName( (string) $settings['from_name'] );
		$phpmailer->From     = $from_email;
		$phpmailer->FromName = $from_name;
	}

	/**
	 * @param \PHPMailer\PHPMailer\PHPMailer $phpmailer
	 * @param array<string, mixed>           $settings
	 */
	private static function maybeApplyReplyTo( $phpmailer, array $settings ): void {
		$reply_email = sanitize_email( (string) ( $settings['reply_to_email'] ?? '' ) );
		if ( ! is_email( $reply_email ) ) {
			return;
		}

		$reply_name = SmtpSettingsHelper::sanitizeFromName( (string) ( $settings['reply_to_name'] ?? '' ) );
		$phpmailer->addReplyTo( $reply_email, $reply_name );
	}

	/**
	 * @param \PHPMailer\PHPMailer\PHPMailer $phpmailer
	 * @param array<string, mixed>           $settings
	 */
	private static function maybeEnableDebug( $phpmailer, array $settings ): void {
		$level = (int) ( $settings['smtp_debug'] ?? 0 );
		if ( $level < 1 || $level > 2 ) {
			return;
		}

		if ( ! ( is_admin() || ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) ) {
			return;
		}

		$phpmailer->SMTPDebug = $level;
		$phpmailer->Debugoutput = static function ( string $str, int $level ): void {
			unset( $level );
			// Redact AUTH lines that may contain credentials.
			$str = preg_replace( '/(AUTH[^\n]*|password[^\n]*)/i', '[redacted]', $str ) ?? $str;
			error_log( 'WP PowerSuite SMTP debug: ' . $str ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
		};
	}

	public static function decryptPasswordStatic( string $value ): string {
		if ( $value === '' ) {
			return $value;
		}
		if ( str_starts_with( $value, 'ENC2:' ) ) {
			return self::decryptV2Static( substr( $value, 5 ) );
		}
		if ( str_starts_with( $value, 'ENC:' ) ) {
			return self::decryptV1Static( substr( $value, 4 ) );
		}
		return '';
	}

	private static function decryptV2Static( string $encoded ): string {
		$auth_key = defined( 'AUTH_KEY' ) && AUTH_KEY ? AUTH_KEY : ABSPATH;
		$key      = hash( 'sha256', self::ENCRYPT_KEY . $auth_key, true );
		$data     = base64_decode( $encoded, true );
		if ( $data === false || strlen( $data ) < 17 ) {
			return '';
		}
		$iv        = substr( $data, 0, 16 );
		$encrypted = substr( $data, 16 );
		$decrypted = openssl_decrypt( $encrypted, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv );
		return $decrypted !== false ? $decrypted : '';
	}

	private static function decryptV1Static( string $encoded ): string {
		$auth_key    = defined( 'AUTH_KEY' ) && AUTH_KEY ? AUTH_KEY : ABSPATH;
		$secure_auth = defined( 'SECURE_AUTH_KEY' ) && SECURE_AUTH_KEY ? SECURE_AUTH_KEY : ABSPATH;
		$key         = substr( hash( 'sha256', self::ENCRYPT_KEY . $auth_key ), 0, 32 );
		$iv          = substr( hash( 'sha256', self::ENCRYPT_KEY . $secure_auth ), 0, 16 );
		$decrypted   = openssl_decrypt( base64_decode( $encoded ), 'AES-128-CBC', $key, OPENSSL_RAW_DATA, $iv );
		return $decrypted !== false ? $decrypted : '';
	}

	public function captureMailError( $wp_error ): void {
		if ( is_wp_error( $wp_error ) ) {
			$this->last_mail_error = $wp_error->get_error_message();
		}
	}

	public function ajaxTestSmtp(): void {
		check_ajax_referer( 'wp_powersuite_ajax', 'nonce' );

		if ( ! NetworkScope::canManagePlugin() ) {
			wp_send_json_error( [ 'message' => __( 'Permission denied', 'wp-powersuite' ) ] );
		}

		$user_id  = get_current_user_id();
		$rate_key = 'wpps_smtp_test_' . $user_id;
		$attempts = get_transient( $rate_key );

		if ( $attempts && $attempts >= 3 ) {
			wp_send_json_error( [
				'message' => __( 'Rate limit exceeded. Please wait 5 minutes before sending another test email.', 'wp-powersuite' ),
			] );
		}

		$test_email = isset( $_POST['test_email'] ) ? sanitize_email( wp_unslash( $_POST['test_email'] ) ) : '';
		if ( ! is_email( $test_email ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid email address', 'wp-powersuite' ) ] );
		}

		$overlay = $this->buildTestSettingsOverlay();
		if ( $overlay !== null ) {
			self::$test_settings_overlay = $overlay;
		}
		$settings = $overlay ?? $this->getSettings();

		if ( empty( $settings['smtp_host'] ) ) {
			wp_send_json_error( [
				'message' => __( 'SMTP Host not configured. Enter your SMTP host, then save or send a test.', 'wp-powersuite' ),
			] );
		}

		$this->last_mail_error = '';

		$site_name    = get_bloginfo( 'name' );
		$subject      = sprintf( __( 'Email delivery check – %s', 'wp-powersuite' ), $site_name );
		$greeting     = __( 'Hello,', 'wp-powersuite' );
		$body_text    = sprintf(
			__( 'You requested to verify that emails from %s are being delivered correctly. This message confirms that your mail settings are working as expected.', 'wp-powersuite' ),
			$site_name
		);
		$inbox_note   = __( 'If you received this in your inbox (and not in spam), your configuration is set up properly.', 'wp-powersuite' );
		$signoff      = __( 'Best regards,', 'wp-powersuite' );
		$plugin_name  = 'WP PowerSuite';

		$plain_message = $greeting . "\n\n" . $body_text . "\n\n" . $inbox_note . "\n\n" . $signoff . "\n" . $plugin_name . "\n\n" . __( 'Sent via WP PowerSuite Email Delivery', 'wp-powersuite' );

		add_action( 'phpmailer_init', $alt_body_callback = static function ( $phpmailer ) use ( $plain_message ): void {
			$phpmailer->AltBody = $plain_message;
		}, 99999 );

		$message = $this->buildTestEmailHtml( $greeting, $body_text, $inbox_note, $signoff, $plugin_name );

		$headers = EmailRenderer::headers();
		$result  = wp_mail( $test_email, $subject, $message, $headers );

		remove_action( 'phpmailer_init', $alt_body_callback, 99999 );
		self::$test_settings_overlay = null;

		if ( $result ) {
			set_transient( $rate_key, ( $attempts ? $attempts + 1 : 1 ), 5 * MINUTE_IN_SECONDS );
			wp_send_json_success( [ 'message' => __( 'Test email sent successfully!', 'wp-powersuite' ) ] );
		}

		$error_message = __( 'Failed to send email.', 'wp-powersuite' );
		if ( ! empty( $this->last_mail_error ) ) {
			$error_message .= ' ' . __( 'Error:', 'wp-powersuite' ) . ' ' . esc_html( $this->last_mail_error );
		}
		wp_send_json_error( [ 'message' => $error_message ] );
	}

	/**
	 * Build temporary settings from unsaved form POST data for test sends.
	 *
	 * @return array<string, mixed>|null Null when no overlay POST data was sent.
	 */
	private function buildTestSettingsOverlay(): ?array {
		$raw = isset( $_POST['settings'] ) ? wp_unslash( $_POST['settings'] ) : null; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		if ( ! is_array( $raw ) || $raw === [] ) {
			return null;
		}

		$stored = $this->getSettings();
		$merged = SmtpSettingsHelper::normalizeSettings( array_merge( $stored, $raw ) );

		$mask          = self::getPasswordMask();
		$post_password = isset( $raw['smtp_password'] ) ? (string) $raw['smtp_password'] : '';
		if ( $post_password === $mask || trim( $post_password ) === '' ) {
			$merged['smtp_password'] = $stored['smtp_password'];
		} elseif ( $post_password !== '' ) {
			$merged['smtp_password'] = self::encryptPassword( $post_password );
		}

		return $merged;
	}

	private function buildTestEmailHtml( string $greeting, string $body_text, string $inbox_note, string $signoff, string $plugin_name ): string {
		$content  = EmailComponents::paragraph( esc_html( $greeting ), [ 'margin' => '0 0 12px' ] );
		$content .= EmailComponents::paragraph( esc_html( $body_text ) );
		$content .= EmailComponents::notice( esc_html( $inbox_note ), 'info' );
		$content .= EmailComponents::paragraph(
			esc_html( $signoff ) . '<br><strong>' . esc_html( $plugin_name ) . '</strong>',
			[ 'margin' => '16px 0 0' ]
		);

		return EmailRenderer::wrap(
			[
				'title'        => __( 'Email delivery check', 'wp-powersuite' ),
				'site_name'    => 'WP PowerSuite',
				'content'      => $content,
				'footer_brand' => __( 'Sent via WP PowerSuite Email Delivery', 'wp-powersuite' ),
			]
		);
	}

	public static function encryptPassword( string $value ): string {
		if ( $value === '' ) {
			return '';
		}

		$auth_key = defined( 'AUTH_KEY' ) && AUTH_KEY ? AUTH_KEY : ABSPATH;
		$key      = hash( 'sha256', self::ENCRYPT_KEY . $auth_key, true );
		$iv       = random_bytes( 16 );

		$encrypted = openssl_encrypt( $value, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv );
		if ( $encrypted === false ) {
			return '';
		}

		return 'ENC2:' . base64_encode( $iv . $encrypted );
	}

	public static function getPasswordMask(): string {
		return self::PASSWORD_MASK;
	}

	/**
	 * @return array<string, mixed>
	 */
	public static function getDefaultSettings(): array {
		return SmtpSettingsHelper::getDefaults();
	}

	/**
	 * @param array<string, mixed> $settings
	 * @return array<string, mixed>
	 */
	public static function normalizeSettings( array $settings ): array {
		return SmtpSettingsHelper::normalizeSettings( $settings );
	}

	/**
	 * @param array<string, mixed> $settings
	 * @return array<string, mixed>
	 */
	public static function redactSettingsForResponse( array $settings ): array {
		return SmtpSettingsHelper::redactForResponse( $settings );
	}

	private function getSettings(): array {
		if ( $this->settings_cache !== null ) {
			return $this->settings_cache;
		}

		$settings = $this->moduleSettings()->all( [] );
		$settings = is_array( $settings ) ? $settings : [];
		$this->settings_cache = SmtpSettingsHelper::mergeWithDefaults( $settings );
		return $this->settings_cache;
	}
}

require_once __DIR__ . '/includes/SmtpSettingsHelper.php';
