<?php
/**
 * Logger -- writes timestamped events to daily log files.
 *
 * Log files live in uploads/cwpm/logs/ and are named
 * cwpm-YYYY-MM-DD.log (one per day, WooCommerce-style).
 *
 * @package CoffeeWP_Media_Cleanup
 */

namespace CWPM\Core;

use CWPM\Singleton;

defined( 'ABSPATH' ) || exit;

class Logger extends Singleton {


	/**
	   * Relative path from the uploads basedir where log files live.
	   */
	const REL_PATH = 'cwpm/logs';

	/**
	 * Purge old log files if the auto-delete setting is enabled.
	 *
	 * Called directly when the logs view is loaded — no throttling.
	 *
	 * @return int Number of files deleted.
	 */
	public static function maybe_purge_old_logs() {
		if ( ! get_option( 'cwpm_auto_delete_logs', false ) ) {
			return 0;
		}

		$deleted = self::purge_old_logs( 30 );

		if ( $deleted > 0 ) {
			self::info( 'Auto-purged ' . $deleted . ' log file(s) older than 30 days.' );
		}

		return $deleted;
	}

	/**
	 * Return the absolute path to the logs directory.
	 *
	 * Lives under the WP uploads basedir (uploads/cwpm/logs/) so we never
	 * write outside the wp.org-permitted user-data location.
	 *
	 * @return string
	 */
	public static function get_logs_dir() {
		$upload_dir = wp_upload_dir();
		return $upload_dir['basedir'] . '/' . self::REL_PATH;
	}

	/**
	 * Ensure the logs directory exists and is protected.
	 *
	 * Creates .htaccess ("deny from all") and index.php stubs,
	 * identical pattern to Trash::ensure_trash_dir().
	 *
	 * @return string Absolute path to the logs directory.
	 */
	public static function ensure_logs_dir() {
		$logs_dir = self::get_logs_dir();

		if ( ! is_dir( $logs_dir ) ) {
			wp_mkdir_p( $logs_dir );
		}

		$htaccess = $logs_dir . '/.htaccess';
		if ( ! file_exists( $htaccess ) ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
			if ( false === file_put_contents( $htaccess, "deny from all\n" ) ) {
				// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- bootstrap-time fallback when our own log file can't be initialized.
				error_log( '[CWPM] Failed to write logs htaccess at ' . $htaccess );
			}
		}

		$index = $logs_dir . '/index.php';
		if ( ! file_exists( $index ) ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
			if ( false === file_put_contents( $index, "<?php // Silence is golden.\n" ) ) {
				// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- bootstrap-time fallback when our own log file can't be initialized.
				error_log( '[CWPM] Failed to write logs index.php at ' . $index );
			}
		}

		return $logs_dir;
	}

	/**
	 * Write a log entry to today's log file.
	 *
	 * Each line is formatted as:
	 * [2026-04-11 14:30:05] LEVEL -- Message
	 *
	 * @param string $level   Log level: 'info', 'warning', 'error'.
	 * @param string $message Human-readable message.
	 */
	public static function log( $level, $message ) {
		$logs_dir = self::ensure_logs_dir();
		$file     = $logs_dir . '/cwpm-' . gmdate( 'Y-m-d' ) . '.log';
		$time     = gmdate( 'Y-m-d H:i:s' );
		$level    = strtoupper( $level );
		$line     = "[{$time}] {$level} -- {$message}" . PHP_EOL;

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
		$bytes = file_put_contents( $file, $line, FILE_APPEND | LOCK_EX );
		if ( false === $bytes ) {

         // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- fallback when our own log file write fails; must not lose entries.
			error_log( '[CWPM] ' . trim( $line ) );
			return;
		}
	}

	/**
	 * Convenience: log an info-level event.
	 *
	 * @param string $message
	 */
	public static function info( $message ) {
		self::log( 'info', $message );
	}

	/**
	 * Convenience: log a warning-level event.
	 *
	 * @param string $message
	 */
	public static function warning( $message ) {
		self::log( 'warning', $message );
	}

	/**
	 * Convenience: log an error-level event.
	 *
	 * @param string $message
	 */
	public static function error( $message ) {
		self::log( 'error', $message );
	}

	/**
	 * List all log files in the logs directory, newest first.
	 *
	 * Returns an array of associative arrays:
	 *   [ 'filename' => 'cwpm-2026-04-11.log', 'date' => '2026-04-11',
	 *     'size' => 12345, 'size_formatted' => '12.1 KB' ]
	 *
	 * @return array[]
	 */
	public static function list_logs() {
		$logs_dir = self::get_logs_dir();

		if ( ! is_dir( $logs_dir ) ) {
			return array();
		}

		$files   = glob( $logs_dir . '/cwpm-*.log' );
		$results = array();

		if ( ! $files ) {
			return array();
		}

		foreach ( $files as $file ) {
			$basename = basename( $file );

			$date = str_replace( array( 'cwpm-', '.log' ), '', $basename );
			$size = (int) filesize( $file );

			$results[] = array(
				'filename'       => $basename,
				'date'           => $date,
				'size'           => $size,
				'size_formatted' => size_format( $size, 1 ),
			);
		}

		usort(
			$results,
			function ( $a, $b ) {
				return strcmp( $b['date'], $a['date'] );
			}
		);

		return $results;
	}

	/**
	 * Get the absolute path to a specific log file (validated).
	 *
	 * Returns false if the filename does not match the expected pattern
	 * or the file does not exist (path traversal protection).
	 *
	 * @param string $filename e.g. 'cwpm-2026-04-11.log'.
	 * @return string|false Absolute path or false.
	 */
	public static function get_log_path( $filename ) {

		if ( ! preg_match( '/^cwpm-\d{4}-\d{2}-\d{2}\.log$/', $filename ) ) {
			return false;
		}

		$path = self::get_logs_dir() . '/' . $filename;

		if ( ! file_exists( $path ) ) {
			return false;
		}

		return $path;
	}

	/**
	 * Delete a specific log file.
	 *
	 * @param string $filename e.g. 'cwpm-2026-04-11.log'.
	 * @return bool True on success.
	 */
	public static function delete_log( $filename ) {
		$path = self::get_log_path( $filename );

		if ( false === $path ) {
			return false;
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
		return unlink( $path );
	}

	/**
	 * Delete log files older than a given number of days.
	 *
	 * @param int $days Number of days to keep.
	 * @return int Number of files deleted.
	 */
	public static function purge_old_logs( $days = 30 ) {
		$logs    = self::list_logs();
		$cutoff  = gmdate( 'Y-m-d', strtotime( "-{$days} days" ) );
		$deleted = 0;

		foreach ( $logs as $log ) {
			if ( $log['date'] < $cutoff ) {
				if ( self::delete_log( $log['filename'] ) ) {
					++$deleted;
				}
			}
		}

		return $deleted;
	}
}
