<?php
/**
 * Scopes launch-version checksum uniqueness to each launch.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\Database\Migrations;

// phpcs:disable WordPress.DB -- Scheduler-owned custom tables require direct wpdb queries.
// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Exception messages are escaped at UI boundaries.

use ScheduledChanges\Database\Migration;
use ScheduledChanges\Database\TableNames;

/**
 * Allows identical immutable plans to exist under different launches.
 */
final class LaunchVersionChecksumScopeMigration implements Migration {
	/**
	 * {@inheritDoc}
	 */
	public function version(): int {
		return 2;
	}

	/**
	 * {@inheritDoc}
	 */
	public function up( \wpdb $wpdb ): void {
		$table = ( new TableNames() )->get( 'launch_versions' );

		if ( $this->indexExists( $wpdb, $table, 'checksum' ) ) {
			$this->execute( $wpdb, $wpdb->prepare( 'ALTER TABLE %i DROP INDEX checksum', $table ) );
		}

		if ( ! $this->indexExists( $wpdb, $table, 'launch_checksum' ) ) {
			$this->execute( $wpdb, $wpdb->prepare( 'ALTER TABLE %i ADD UNIQUE KEY launch_checksum (launch_id, checksum)', $table ) );
		}
	}

	/**
	 * Checks whether an index exists on the launch-version table.
	 *
	 * @param \wpdb  $wpdb WordPress database connection.
	 * @param string $table Table name.
	 * @param string $index Index name.
	 */
	private function indexExists( \wpdb $wpdb, string $table, string $index ): bool {
		$rows = $wpdb->get_results(
			$wpdb->prepare(
				'SHOW INDEX FROM %i WHERE Key_name = %s',
				$table,
				$index
			),
			ARRAY_A
		);

		if ( '' !== (string) $wpdb->last_error ) {
			throw new \RuntimeException( 'Unable to inspect launch version indexes: ' . $wpdb->last_error );
		}

		return is_array( $rows ) && array() !== $rows;
	}

	/**
	 * Executes one schema statement and converts wpdb errors to exceptions.
	 *
	 * @param \wpdb  $wpdb WordPress database connection.
	 * @param string $sql SQL statement.
	 */
	private function execute( \wpdb $wpdb, string $sql ): void {
		// phpcs:ignore PluginCheck.Security.DirectDB.UnescapedDBParameter -- Schema SQL is prepared at call sites because identifiers cannot be escaped with value placeholders.
		$result = $wpdb->query( $sql );

		if ( false === $result ) {
			throw new \RuntimeException( 'Unable to update launch version checksum index: ' . $wpdb->last_error );
		}
	}
}
