<?php
/**
 * Storage Monitor Module
 *
 * @package StorageMonitor
 * @since 1.0.0
 */

namespace StorageMonitor;

use WP_PowerSuite\Core\AbstractModule;

class Module extends AbstractModule {

	private const MODULE_ID = 'storage-monitor';
	private const MODULE_VERSION = '1.3.0';
	private const CACHE_KEY = 'wpps_storage_monitor_data';
	private const CACHE_DURATION = 3600;

	/**
	 * Path fragments that commonly trigger open_basedir or symlink errors on shared hosting.
	 *
	 * @var list<string>
	 */
	private const SKIPPED_PATH_FRAGMENTS = [
		'.cagefs',
		'.cl.selector',
		'.clwpos',
		'.well-known',
		'.tmb',
		'.wp-cli',
		'cgi-bin',
		'wp-nc-easywp',
	];

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

	public function getName(): string {
		return __( 'Storage Monitor', 'wp-powersuite' );
	}

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

	public function getDescription(): string {
		return __( 'Disk usage at a glance on the dashboard home.', 'wp-powersuite' );
	}

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

	public function getDependencies(): array {
		return [];
	}

	protected function registerHooks(): void {
		if ( ! is_admin() ) {
			return;
		}

		add_action( 'wp_dashboard_setup', [ $this, 'registerDashboardWidget' ] );
		add_action( 'wp_ajax_wpps_refresh_storage_data', [ $this, 'handleAjaxRefresh' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAssets' ] );
	}

	public function enqueueAssets( string $hook ): void {
		if ( $hook !== 'index.php' ) {
			return;
		}

		add_action( 'admin_head', [ $this, 'addWidgetStyles' ] );

		$base       = defined( 'WP_POWERSUITE_PLUGIN_URL' ) ? WP_POWERSUITE_PLUGIN_URL : '';
		$script_url = $base . 'modules/' . self::MODULE_ID . '/assets/storage-monitor.js';

		wp_enqueue_script(
			'wpps-storage-monitor',
			$script_url,
			[ 'jquery' ],
			self::MODULE_VERSION,
			true
		);

		wp_localize_script(
			'wpps-storage-monitor',
			'wppsStorageMonitor',
			[
				'strings' => [
					'justNow'   => __( 'just now', 'wp-powersuite' ),
					'ago'       => __( 'ago', 'wp-powersuite' ),
					'cacheInfo' => __( 'Cached for 1 hour. Last updated:', 'wp-powersuite' ),
					'failed'    => __( 'Failed to refresh storage data.', 'wp-powersuite' ),
					'error'     => __( 'An error occurred while refreshing storage data.', 'wp-powersuite' ),
				],
			]
		);
	}

	public function registerDashboardWidget(): void {
		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		wp_add_dashboard_widget(
			'wpps_storage_monitor',
			__( 'Storage Monitor', 'wp-powersuite' ),
			[ $this, 'renderDashboardWidget' ]
		);
	}

	public function renderDashboardWidget(): void {
		$data = $this->getStorageData();

		?>
		<div class="wpps-storage-monitor">
			<?php if ( ! empty( $data['error'] ) ) : ?>
				<div class="wpps-storage-error">
					<p><?php echo esc_html( $data['error'] ); ?></p>
					<p class="wpps-storage-error-actions">
						<button type="button" class="button button-secondary wpps-storage-refresh" data-nonce="<?php echo esc_attr( wp_create_nonce( 'wpps_refresh_storage' ) ); ?>">
							<span class="dashicons dashicons-update"></span>
							<?php esc_html_e( 'Try Again', 'wp-powersuite' ); ?>
						</button>
					</p>
				</div>
			<?php else : ?>
				<div class="wpps-storage-summary">
					<div class="wpps-storage-total">
						<div>
							<span class="wpps-storage-label"><?php esc_html_e( 'Tracked Storage', 'wp-powersuite' ); ?></span>
							<span class="wpps-storage-sublabel"><?php esc_html_e( 'Uploads, plugins, themes, and database', 'wp-powersuite' ); ?></span>
						</div>
						<span class="wpps-storage-value"><?php echo esc_html( $data['total_formatted'] ); ?></span>
					</div>
				</div>

				<div class="wpps-storage-breakdown">
					<?php $this->renderBreakdownItem( 'uploads', __( 'Uploads', 'wp-powersuite' ), '#2271b1', $data ); ?>
					<?php $this->renderBreakdownItem( 'plugins', __( 'Plugins', 'wp-powersuite' ), '#00a32a', $data ); ?>
					<?php $this->renderBreakdownItem( 'themes', __( 'Themes', 'wp-powersuite' ), '#f59e0b', $data ); ?>
					<?php $this->renderBreakdownItem( 'database', __( 'Database', 'wp-powersuite' ), '#6366f1', $data ); ?>
				</div>

				<div class="wpps-storage-footer">
					<button type="button" class="button button-secondary wpps-storage-refresh" data-nonce="<?php echo esc_attr( wp_create_nonce( 'wpps_refresh_storage' ) ); ?>">
						<span class="dashicons dashicons-update"></span>
						<?php esc_html_e( 'Refresh Data', 'wp-powersuite' ); ?>
					</button>
					<span class="wpps-storage-cache-info">
						<?php
						printf(
							esc_html__( 'Cached for 1 hour. Last updated: %s', 'wp-powersuite' ),
							esc_html( human_time_diff( $data['last_updated'], time() ) ) . ' ' . esc_html__( 'ago', 'wp-powersuite' )
						);
						?>
					</span>
				</div>
			<?php endif; ?>
		</div>
		<?php
	}

	/**
	 * @param array<string, mixed> $data Storage payload.
	 */
	private function renderBreakdownItem( string $key, string $label, string $color, array $data ): void {
		?>
		<div class="wpps-storage-item wpps-storage-<?php echo esc_attr( $key ); ?>">
			<div class="wpps-storage-item-header">
				<span class="wpps-storage-item-label">
					<span class="wpps-storage-color-indicator" style="background-color: <?php echo esc_attr( $color ); ?>;"></span>
					<?php echo esc_html( $label ); ?>
				</span>
				<span class="wpps-storage-item-size"><?php echo esc_html( $data[ $key . '_formatted' ] ?? size_format( 0, 2 ) ); ?></span>
			</div>
			<div class="wpps-storage-progress">
				<div class="wpps-storage-progress-bar" style="width: <?php echo esc_attr( (string) ( $data[ $key . '_percentage' ] ?? 0 ) ); ?>%; background-color: <?php echo esc_attr( $color ); ?>;"></div>
			</div>
			<div class="wpps-storage-item-percentage"><?php echo esc_html( (string) ( $data[ $key . '_percentage' ] ?? 0 ) ); ?>%</div>
		</div>
		<?php
	}

	/**
	 * @return array<string, mixed>
	 */
	private function getStorageData( bool $force_refresh = false ): array {
		if ( ! $force_refresh ) {
			$cached = get_transient( self::CACHE_KEY );
			if ( $cached !== false && is_array( $cached ) ) {
				return $cached;
			}
		}

		$uploads_scan  = $this->scanDirectory( $this->getUploadsPath() );
		$plugins_scan  = $this->scanDirectory( WP_PLUGIN_DIR );
		$themes_scan   = $this->scanDirectory( get_theme_root() );
		$database_scan = $this->scanDatabase();

		$any_success = $uploads_scan['success']
			|| $plugins_scan['success']
			|| $themes_scan['success']
			|| $database_scan['success'];

		if ( ! $any_success ) {
			return $this->buildErrorPayload(
				__( 'Unable to calculate storage usage. Please check directory and database permissions.', 'wp-powersuite' )
			);
		}

		$uploads_size  = $uploads_scan['size'];
		$plugins_size  = $plugins_scan['size'];
		$themes_size   = $themes_scan['size'];
		$database_size = $database_scan['size'];
		$total         = $uploads_size + $plugins_size + $themes_size + $database_size;

		$data = $this->buildSuccessPayload( $uploads_size, $plugins_size, $themes_size, $database_size, $total );

		set_transient( self::CACHE_KEY, $data, self::CACHE_DURATION );

		return $data;
	}

	public function handleAjaxRefresh(): void {
		if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'wpps_refresh_storage' ) ) {
			wp_send_json_error( [ 'message' => __( 'Security check failed.', 'wp-powersuite' ) ] );
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( [ 'message' => __( 'Insufficient permissions.', 'wp-powersuite' ) ] );
		}

		delete_transient( self::CACHE_KEY );
		$data = $this->getStorageData( true );

		if ( ! empty( $data['error'] ) ) {
			wp_send_json_error(
				[
					'message' => is_string( $data['error'] ) ? $data['error'] : __( 'Unable to calculate storage usage.', 'wp-powersuite' ),
				]
			);
		}

		wp_send_json_success( $data );
	}

