<?php
/**
 * Scheduler table health.
 *
 * @package ScheduledChanges
 */

namespace ScheduledChanges\Database;

use ScheduledChanges\Support\WpdbConnection;

// phpcs:disable WordPress.DB -- Scheduler-owned custom tables require direct wpdb queries.

/**
 * Inspects table presence without mutating the schema.
 */
final class TableHealth {
	private \wpdb $wpdb;
	private TableNames $tables;
	/** @var array<string,array{exists:bool,engine:?string,transaction_capable:bool}> */
	private array $detail_cache         = array();
	private const TRANSACTIONAL_ENGINES = array( 'innodb', 'xtradb', 'ndb', 'ndbcluster' );

	/**
	 * Creates the table health inspector.
	 *
	 * @param \wpdb|null      $database WordPress database connection.
	 * @param TableNames|null $tables Scheduler table resolver.
	 */
	public function __construct( ?\wpdb $database = null, ?TableNames $tables = null ) {
		$this->wpdb   = WpdbConnection::resolve( $database );
		$this->tables = $tables ?? new TableNames( $database );
	}

	/**
	 * Returns table existence keyed by domain table name.
	 *
	 * @return array<string, bool>
	 */
	public function inspect(): array {
		$result = array();

		foreach ( $this->tables->all() as $name => $table ) {
			$found = $this->wpdb->get_var(
				$this->wpdb->prepare( 'SHOW TABLES LIKE %s', $this->wpdb->esc_like( $table ) )
			);

			$result[ $name ] = $found === $table;
		}

		return $result;
	}

	/**
	 * Returns table storage details keyed by domain table name.
	 *
	 * @return array<string,array{exists:bool,engine:?string,transaction_capable:bool}>
	 */
	public function details(): array {
		$result = array();

		foreach ( $this->tables->all() as $name => $table ) {
			$result[ $name ] = $this->detail( $name, $table );
		}

		return $result;
	}

	/**
	 * Returns a transaction capability diagnostic for selected scheduler tables.
	 *
	 * @param string[] $table_keys Scheduler table keys.
	 * @return array{requested_tables:list<string>,transaction_capable:bool,non_transactional:list<string>,missing:list<string>,tables:array<string,array{exists:bool,engine:?string,transaction_capable:bool}>}
	 */
	public function transactionDiagnostic( array $table_keys ): array {
		$requested = array_values( array_unique( $table_keys ) );
		$tables    = $this->tables->all();
		$selected  = array();

		foreach ( $requested as $name ) {
			if ( isset( $tables[ $name ] ) ) {
				$selected[ $name ] = $this->detail( $name, $tables[ $name ] );
			}
		}

		$missing           = array_values(
			array_filter(
				$requested,
				static fn ( string $name ): bool => ! isset( $selected[ $name ] ) || ! (bool) $selected[ $name ]['exists']
			)
		);
		$non_transactional = array_values(
			array_filter(
				array_keys( $selected ),
				static fn ( string $name ): bool => ! (bool) $selected[ $name ]['transaction_capable']
			)
		);

		return array(
			'requested_tables'    => $requested,
			'transaction_capable' => array() !== $requested && array() === $missing && array() === $non_transactional,
			'non_transactional'   => $non_transactional,
			'missing'             => $missing,
			'tables'              => $selected,
		);
	}

	/**
	 * Returns one memoized storage-engine diagnostic.
	 *
	 * @return array{exists:bool,engine:?string,transaction_capable:bool}
	 */
	private function detail( string $name, string $table ): array {
		if ( isset( $this->detail_cache[ $name ] ) ) {
			return $this->detail_cache[ $name ];
		}

		$found  = $this->wpdb->get_var(
			$this->wpdb->prepare( 'SHOW TABLES LIKE %s', $this->wpdb->esc_like( $table ) )
		);
		$exists = $found === $table;
		$engine = null;

		if ( $exists ) {
			$row = $this->wpdb->get_row(
				$this->wpdb->prepare( 'SHOW TABLE STATUS LIKE %s', $this->wpdb->esc_like( $table ) ),
				'ARRAY_A'
			);

			if ( is_array( $row ) && isset( $row['Engine'] ) ) {
				$engine = (string) $row['Engine'];
			}
		}

		$this->detail_cache[ $name ] = array(
			'exists'              => $exists,
			'engine'              => $engine,
			'transaction_capable' => null !== $engine && in_array( strtolower( $engine ), self::TRANSACTIONAL_ENGINES, true ),
		);

		return $this->detail_cache[ $name ];
	}

	/**
	 * Summary for diagnostics.
	 *
	 * @return array{expected:int, existing:int, missing:list<string>, transaction_capable:bool, non_transactional:list<string>, tables:array<string,array{exists:bool,engine:?string,transaction_capable:bool}>}
	 */
	public function summary(): array {
		$details           = $this->details();
		$missing           = array_keys( array_filter( $details, static fn( array $detail ): bool => ! (bool) $detail['exists'] ) );
		$non_transactional = array_keys( array_filter( $details, static fn( array $detail ): bool => (bool) $detail['exists'] && ! (bool) $detail['transaction_capable'] ) );

		return array(
			'expected'            => count( $details ),
			'existing'            => count( $details ) - count( $missing ),
			'missing'             => $missing,
			'transaction_capable' => array() === $missing && array() === $non_transactional,
			'non_transactional'   => $non_transactional,
			'tables'              => $details,
		);
	}
}
