<?php
/**
 * Source-aware finding location presentation.
 *
 * @package LocalGuardScanner
 */

namespace LocalGuardScanner\Admin;

/**
 * Converts internal locators into bounded human-readable locations.
 */
final class FindingLocationPresenter {
	/** @var mixed */
	private $wpdb;
	/** @var DatabaseReviewQueryBuilder */
	private $queries;

	/** @param mixed $wpdb WordPress database object. @param DatabaseReviewQueryBuilder|null $queries Query builder. */
	public function __construct( $wpdb = null, ?DatabaseReviewQueryBuilder $queries = null ) {
		$this->wpdb    = $wpdb;
		$this->queries = null !== $queries ? $queries : new DatabaseReviewQueryBuilder();
	}

	/**
	 * @return array{source:string,source_label:string,kind:string,kind_label:string,display:string,copy_label:string,copy_value:string,sql:string,recognized:bool}
	 */
	public function present( string $object_type, string $locator, int $stored_site_id ): array {
		if ( 'file' === $object_type ) {
			return $this->result( 'files', __( 'File', 'localguard-scanner' ), 'file', __( 'File', 'localguard-scanner' ), $locator, __( 'Copy path', 'localguard-scanner' ), $locator, '', true );
		}
		if ( 'plugin' === $object_type ) {
			return $this->result( 'plugins', __( 'Plugin', 'localguard-scanner' ), 'plugin', __( 'Plugin', 'localguard-scanner' ), $locator, __( 'Copy identifier', 'localguard-scanner' ), $locator, '', true );
		}
		if ( ! in_array( $object_type, array( 'db_content', 'db_option', 'db_user' ), true ) ) {
			return $this->unknown( $locator, __( 'Unknown source', 'localguard-scanner' ) );
		}
		if ( null === $this->wpdb || ! method_exists( $this->wpdb, 'get_blog_prefix' ) ) {
			return $this->unknown( $locator, __( 'Database identifier not recognized', 'localguard-scanner' ) );
		}

		if ( 'db_content' === $object_type && preg_match( '#^content/([1-9][0-9]*)/site/([1-9][0-9]*)$#', $locator, $matches ) ) {
			return $this->content( $this->positive_id( $matches[1] ), $this->positive_id( $matches[2] ), $stored_site_id, $locator );
		}
		if ( 'db_option' === $object_type && preg_match( '#^options/([^/]+)/site/([1-9][0-9]*)$#', $locator, $matches ) ) {
			return $this->option( rawurldecode( $matches[1] ), $this->positive_id( $matches[2] ), $stored_site_id, $locator );
		}
		if ( 'db_user' === $object_type && preg_match( '#^users/([1-9][0-9]*)/site/([1-9][0-9]*)$#', $locator, $matches ) ) {
			return $this->user( $this->positive_id( $matches[1] ), $this->positive_id( $matches[2] ), $stored_site_id, $locator );
		}
		return $this->unknown( $locator, __( 'Database identifier not recognized', 'localguard-scanner' ) );
	}

	/** @return array{source:string,source_label:string,kind:string,kind_label:string,display:string,copy_label:string,copy_value:string,sql:string,recognized:bool} */
	private function content( int $post_id, int $site_id, int $stored_site_id, string $locator ): array {
		$table = $this->site_table( $site_id, 'posts', $stored_site_id );
		if ( $post_id <= 0 || '' === $table ) {
			return $this->unknown( $locator, __( 'Database identifier not recognized', 'localguard-scanner' ) );
		}
		/* translators: 1: content ID, 2: database table, 3: WordPress site ID. */
		$display = sprintf( __( 'Content ID %1$d · table %2$s · site %3$d', 'localguard-scanner' ), $post_id, $table, $site_id );
		return $this->result( 'database', __( 'Database', 'localguard-scanner' ), 'content', __( 'Database · Content', 'localguard-scanner' ), $display, __( 'Copy SQL query', 'localguard-scanner' ), '', $this->queries->content( $table, $post_id ), true );
	}

