<?php
/**
 * WooCommerce module lifecycle certification manifest.
 *
 * @package ScheduledChangesWooCommerce
 */

namespace ScheduledChangesWooCommerce;

use ScheduledChanges\Extension\ModuleDefinition;

/**
 * Loads and validates the bundled release evidence contract.
 */
final class CertificationManifest {
	private const REQUIRED_LIFECYCLE = array(
		'save',
		'validation',
		'recording',
		'diff',
		'snapshot',
		'rollback',
		'cleanup',
	);

	/**
	 * @var array<string,mixed>|null
	 */
	private static ?array $manifest = null;
	/**
	 * @var array<string,mixed>|null
	 */
	private static ?array $result = null;

	/**
	 * @return array<string,mixed>
	 */
	public static function certification(): array {
		$manifest = self::manifest();

		return array(
			'required'       => true,
			'schema_version' => (int) ( $manifest['schema_version'] ?? 0 ),
			'id'             => (string) ( $manifest['id'] ?? '' ),
			'evidence'       => is_array( $manifest['evidence'] ?? null ) ? $manifest['evidence'] : array(),
			'commands'       => self::commandChecksums(),
			'checksum'       => (string) ( $manifest['checksum'] ?? '' ),
			'result'         => self::result(),
		);
	}

	/**
	 * @return string[]
	 */
	public static function verifiedLifecycle(): array {
		if ( ! self::valid() ) {
			return array();
		}

		$steps = array_keys( self::normalizedEvidence() );
		sort( $steps );

		return $steps;
	}

	/**
	 * Returns the certified lifecycle status.
	 */
	public static function status(): string {
		return self::valid() && array() === array_diff( self::REQUIRED_LIFECYCLE, self::verifiedLifecycle() )
			? ModuleDefinition::LIFECYCLE_COMPLETE
			: ModuleDefinition::LIFECYCLE_PARTIAL;
	}

	/**
	 * Returns the lifecycle certification diagnostic.
	 */
	public static function message(): string {
		if ( ModuleDefinition::LIFECYCLE_COMPLETE === self::status() ) {
			return 'WooCommerce provider lifecycle certification evidence is bundled and valid.';
		}

		return 'WooCommerce provider lifecycle certification evidence is missing, incomplete, or invalid.';
	}

	/**
	 * Returns whether the manifest and pass result match.
	 */
	public static function valid(): bool {
		$manifest = self::manifest();
		$payload  = array(
			'schema_version' => (int) ( $manifest['schema_version'] ?? 0 ),
			'id'             => self::slugPart( (string) ( $manifest['id'] ?? '' ) ),
			'evidence'       => self::normalizedEvidence(),
			'commands'       => self::commandChecksums(),
		);
		$checksum = strtolower( trim( (string) ( $manifest['checksum'] ?? '' ) ) );
		$json     = function_exists( 'wp_json_encode' )
			? wp_json_encode( $payload, JSON_UNESCAPED_SLASHES )
			// phpcs:ignore WordPress.WP.AlternativeFunctions.json_encode_json_encode -- Supports the isolated scaffold smoke.
			: json_encode( $payload, JSON_UNESCAPED_SLASHES );
		$result = self::result();
		$passed = self::normalizeStringList( $result['commands'] ?? array() );

		return 1 === $payload['schema_version']
			&& '' !== $payload['id']
			&& array() !== $payload['commands']
			&& is_string( $json )
			&& 1 === preg_match( '/^[a-f0-9]{64}$/', $checksum )
			&& hash_equals( hash( 'sha256', $json ), $checksum )
			&& 'passed' === self::slugPart( (string) ( $result['status'] ?? '' ) )
			&& self::slugPart( (string) ( $result['certification_id'] ?? '' ) ) === $payload['id']
			&& hash_equals( $checksum, strtolower( trim( (string) ( $result['manifest_checksum'] ?? '' ) ) ) )
			&& array() === array_diff( array_keys( $payload['commands'] ), $passed );
	}

	/**
	 * @return array<string,mixed>
	 */
	private static function manifest(): array {
		if ( null !== self::$manifest ) {
			return self::$manifest;
		}

		$path = SCHEDULED_CHANGES_WC_PLUGIN_DIR . 'config/lifecycle-certification.json';
		if ( ! is_readable( $path ) ) {
			self::$manifest = array();
			return self::$manifest;
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reads bundled local JSON.
		$decoded        = json_decode( (string) file_get_contents( $path ), true );
		self::$manifest = is_array( $decoded ) ? $decoded : array();

		return self::$manifest;
	}

	/**
	 * @return array<string,mixed>
	 */
	private static function result(): array {
		if ( null !== self::$result ) {
			return self::$result;
		}

		$path = SCHEDULED_CHANGES_WC_PLUGIN_DIR . 'config/lifecycle-certification-result.json';
		if ( ! is_readable( $path ) ) {
			self::$result = array();
			return self::$result;
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Reads bundled local JSON.
		$decoded      = json_decode( (string) file_get_contents( $path ), true );
		self::$result = is_array( $decoded ) ? $decoded : array();

		return self::$result;
	}

	/**
	 * @return array<string,string[]>
	 */
	private static function normalizedEvidence(): array {
		$manifest = self::manifest();
		$raw      = is_array( $manifest['evidence'] ?? null ) ? $manifest['evidence'] : array();
		$evidence = array();

		foreach ( $raw as $step => $ids ) {
			$step = self::slugPart( (string) $step );
			if ( '' === $step || ! is_array( $ids ) ) {
				continue;
			}

			$normalized = array_values(
				array_unique(
					array_filter(
						array_map( static fn ( $id ): string => trim( (string) $id ), $ids ),
						static fn ( string $id ): bool => '' !== $id
					)
				)
			);
			sort( $normalized );

			if ( array() !== $normalized ) {
				$evidence[ $step ] = $normalized;
			}
		}

		ksort( $evidence );

		return $evidence;
	}

	/**
	 * @return array<string,string>
	 */
	private static function commandChecksums(): array {
		$manifest = self::manifest();
		$commands = array();

		foreach ( is_array( $manifest['commands'] ?? null ) ? $manifest['commands'] : array() as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}

			$id      = trim( (string) ( $row['id'] ?? '' ) );
			$command = trim( (string) ( $row['command'] ?? '' ) );
			if ( '' !== $id && '' !== $command ) {
				$commands[ $id ] = hash( 'sha256', $command );
			}
		}
		ksort( $commands );

		return $commands;
	}

	/**
	 * @param mixed $values Candidate string list.
	 * @return string[]
	 */
	private static function normalizeStringList( $values ): array {
		if ( ! is_array( $values ) ) {
			return array();
		}

		$values = array_values(
			array_unique(
				array_filter(
					array_map( static fn ( $value ): string => trim( (string) $value ), $values ),
					static fn ( string $value ): bool => '' !== $value
				)
			)
		);
		sort( $values );

		return $values;
	}

	/**
	 * Normalizes an identifier used in the certification contract.
	 */
	private static function slugPart( string $value ): string {
		return trim( (string) preg_replace( '/[^a-z0-9_-]+/', '', strtolower( $value ) ), '_-' );
	}
}
