<?php

namespace Fotogenic_AI;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Bulk admission must live-verify the exact plugin table engines instead of trusting cached host state.

/**
 * Single owner for host capabilities that alter Fotogenic runtime behavior.
 *
 * Optional callables make the host boundary deterministic without introducing
 * test-only global overrides into production code.
 */
final class Platform_Capabilities {
	public const MINIMUM_PHP = '7.4';
	public const MINIMUM_WORDPRESS = '6.5';
	public const MINIMUM_WOOCOMMERCE = '9.4.0';
	private const REQUIRED_WOOCOMMERCE_FUNCTIONS = [ 'wc_get_product', 'wc_get_products' ];
	private const SODIUM_FUNCTIONS = [ 'sodium_crypto_secretbox', 'sodium_crypto_secretbox_open' ];
	private const OPENSSL_FUNCTIONS = [ 'openssl_encrypt', 'openssl_decrypt', 'openssl_get_cipher_methods' ];

	private static ?Platform_Capabilities $instance = null;

	/** @var callable */
	private $function_exists;

	/** @var callable */
	private $constant_value;

	/** @var callable */
	private $is_multisite;

	/** @var callable */
	private $table_engine_reader;

	/** @var callable */
	private $openssl_cipher_reader;

	public function __construct(
		?callable $function_exists = null,
		?callable $constant_value = null,
		?callable $is_multisite = null,
		?callable $table_engine_reader = null,
		?callable $openssl_cipher_reader = null
	) {
		$this->function_exists = $function_exists ?? static fn( string $function ): bool => function_exists( $function );
		$this->constant_value = $constant_value ?? static fn( string $constant ) => defined( $constant ) ? constant( $constant ) : null;
		$this->is_multisite = $is_multisite ?? static fn(): bool => function_exists( 'is_multisite' ) && is_multisite();
		$this->table_engine_reader = $table_engine_reader ?? static function ( string $table ): string {
			global $wpdb;
			if ( ! isset( $wpdb ) || ! is_object( $wpdb ) || ! method_exists( $wpdb, 'get_row' ) || ! method_exists( $wpdb, 'prepare' ) ) {
				return '';
			}
			$row = $wpdb->get_row(
				$wpdb->prepare( 'SHOW TABLE STATUS WHERE Name = %s', $table ),
				ARRAY_A
			);
			if ( ! is_array( $row ) || $table !== (string) ( $row['Name'] ?? '' ) ) {
				return '';
			}

			return trim( (string) ( $row['Engine'] ?? '' ) );
		};
		$this->openssl_cipher_reader = $openssl_cipher_reader ?? static function (): array {
			return function_exists( 'openssl_get_cipher_methods' )
				? (array) openssl_get_cipher_methods()
				: [];
		};
	}

	public static function instance(): Platform_Capabilities {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}

