<?php

namespace LimitMediaUploadSize;

use WP_PowerSuite\Core\AbstractModule;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Module extends AbstractModule {

	private const MODULE_ID = 'limit-media-upload-size';
	private const MODULE_VERSION = '1.0.0';
	private const MIN_MB = 1;
	private const MAX_MB = 1000;
	private const DEFAULT_MB = 10;

	private ?int $max_bytes_cache = null;

	private bool $upload_size_filter_registered = false;

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Limit Media Upload Size', 'wp-powersuite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Caps how large an upload can be—helpful for shared hosting or editorial guidelines.', 'wp-powersuite' );
	}

	public function getContext(): string|array {
		return [ 'admin', 'rest', 'ajax' ];
	}

	protected function registerHooks(): void {
		if ( $this->getMaxBytes() <= 0 ) {
			return;
		}

		add_filter( 'wp_handle_upload_prefilter', [ $this, 'validateUploadSize' ], 999 );
		add_filter( 'rest_pre_upload_file', [ $this, 'validateRestUploadSize' ], 999, 2 );

		add_action( 'load-upload.php', [ $this, 'registerUploadSizeLimit' ] );
		add_action( 'load-media.php', [ $this, 'registerUploadSizeLimit' ] );
		add_action( 'wp_enqueue_media', [ $this, 'registerUploadSizeLimit' ] );
	}

	public function registerUploadSizeLimit(): void {
		if ( $this->upload_size_filter_registered ) {
			return;
		}

		$this->upload_size_filter_registered = true;
		add_filter( 'upload_size_limit', [ $this, 'limitUploadSize' ], 999 );
	}

	public function limitUploadSize( $size ): int {
		return min( (int) $size, $this->getMaxBytes() );
	}

	/** @param array<string, mixed> $file */
	public function validateUploadSize( array $file ): array {
		if ( ! $this->isMediaUploadRequest() ) {
			return $file;
		}

		if ( isset( $file['error'] ) && UPLOAD_ERR_OK !== (int) $file['error'] ) {
			return $file;
		}

		$error = $this->getUploadSizeError( $file );
		if ( $error !== null ) {
			$file['error'] = $error;
		}

		return $file;
	}

	/**
	 * @param array<string, mixed>|\WP_Error $file
	 * @return array<string, mixed>|\WP_Error
	 */
	public function validateRestUploadSize( $file, $request ) {
		unset( $request );

		if ( ! is_array( $file ) ) {
			return $file;
		}

		$error = $this->getUploadSizeError( $file );
		if ( $error === null ) {
			return $file;
		}

		return new \WP_Error(
			'upload_size_exceeded',
			$error,
			[ 'status' => 413 ]
		);
	}

	public static function sanitizeMaxUploadSizeMb( $value ): int {
		return max( self::MIN_MB, min( self::MAX_MB, absint( $value ) ) );
	}

	/** @param array<string, mixed> $raw */
	public static function normalizeSettings( array $raw ): array {
		return [
			'max_upload_size' => self::sanitizeMaxUploadSizeMb( $raw['max_upload_size'] ?? self::DEFAULT_MB ),
		];
	}

	public static function getEffectiveMaxBytes( int $max_mb ): int {
		$max_mb       = self::sanitizeMaxUploadSizeMb( $max_mb );
		$module_bytes = $max_mb * 1024 * 1024;
		$upload_bytes = wp_convert_hr_to_bytes( (string) ini_get( 'upload_max_filesize' ) );
		$post_bytes   = wp_convert_hr_to_bytes( (string) ini_get( 'post_max_size' ) );

		return min( $module_bytes, $upload_bytes, $post_bytes );
	}

	/** @param array<string, mixed> $file */
	private function getUploadSizeError( array $file ): ?string {
		$file_size = isset( $file['size'] ) ? (int) $file['size'] : 0;
		if ( $file_size <= $this->getMaxBytes() ) {
			return null;
		}

		return sprintf(
			__( 'File size exceeds the maximum allowed size of %s.', 'wp-powersuite' ),
			size_format( $this->getMaxBytes(), 0 )
		);
	}

	private function getMaxBytes(): int {
		if ( $this->max_bytes_cache !== null ) {
			return $this->max_bytes_cache;
		}

		$settings              = self::normalizeSettings( $this->moduleSettings()->all( [] ) );
		$this->max_bytes_cache = $settings['max_upload_size'] * 1024 * 1024;

		return $this->max_bytes_cache;
	}

	private function isMediaUploadRequest(): bool {
		if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
			$action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : '';

			return $action === 'upload-attachment';
		}

		return ( $GLOBALS['pagenow'] ?? '' ) === 'upload.php';
	}
}