	/** @return array{source:string,source_label:string,kind:string,kind_label:string,display:string,copy_label:string,copy_value:string,sql:string,recognized:bool} */
	private function option( string $option_name, int $site_id, int $stored_site_id, string $locator ): array {
		$table = $this->site_table( $site_id, 'options', $stored_site_id );
		if ( '' === $table || '' === $option_name || strlen( $option_name ) > 191 || preg_match( '/[\x00-\x1F\x7F]/', $option_name ) ) {
			return $this->unknown( $locator, __( 'Database identifier not recognized', 'localguard-scanner' ) );
		}
		/* translators: 1: option name, 2: database table, 3: WordPress site ID. */
		$display = sprintf( __( 'Option %1$s · table %2$s · site %3$d', 'localguard-scanner' ), $option_name, $table, $site_id );
		return $this->result( 'database', __( 'Database', 'localguard-scanner' ), 'option', __( 'Database · Option', 'localguard-scanner' ), $display, __( 'Copy SQL query', 'localguard-scanner' ), '', $this->queries->option( $table, $option_name ), true );
	}

	/** @return array{source:string,source_label:string,kind:string,kind_label:string,display:string,copy_label:string,copy_value:string,sql:string,recognized:bool} */
	private function user( int $user_id, int $site_id, int $stored_site_id, string $locator ): array {
		$prefix = $this->site_prefix( $site_id, $stored_site_id );
		$users  = isset( $this->wpdb->users ) && is_string( $this->wpdb->users ) ? $this->wpdb->users : '';
		$meta   = isset( $this->wpdb->usermeta ) && is_string( $this->wpdb->usermeta ) ? $this->wpdb->usermeta : '';
		if ( $user_id <= 0 || '' === $prefix || ! $this->safe_identifier( $users ) || ! $this->safe_identifier( $meta ) ) {
			return $this->unknown( $locator, __( 'Database identifier not recognized', 'localguard-scanner' ) );
		}
		/* translators: 1: user ID, 2: users table, 3: WordPress site ID. */
		$display = sprintf( __( 'User ID %1$d · table %2$s · site %3$d', 'localguard-scanner' ), $user_id, $users, $site_id );
		return $this->result( 'database', __( 'Database', 'localguard-scanner' ), 'user', __( 'Database · User', 'localguard-scanner' ), $display, __( 'Copy SQL query', 'localguard-scanner' ), '', $this->queries->user( $users, $meta, $prefix, $user_id ), true );
	}

	/** @return string */
	private function site_table( int $site_id, string $suffix, int $stored_site_id ): string {
		$prefix = $this->site_prefix( $site_id, $stored_site_id );
		$table  = $prefix . $suffix;
		return '' !== $prefix && $this->safe_identifier( $table ) ? $table : '';
	}

	/** @return string */
	private function site_prefix( int $site_id, int $stored_site_id ): string {
		if ( $site_id <= 0 || $stored_site_id <= 0 || $site_id !== $stored_site_id ) {
			return '';
		}
		$prefix = $this->wpdb->get_blog_prefix( $site_id );
		return is_string( $prefix ) && $this->safe_identifier( $prefix . 'options' ) ? $prefix : '';
	}

	/** @return bool */
	private function safe_identifier( string $identifier ): bool {
		return '' !== $identifier && strlen( $identifier ) <= 191 && 1 === preg_match( '/^[A-Za-z0-9_]+$/', $identifier );
	}

	/** @return int */
	private function positive_id( string $value ): int {
		if ( '' === $value || strlen( $value ) > strlen( (string) PHP_INT_MAX ) || 1 !== preg_match( '/^[0-9]+$/', $value ) ) {
			return 0;
		}
		$id = (int) $value;
		return $id > 0 && (string) $id === $value ? $id : 0;
	}

	/** @return array{source:string,source_label:string,kind:string,kind_label:string,display:string,copy_label:string,copy_value:string,sql:string,recognized:bool} */
	private function unknown( string $locator, string $label ): array {
		return $this->result( 'unknown', __( 'Unknown source', 'localguard-scanner' ), 'unknown', $label, $locator, '', '', '', false );
	}

	/** @return array{source:string,source_label:string,kind:string,kind_label:string,display:string,copy_label:string,copy_value:string,sql:string,recognized:bool} */
	private function result( string $source, string $source_label, string $kind, string $kind_label, string $display, string $copy_label, string $copy_value, string $sql, bool $recognized ): array {
		return array(
			'source'       => $source,
			'source_label' => $source_label,
			'kind'         => $kind,
			'kind_label'   => $kind_label,
			'display'      => $display,
			'copy_label'   => $copy_label,
			'copy_value'   => $copy_value,
			'sql'          => $sql,
			'recognized'   => $recognized,
		);
	}
}