	public function addWidgetStyles(): void {
		?>
		<style type="text/css">
			.wpps-storage-monitor {
				padding: 0;
			}

			.wpps-storage-summary {
				margin-bottom: 20px;
				padding: 15px;
				background: #f6f7f7;
				border: 1px solid #c3c4c7;
				border-radius: 4px;
			}

			.wpps-storage-total {
				display: flex;
				justify-content: space-between;
				align-items: center;
				gap: 12px;
			}

			.wpps-storage-label {
				display: block;
				font-size: 13px;
				color: #646970;
				font-weight: 500;
			}

			.wpps-storage-sublabel {
				display: block;
				margin-top: 2px;
				font-size: 11px;
				color: #8c8f94;
			}

			.wpps-storage-value {
				font-size: 24px;
				font-weight: 700;
				color: #1d2327;
				letter-spacing: -0.5px;
				white-space: nowrap;
			}

			.wpps-storage-breakdown {
				margin-bottom: 15px;
			}

			.wpps-storage-item {
				margin-bottom: 18px;
			}

			.wpps-storage-item:last-child {
				margin-bottom: 0;
			}

			.wpps-storage-item-header {
				display: flex;
				justify-content: space-between;
				align-items: center;
				margin-bottom: 8px;
			}

			.wpps-storage-item-label {
				display: flex;
				align-items: center;
				gap: 8px;
				font-size: 13px;
				font-weight: 600;
				color: #1d2327;
			}

			.wpps-storage-color-indicator {
				width: 12px;
				height: 12px;
				border-radius: 3px;
				display: inline-block;
				flex-shrink: 0;
			}

			.wpps-storage-item-size {
				font-size: 13px;
				font-weight: 600;
				color: #50575e;
			}

			.wpps-storage-progress {
				height: 8px;
				background-color: #f0f0f1;
				border-radius: 4px;
				overflow: hidden;
				margin-bottom: 6px;
			}

			.wpps-storage-progress-bar {
				height: 100%;
				border-radius: 4px;
				transition: width 0.3s ease;
			}

			.wpps-storage-item-percentage {
				font-size: 11px;
				color: #8c8f94;
				text-align: right;
			}

			.wpps-storage-footer {
				display: flex;
				justify-content: space-between;
				align-items: center;
				padding-top: 15px;
				border-top: 1px solid #f0f0f1;
				margin-top: 15px;
				gap: 12px;
			}

			.wpps-storage-refresh .dashicons {
				font-size: 16px;
				width: 16px;
				height: 16px;
				vertical-align: text-top;
				margin-right: 4px;
			}

			.wpps-storage-refresh.loading .dashicons {
				animation: wpps-spin 1s linear infinite;
			}

			@keyframes wpps-spin {
				from { transform: rotate(0deg); }
				to { transform: rotate(360deg); }
			}

			.wpps-storage-cache-info {
				font-size: 11px;
				color: #8c8f94;
				text-align: right;
			}

			.wpps-storage-error {
				padding: 15px;
				background: #fff3cd;
				border-left: 4px solid #ffc107;
				border-radius: 4px;
			}

			.wpps-storage-error p {
				margin: 0 0 10px;
				color: #856404;
			}

			.wpps-storage-error-actions {
				margin-bottom: 0 !important;
			}
		</style>
		<?php
	}

