<?php
/**
 * Plugin migration helpers.
 *
 * @package MailDoctor
 */

namespace MailDoctor\Helpers;

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

/**
 * Handles plugin versioning and migration routines.
 */
class Migrations {


	/**
	 * Option key that stores plugin schema/settings version.
	 */
	const OPTION_VERSION = 'mail_doctor_version';

	/**
	 * Retrieve stored plugin version.
	 *
	 * @return string
	 */
	public static function current_version(): string {
		return (string) get_option( self::OPTION_VERSION, '0' );
	}

	/**
	 * Persist new plugin version.
	 *
	 * @param string $version Plugin version.
	 * @return void
	 */
	protected static function update_version( string $version ): void {
		update_option( self::OPTION_VERSION, $version );
	}

	/**
	 * Run migrations if needed.
	 *
	 * @param string $target_version Plugin version to compare against.
	 * @return void
	 */
	public static function maybe_upgrade( string $target_version ): void {
		$current = self::current_version();

		if ( '' === $target_version ) {
			return;
		}

		if ( version_compare( $current, $target_version, '>=' ) ) {
			return;
		}

		if ( version_compare( $current, '1.0.0', '<' ) ) {
			self::upgrade_to_100();
		}

		if ( version_compare( $current, '1.0.2', '<' ) ) {
			self::upgrade_to_110();
		}

		if ( version_compare( $current, '1.2.0', '<' ) ) {
			self::upgrade_to_120();
		}

		if ( version_compare( $current, '1.3.0', '<' ) ) {
			self::upgrade_to_130();
		}

		if ( version_compare( $current, '1.4.0', '<' ) ) {
			self::upgrade_to_140();
		}

		self::update_version( $target_version );
	}

	/**
	 * Baseline upgrade handler for first release.
	 *
	 * @return void
	 */
	protected static function upgrade_to_100(): void {
		// Ensure all default settings keys exist.
		Settings::update( Settings::all() );
	}

	/**
	 * Upgrade path for 1.0.2 introducing queue safeguards.
	 *
	 * @return void
	 */
	protected static function upgrade_to_110(): void {
		global $wpdb;

		$table = $wpdb->prefix . 'mail_doctor_log';

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$columns = $wpdb->get_col(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
				"SHOW COLUMNS FROM {$table} LIKE %s",
				'idempotency_key'
			)
		);

		if ( empty( $columns ) ) {
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
			$wpdb->query( "ALTER TABLE {$table} ADD COLUMN idempotency_key CHAR(64) NULL DEFAULT NULL AFTER payload" );
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$bucket_column = $wpdb->get_col(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
				"SHOW COLUMNS FROM {$table} LIKE %s",
				'idempotency_bucket'
			)
		);

