<?php
/**
 * Plugin / schema upgrade routines.
 *
 * Runs on plugins_loaded when a stored schema (or plugin) version is stale,
 * so existing installs get dbDelta() updates without re-activating.
 *
 * Schema V2 migration (which inserts CPT posts) is deferred until after
 * `init` so rewrite / post-type registration are ready — calling
 * wp_insert_post → get_permalink during plugins_loaded can fatal.
 *
 * @package SEOvaultSEO
 */

namespace SEOvaultSEO\Core;

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

/**
 * Detects version mismatches and applies table / plugin upgrades.
 */
class Upgrade {

	/**
	 * Option storing the last-known plugin version that completed upgrades.
	 */
	const VERSION_OPTION = 'seovaultseo_version';

	/**
	 * Compare stored schema / plugin versions and upgrade when needed.
	 *
	 * Safe to call on every request: no-ops when versions already match.
	 *
	 * @return void
	 */
	public static function maybe_upgrade() {
		$needs_schema          = self::needs_schema_upgrade();
		$needs_plugin          = self::needs_plugin_upgrade();
		$needs_schema_defaults = \SEOvaultSEO\Schema\SchemaDefaultsMigration::is_needed();
		$needs_schema_v2       = \SEOvaultSEO\Schema\SchemaMigration::should_auto_run();

		if ( ! $needs_schema && ! $needs_plugin && ! $needs_schema_defaults && ! $needs_schema_v2 ) {
			return;
		}

		if ( $needs_schema || $needs_plugin || $needs_schema_defaults ) {
			self::run_immediate();
		}

		if ( $needs_schema_v2 ) {
			self::schedule_or_run_schema_v2();
		}
	}

	/**
	 * Whether any custom-table schema version is missing or outdated.
	 *
	 * @return bool
	 */
	public static function needs_schema_upgrade() {
		if ( (string) get_option( \SEOvaultSEO\Monitor404\Db::DB_VERSION_OPTION, '' ) !== \SEOvaultSEO\Monitor404\Db::DB_VERSION ) {
			return true;
		}

		if ( (string) get_option( \SEOvaultSEO\RedirectManager\RedirectDb::DB_VERSION_OPTION, '' ) !== \SEOvaultSEO\RedirectManager\RedirectDb::DB_VERSION ) {
			return true;
		}

		if ( (string) get_option( \SEOvaultSEO\Api\AuditLog::DB_VERSION_OPTION, '' ) !== \SEOvaultSEO\Api\AuditLog::DB_VERSION ) {
			return true;
		}

		return false;
	}

	/**
	 * Whether the stored plugin version differs from SEOVAULT_SEO_VERSION.
	 *
	 * @return bool
	 */
	public static function needs_plugin_upgrade() {
		if ( ! defined( 'SEOVAULT_SEO_VERSION' ) ) {
			return false;
		}

		return (string) get_option( self::VERSION_OPTION, '' ) !== SEOVAULT_SEO_VERSION;
	}

	/**
	 * Re-run table installers, schema migrations, and record the plugin version.
	 *
	 * Used by activation and tests. Runs Schema V2 immediately (caller must be
	 * past `init`, or tests that register the CPT themselves).
	 *
	 * @return void
	 */
	public static function run() {
		self::run_immediate();
		\SEOvaultSEO\Schema\SchemaMigration::run();
	}

	/**
	 * Table installers, defaults migration, and plugin version stamp only.
	 *
	 * @return void
	 */
	public static function run_immediate() {
		self::migrate_ga_tracking_config();
		\SEOvaultSEO\Monitor404\Db::create_table();
		\SEOvaultSEO\RedirectManager\RedirectDb::create_table();
		\SEOvaultSEO\Api\AuditLog::install_table();
		\SEOvaultSEO\Schema\SchemaDefaultsMigration::run();

		if ( defined( 'SEOVAULT_SEO_VERSION' ) ) {
			update_option( self::VERSION_OPTION, SEOVAULT_SEO_VERSION, false );
		}
	}

	/**
	 * Remove the legacy arbitrary-JavaScript analytics option.
	 *
	 * Prior releases let administrators save raw tracking JavaScript
	 * (ga_custom_code). That is no longer permitted. Existing values are
	 * migrated to a Measurement ID when one can be detected, the tracking
	 * method is forced back to the programmatic gtag output, and the stored
	 * script is deleted so it can never be emitted again.
	 *
	 * Idempotent: after the first run the method is 'gtag' and the option is
	 * gone, so subsequent upgrades no-op.
	 *
	 * @return void
	 */
	private static function migrate_ga_tracking_config() {
		$code = (string) Options::get( 'ga_custom_code', '' );
		if ( 'custom' === Options::get_ga_tracking_method() || '' !== trim( $code ) ) {
			$id   = '';
			if ( '' === (string) Options::get_ga_measurement_id() ) {
				if ( preg_match( '/\bG-[A-Z0-9]{6,}\b/', $code, $m ) ) {
					$id = $m[0];
				} elseif ( preg_match( '/\bUA-[0-9]+-[0-9]+\b/', $code, $m ) ) {
					$id = $m[0];
				} elseif ( preg_match( '/\bAW-[A-Z0-9_-]+\b/', $code, $m ) ) {
					$id = $m[0];
				}
			}
			if ( '' !== $id ) {
				Options::set_ga_measurement_id( $id );
			}
			Options::set_ga_tracking_method( 'gtag' );
		}

		Options::delete( 'ga_custom_code' );
	}

	/**
	 * Run Schema V2 migration now, or schedule it after `init`.
	 *
	 * Auto path uses one post batch per request to avoid admin timeouts.
	 *
	 * @return void
	 */
	public static function schedule_or_run_schema_v2() {
		if ( did_action( 'init' ) ) {
			self::run_schema_v2_migration();
			return;
		}

		if ( ! has_action( 'init', array( __CLASS__, 'run_schema_v2_migration' ) ) ) {
			add_action( 'init', array( __CLASS__, 'run_schema_v2_migration' ), 20 );
		}
	}

	/**
	 * Bound Schema V2 migration for the automatic upgrade path.
	 *
	 * @return void
	 */
	public static function run_schema_v2_migration() {
		if ( ! \SEOvaultSEO\Schema\SchemaMigration::should_auto_run() ) {
			return;
		}

		\SEOvaultSEO\Schema\SchemaMigration::run(
			array(
				'max_post_batches' => 1,
			)
		);
	}
}