	private function getUploadsPath(): string {
		$uploads = wp_get_upload_dir();

		return ! empty( $uploads['basedir'] ) ? (string) $uploads['basedir'] : '';
	}

	/**
	 * @return array{size: int, success: bool}
	 */
	private function scanDirectory( string $directory ): array {
		if ( $directory === '' || ! is_dir( $directory ) || ! is_readable( $directory ) ) {
			return [
				'size'    => 0,
				'success' => false,
			];
		}

		$size = 0;

		try {
			$iterator = new \RecursiveIteratorIterator(
				new \RecursiveDirectoryIterator( $directory, \FilesystemIterator::SKIP_DOTS ),
				\RecursiveIteratorIterator::LEAVES_ONLY,
				\RecursiveIteratorIterator::CATCH_GET_CHILD
			);

			foreach ( $iterator as $file ) {
				if ( ! $file instanceof \SplFileInfo || ! $file->isFile() ) {
					continue;
				}

				$file_path = $file->getPathname();
				if ( ! $this->shouldProcessPath( $file_path ) ) {
					continue;
				}

				$size += $file->getSize();
			}
		} catch ( \Exception $e ) {
			return [
				'size'    => 0,
				'success' => false,
			];
		}

		return [
			'size'    => $size,
			'success' => true,
		];
	}