		if ( empty( $bucket_column ) ) {
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
			$wpdb->query( "ALTER TABLE {$table} ADD COLUMN idempotency_bucket BIGINT NULL DEFAULT NULL AFTER idempotency_key" );
		}

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$indexes = $wpdb->get_results(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
				"SHOW INDEX FROM {$table}"
			),
			ARRAY_A
		);
		$has_key_index    = false;
		$has_bucket_index = false;

		foreach ( (array) $indexes as $index ) {
			if ( isset( $index['Key_name'] ) && 'idempotency_key' === $index['Key_name'] ) {
				$has_key_index = true;
			}

			if ( isset( $index['Key_name'] ) && 'idempotency_bucket' === $index['Key_name'] ) {
				$has_bucket_index = true;
			}
		}

		if ( ! $has_key_index ) {
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
			$wpdb->query( "ALTER TABLE {$table} ADD KEY idempotency_key (idempotency_key)" );
		}

		if ( ! $has_bucket_index ) {
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
			$wpdb->query( "ALTER TABLE {$table} ADD KEY idempotency_bucket (idempotency_bucket)" );
		}

		// Refresh settings to include new defaults.
		Settings::update( Settings::all() );
	}

	/**
	 * Upgrade path introducing dedicated SMTP profile tables and suppression list.
	 *
	 * @return void
	 */
	protected static function upgrade_to_120(): void {
		global $wpdb;

		if ( class_exists( '\\MailDoctor\\Profiles' ) ) {
			\MailDoctor\Profiles::install();
		}

		if ( class_exists( '\\MailDoctor\\Suppression' ) ) {
			\MailDoctor\Suppression::install();
		}

		$log_table = \MailDoctor\Logger::table_name();
		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$wpdb->query( "ALTER TABLE {$log_table} MODIFY status ENUM('queued','sent','failed','skipped') NOT NULL" );

		$option = get_option( Settings::OPTION_KEY, array() );

		$slug_to_id = array();

		if ( isset( $option['smtp_profiles'] ) && is_array( $option['smtp_profiles'] ) && class_exists( '\\MailDoctor\\Profiles' ) ) {
			foreach ( $option['smtp_profiles'] as $slug => $profile ) {
				$form = array(
					array(
						'id'         => 0,
						'name'       => $profile['label'] ?? ( $profile['host'] ?? __( 'Migrated Profile', 'mail-doctor' ) ),
						'host'       => $profile['host'] ?? '',
						'port'       => $profile['port'] ?? 587,
						'secure'     => $profile['encryption'] ?? 'tls',
						'auth'       => 'password',
						'username'   => $profile['username'] ?? '',
						'password'   => $profile['password'] ?? '',
						'from_email' => $option['from_email'] ?? '',
						'from_name'  => $option['from_name'] ?? '',
						'slug'       => sanitize_key( $slug ),
					),
				);

				\MailDoctor\Profiles::sync_from_form( $form );
				$migrated = \MailDoctor\Profiles::find_by_slug( sanitize_key( $slug ) );
				if ( $migrated ) {
					$slug_to_id[ sanitize_key( $slug ) ] = (string) $migrated['id'];
				}
			}
		}

		$assignments = array(
			'emails' => array(),
			'roles'  => array(),
			'sites'  => array(),
		);

		if ( isset( $option['smtp_profile_map'] ) && is_array( $option['smtp_profile_map'] ) ) {
			foreach ( $option['smtp_profile_map'] as $email_id => $slug ) {
				$email_key = sanitize_key( $email_id );
				$slug_key  = sanitize_key( $slug );

				if ( '' === $email_key || '' === $slug_key || 'default' === $slug_key ) {
					continue;
				}

				if ( isset( $slug_to_id[ $slug_key ] ) ) {
					$assignments['emails'][ $email_key ] = $slug_to_id[ $slug_key ];
					continue;
				}

				if ( class_exists( '\\MailDoctor\\Profiles' ) ) {
					$migrated = \MailDoctor\Profiles::find_by_slug( $slug_key );
					if ( $migrated ) {
						$slug_to_id[ $slug_key ]             = (string) $migrated['id'];
						$assignments['emails'][ $email_key ] = (string) $migrated['id'];
					}
				}
			}
		}

		$option['smtp_profile_assignments'] = $assignments;
		unset( $option['smtp_profiles'], $option['smtp_profile_map'] );

		if ( empty( $option['active_profile'] ) ) {
			$option['active_profile'] = 'default';
		}

		if ( empty( $option['webhook_token'] ) ) {
			try {
				$option['webhook_token'] = bin2hex( \random_bytes( 16 ) );
			} catch ( \Exception $exception ) {
				$option['webhook_token'] = strtolower( \wp_generate_password( 24, false, false ) );
			}
		}

		Settings::update( $option );
	}

	/**
	 * Upgrade path introducing async infrastructure & archive tooling.
	 *
	 * @return void
	 */
	protected static function upgrade_to_130(): void {
		global $wpdb;

		$table = \MailDoctor\Logger::table_name();

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$indexes = $wpdb->get_results(
			$wpdb->prepare(
				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared
				"SHOW INDEX FROM {$table}"
			),
			ARRAY_A
		);
		$has_composite = false;

		foreach ( (array) $indexes as $index ) {
			if ( isset( $index['Key_name'] ) && 'status_created' === $index['Key_name'] ) {
				$has_composite = true;
				break;
			}
		}

		if ( ! $has_composite ) {
			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
			$wpdb->query( "ALTER TABLE {$table} ADD KEY status_created (status, created_at)" );
		}

		\MailDoctor\Logger::ensure_archive_table();
		\MailDoctor\Logger::schedule_archive();
	}

	/**
	 * Align integration settings defaults and thresholds.
	 *
	 * @return void
	 */
	protected static function upgrade_to_140(): void {
		$option = get_option( Settings::OPTION_KEY, array() );

		if ( ! is_array( $option ) ) {
			$option = array();
		}

		// Backfill webhook flag/token.
		if ( ! isset( $option['webhook_enabled'] ) ) {
			$option['webhook_enabled'] = false;
		}

		if ( empty( $option['webhook_token'] ) ) {
			$option['webhook_token'] = Settings::new_webhook_token();
		}

		// Migrate legacy alert keys.
		if ( isset( $option['alerts_threshold'] ) && ! isset( $option['alert_threshold_percent'] ) ) {
			$option['alert_threshold_percent'] = max( 1, (int) $option['alerts_threshold'] );
		}

		if ( isset( $option['alerts_window'] ) && ! isset( $option['alert_window_minutes'] ) ) {
			$option['alert_window_minutes'] = max( 5, (int) $option['alerts_window'] );
		}

		if ( ! isset( $option['alert_burst_failures'] ) ) {
			$option['alert_burst_failures'] = 5;
		}

		if ( ! isset( $option['alert_burst_minutes'] ) ) {
			$option['alert_burst_minutes'] = 5;
		}

		if ( ! isset( $option['alert_cooldown_minutes'] ) ) {
			$option['alert_cooldown_minutes'] = 60;
		}

		Settings::update( $option );
	}
}