		return self::$instance;
	}

	/** @return array{available: bool, code: string, message: string} */
	public function runtime_status( string $php_version, string $wordpress_version ): array {
		if ( version_compare( $php_version, self::MINIMUM_PHP, '<' ) ) {
			return $this->status(
				false,
				'php_version_unsupported',
				$this->message( 'php_version_unsupported', self::MINIMUM_PHP )
			);
		}
		if ( version_compare( $wordpress_version, self::MINIMUM_WORDPRESS, '<' ) ) {
			return $this->status(
				false,
				'wordpress_version_unsupported',
				$this->message( 'wordpress_version_unsupported', self::MINIMUM_WORDPRESS )
			);
		}

		return $this->status( true, 'runtime_versions_ready', '' );
	}

	/**
	 * @return array{available: bool, code: string, message: string}
	 */
	public function activation_status( bool $network_wide ): array {
		if ( $network_wide ) {
			return $this->status(
				false,
				'network_activation_unsupported',
				$this->message( 'network_activation_unsupported' )
			);
		}
		if ( ( $this->is_multisite )() ) {
			return $this->status(
				false,
				'multisite_unsupported',
				$this->message( 'multisite_unsupported' )
			);
		}

		return $this->status( true, 'single_site_ready', '' );
	}

	/**
	 * @return array{available: bool, code: string, message: string, version: string, missingApis: array<int, string>}
	 */
	public function woocommerce_status(): array {
		$version = trim( (string) ( $this->constant( 'WC_VERSION' ) ?? $this->constant( 'WOOCOMMERCE_VERSION' ) ?? '' ) );
		$missing = [];
		foreach ( self::REQUIRED_WOOCOMMERCE_FUNCTIONS as $function ) {
			if ( ! $this->has_function( $function ) ) {
				$missing[] = $function;
			}
		}

		if ( '' === $version && count( $missing ) === count( self::REQUIRED_WOOCOMMERCE_FUNCTIONS ) ) {
			return $this->woocommerce_result(
				false,
				'woocommerce_absent',
				$this->message( 'woocommerce_absent' ),
				'',
				$missing
			);
		}
		if ( '' === $version ) {
			return $this->woocommerce_result(
				false,
				'woocommerce_version_unavailable',
				$this->message( 'woocommerce_version_unavailable' ),
				'',
				$missing
			);
		}
		if ( version_compare( $version, self::MINIMUM_WOOCOMMERCE, '<' ) ) {
			return $this->woocommerce_result(
				false,
				'woocommerce_version_unsupported',
				$this->message( 'woocommerce_version_unsupported', self::MINIMUM_WOOCOMMERCE ),
				$version,
				$missing
			);
		}
		if ( ! empty( $missing ) ) {
			return $this->woocommerce_result(
				false,
				'woocommerce_api_incomplete',
				$this->message( 'woocommerce_api_incomplete' ),
				$version,
				$missing
			);
		}

		return $this->woocommerce_result( true, 'woocommerce_ready', '', $version, [] );
	}

	/**
	 * Return write readiness and, when supplied, stored-payload readability.
	 *
	 * @return array{available: bool, readable: bool, backend: string, storedBackend: string, code: string, message: string}
	 */
	public function crypto_status( string $payload = '' ): array {
		$backend = $this->preferred_crypto_backend();
		$stored_backend = '' !== $payload ? $this->stored_crypto_backend( $payload ) : '';
		$readable = '' === $stored_backend ? '' === $payload || '' !== $backend : $this->crypto_backend_available( $stored_backend );

		if ( '' !== $payload && '' === $stored_backend ) {
			return $this->crypto_result(
				'' !== $backend,
				false,
				$backend,
				'',
				'crypto_payload_invalid',
				$this->message( 'crypto_payload_invalid' )
			);
		}
		if ( '' !== $stored_backend && ! $readable ) {
			return $this->crypto_result(
				'' !== $backend,
				false,
				$backend,
				$stored_backend,
				'crypto_backend_unavailable',
				$this->message( 'crypto_backend_unavailable', 'sodium' === $stored_backend ? 'Sodium' : 'OpenSSL' )
			);
		}
		if ( '' === $backend ) {
			return $this->crypto_result(
				false,
				false,
				'',
				$stored_backend,
				'crypto_unavailable',
				$this->message( 'crypto_unavailable' )
			);
		}

		return $this->crypto_result( true, true, $backend, $stored_backend, 'crypto_ready', '' );
	}

	public function preferred_crypto_backend(): string {
		if ( $this->crypto_backend_available( 'sodium' ) ) {
			return 'sodium';
		}
		if ( $this->crypto_backend_available( 'openssl' ) ) {
			return 'openssl';
		}

		return '';
	}

	public function crypto_backend_available( string $backend ): bool {
		$functions = 'sodium' === $backend
			? self::SODIUM_FUNCTIONS
			: ( 'openssl' === $backend ? self::OPENSSL_FUNCTIONS : [] );
		if ( empty( $functions ) ) {
			return false;
		}
		foreach ( $functions as $function ) {
			if ( ! $this->has_function( $function ) ) {
				return false;
			}
		}
		if ( 'openssl' === $backend ) {
			$ciphers = array_map( 'strtolower', (array) ( $this->openssl_cipher_reader )() );
			return in_array( 'aes-256-gcm', $ciphers, true );
		}

		return true;
	}

	public function stored_crypto_backend( string $payload ): string {
		// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode -- The companion encrypt helper stores a base64-encoded JSON envelope.
		$decoded = base64_decode( $payload, true );
		if ( false === $decoded ) {
			return '';
		}
		$data = json_decode( $decoded, true );
		$backend = is_array( $data ) ? (string) ( $data['m'] ?? '' ) : '';

		return in_array( $backend, [ 'sodium', 'openssl' ], true ) ? $backend : '';
	}

	/**
	 * Live-check every exact plugin table before a transactional Bulk admission.
	 *
	 * @param array<int, string> $tables Exact plugin-owned table names.
	 * @return array{available: bool, code: string, message: string, engines: array<string, string>}
	 */
	public function transactional_storage_status( array $tables ): array {
		$engines = [];
		$missing = false;
		$nontransactional = false;
		foreach ( array_values( array_unique( array_filter( array_map( 'strval', $tables ) ) ) ) as $table ) {
			$engine = trim( (string) ( $this->table_engine_reader )( $table ) );
			$engines[ $table ] = $engine;
			if ( '' === $engine ) {
				$missing = true;
			} elseif ( 0 !== strcasecmp( 'InnoDB', $engine ) ) {
				$nontransactional = true;
			}
		}

		if ( empty( $engines ) || $missing ) {
			return $this->storage_result(
				false,
				'transactional_storage_unavailable',
				$this->message( 'transactional_storage_unavailable' ),
				$engines
			);
		}
		if ( $nontransactional ) {
			return $this->storage_result(
				false,
				'transactional_storage_required',
				$this->message( 'transactional_storage_required' ),
				$engines
			);
		}

		return $this->storage_result( true, 'transactional_storage_ready', '', $engines );
	}

	/** @return mixed */
	private function constant( string $constant ) {
		return ( $this->constant_value )( $constant );
	}

	private function has_function( string $function ): bool {
		return (bool) ( $this->function_exists )( $function );
	}

	private function message( string $code, string $value = '' ): string {
		$translate = function_exists( 'did_action' ) && did_action( 'init' ) > 0;
		switch ( $code ) {
			case 'php_version_unsupported':
				/* translators: %s: minimum supported PHP version. */
				$message = $translate ? __( 'Fotogenic AI requires PHP %s or higher.', 'fotogenic-ai' ) : 'Fotogenic AI requires PHP %s or higher.';
				break;
			case 'wordpress_version_unsupported':
				/* translators: %s: minimum supported WordPress version. */
				$message = $translate ? __( 'Fotogenic AI requires WordPress %s or higher.', 'fotogenic-ai' ) : 'Fotogenic AI requires WordPress %s or higher.';
				break;
			case 'network_activation_unsupported':
				$message = $translate ? __( 'Fotogenic AI 1.1 cannot be network-activated. Install it on a supported single WordPress site.', 'fotogenic-ai' ) : 'Fotogenic AI 1.1 cannot be network-activated. Install it on a supported single WordPress site.';
				break;
			case 'multisite_unsupported':
				$message = $translate ? __( 'Fotogenic AI 1.1 supports single-site WordPress installations only.', 'fotogenic-ai' ) : 'Fotogenic AI 1.1 supports single-site WordPress installations only.';
				break;
			case 'woocommerce_absent':
				$message = $translate ? __( 'WooCommerce is not active. Media, Post, and Page sources remain available.', 'fotogenic-ai' ) : 'WooCommerce is not active. Media, Post, and Page sources remain available.';
				break;
			case 'woocommerce_version_unavailable':
				$message = $translate ? __( 'WooCommerce Product sources are unavailable because the active WooCommerce version could not be verified.', 'fotogenic-ai' ) : 'WooCommerce Product sources are unavailable because the active WooCommerce version could not be verified.';
				break;
			case 'woocommerce_version_unsupported':
				/* translators: %s: minimum supported WooCommerce version. */
				$message = $translate ? __( 'WooCommerce Product sources require WooCommerce %s or newer. Media, Post, and Page sources remain available.', 'fotogenic-ai' ) : 'WooCommerce Product sources require WooCommerce %s or newer. Media, Post, and Page sources remain available.';
				break;
			case 'woocommerce_api_incomplete':
				$message = $translate ? __( 'WooCommerce Product sources are unavailable because required product APIs are missing. Update or repair WooCommerce.', 'fotogenic-ai' ) : 'WooCommerce Product sources are unavailable because required product APIs are missing. Update or repair WooCommerce.';
				break;
			case 'crypto_payload_invalid':
				$message = $translate ? __( 'The stored Fotogenic login is unreadable. Disconnect it and connect the account again.', 'fotogenic-ai' ) : 'The stored Fotogenic login is unreadable. Disconnect it and connect the account again.';
				break;
			case 'crypto_backend_unavailable':
				/* translators: %s: PHP crypto extension name. */
				$message = $translate ? __( 'The stored Fotogenic login requires the PHP %s extension. Restore that extension or reconnect the account using an available crypto backend.', 'fotogenic-ai' ) : 'The stored Fotogenic login requires the PHP %s extension. Restore that extension or reconnect the account using an available crypto backend.';
				break;
			case 'crypto_unavailable':
				$message = $translate ? __( 'Fotogenic account login requires the PHP Sodium or OpenSSL extension. Ask the host to enable one before connecting.', 'fotogenic-ai' ) : 'Fotogenic account login requires the PHP Sodium or OpenSSL extension. Ask the host to enable one before connecting.';
				break;
			case 'transactional_storage_unavailable':
				$message = $translate ? __( 'Fotogenic Bulk storage is unavailable. Ask the host to verify the plugin database tables.', 'fotogenic-ai' ) : 'Fotogenic Bulk storage is unavailable. Ask the host to verify the plugin database tables.';
				break;
			case 'transactional_storage_required':
				$message = $translate ? __( 'Fotogenic Bulk requires InnoDB tables. Ask the host to convert the Fotogenic Bulk tables to InnoDB.', 'fotogenic-ai' ) : 'Fotogenic Bulk requires InnoDB tables. Ask the host to convert the Fotogenic Bulk tables to InnoDB.';
				break;
			default:
				return '';
		}

		return '' !== $value ? sprintf( $message, $value ) : $message;
	}

	/** @return array{available: bool, code: string, message: string} */
	private function status( bool $available, string $code, string $message ): array {
		return compact( 'available', 'code', 'message' );
	}

	/** @return array{available: bool, code: string, message: string, version: string, missingApis: array<int, string>} */
	private function woocommerce_result( bool $available, string $code, string $message, string $version, array $missing_apis ): array {
		return [
			'available'   => $available,
			'code'        => $code,
			'message'     => $message,
			'version'     => $version,
			'missingApis' => array_values( $missing_apis ),
		];
	}

	/** @return array{available: bool, readable: bool, backend: string, storedBackend: string, code: string, message: string} */
	private function crypto_result( bool $available, bool $readable, string $backend, string $stored_backend, string $code, string $message ): array {
		return [
			'available'     => $available,
			'readable'      => $readable,
			'backend'       => $backend,
			'storedBackend' => $stored_backend,
			'code'          => $code,
			'message'       => $message,
		];
	}

	/** @return array{available: bool, code: string, message: string, engines: array<string, string>} */
	private function storage_result( bool $available, string $code, string $message, array $engines ): array {
		return [
			'available' => $available,
			'code'      => $code,
			'message'   => $message,
			'engines'   => $engines,
		];
	}
}