	private function shouldProcessPath( string $file_path ): bool {
		foreach ( self::SKIPPED_PATH_FRAGMENTS as $fragment ) {
			if ( str_contains( $file_path, $fragment ) ) {
				return false;
			}
		}

		if ( str_ends_with( $file_path, '/wp' ) || str_ends_with( $file_path, '\\wp' ) ) {
			return false;
		}

		$path_length = mb_strlen( $file_path );
		if ( ! is_numeric( $path_length ) || (int) $path_length > PHP_MAXPATHLEN ) {
			return false;
		}

		return true;
	}

	/**
	 * @return array{size: int, success: bool}
	 */
	private function scanDatabase(): array {
		$size = $this->getDatabaseSizeFromInformationSchema();
		if ( $size !== null ) {
			return [
				'size'    => $size,
				'success' => true,
			];
		}

		$size = $this->getDatabaseSizeFromTableStatus();
		if ( $size !== null ) {
			return [
				'size'    => $size,
				'success' => true,
			];
		}

		return [
			'size'    => 0,
			'success' => false,
		];
	}

	private function getDatabaseSizeFromInformationSchema(): ?int {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$result = $wpdb->get_var(
			$wpdb->prepare(
				'SELECT ROUND(SUM(data_length + index_length), 0) FROM information_schema.TABLES WHERE table_schema = %s',
				$wpdb->dbname
			)
		);

		if ( $result === null || $wpdb->last_error !== '' ) {
			return null;
		}

		return max( 0, (int) $result );
	}

	private function getDatabaseSizeFromTableStatus(): ?int {
		global $wpdb;

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		$result = $wpdb->get_results( 'SHOW TABLE STATUS', ARRAY_A );
		if ( ! is_array( $result ) || $wpdb->last_error !== '' ) {
			return null;
		}

		$db_size = 0;
		foreach ( $result as $row ) {
			$db_size += (int) ( $row['Data_length'] ?? 0 );
			$db_size += (int) ( $row['Index_length'] ?? 0 );
		}

		return $db_size;
	}

	/**
	 * @return array<string, mixed>
	 */
	private function buildErrorPayload( string $message ): array {
		return [
			'error'        => $message,
			'total'        => 0,
			'uploads'      => 0,
			'plugins'      => 0,
			'themes'       => 0,
			'database'     => 0,
			'last_updated' => time(),
		];
	}

	/**
	 * @return array<string, mixed>
	 */
	private function buildSuccessPayload( int $uploads_size, int $plugins_size, int $themes_size, int $database_size, int $total ): array {
		$uploads_percentage  = $total > 0 ? round( ( $uploads_size / $total ) * 100, 1 ) : 0;
		$plugins_percentage  = $total > 0 ? round( ( $plugins_size / $total ) * 100, 1 ) : 0;
		$themes_percentage   = $total > 0 ? round( ( $themes_size / $total ) * 100, 1 ) : 0;
		$database_percentage = $total > 0 ? round( ( $database_size / $total ) * 100, 1 ) : 0;

		return [
			'error'               => false,
			'total'               => $total,
			'uploads'             => $uploads_size,
			'plugins'             => $plugins_size,
			'themes'              => $themes_size,
			'database'            => $database_size,
			'total_formatted'     => size_format( $total, 2 ),
			'uploads_formatted'   => size_format( $uploads_size, 2 ),
			'plugins_formatted'   => size_format( $plugins_size, 2 ),
			'themes_formatted'    => size_format( $themes_size, 2 ),
			'database_formatted'  => size_format( $database_size, 2 ),
			'uploads_percentage'  => $uploads_percentage,
			'plugins_percentage'  => $plugins_percentage,
			'themes_percentage'   => $themes_percentage,
			'database_percentage' => $database_percentage,
			'last_updated'        => time(),
		];
	}
}
