<?php
/**
 * CacheLoop Logger
 *
 * Simple file-based logger for cache hits/misses, errors, and debug info.
 * Log entries are stored in wp-content/cache/cacheloop/debug.log
 *
 * @package CacheLoop
 */

// Security: Prevent direct file access.
defined( 'ABSPATH' ) || exit;

/**
 * Class CACHELOOP_Logger
 *
 * Provides a lightweight, file-based logging mechanism for CacheLoop.
 * Rotates log files when they exceed 1MB to avoid filling disk space.
 */
class CACHELOOP_Logger {

	const MAX_LOG_SIZE = 1048576; // 1MB
	const LOG_FILE     = 'debug.log';

	/**
	 * Log levels.
	 */
	const LEVEL_INFO  = 'INFO';
	const LEVEL_WARN  = 'WARN';
	const LEVEL_ERROR = 'ERROR';
	const LEVEL_HIT   = 'HIT';
	const LEVEL_MISS  = 'MISS';

	/**
	 * Write a log entry.
	 *
	 * @param string $message Human-readable message.
	 * @param string $level   Log level: INFO, WARN, ERROR, HIT, MISS.
	 * @param array  $context Optional associative array of contextual data.
	 * @return void
	 */
	public static function log( $message, $level = self::LEVEL_INFO, $context = array() ) {
		$timestamp = gmdate( 'Y-m-d H:i:s' ) . ' UTC';
		$entry     = sprintf( '[%s] [%s] %s', $timestamp, $level, $message );

		if ( ! empty( $context ) ) {
			$entry .= ' | ' . wp_json_encode( $context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
		}

		$entry .= "\n";

		$log_dir  = WP_CONTENT_DIR . '/cache/cacheloop/';
		$log_file = $log_dir . self::LOG_FILE;

		// Ensure directory exists.
		if ( ! is_dir( $log_dir ) ) {
			wp_mkdir_p( $log_dir );
		}

		// Rotate if file is too large.
		if ( file_exists( $log_file ) && filesize( $log_file ) > self::MAX_LOG_SIZE ) {
			$backup = $log_dir . 'debug.log.bak';
            // phpcs:ignore WordPress.WP.AlternativeFunctions.rename_rename, PluginCheck.CodeAnalysis.WriteFile.PluginDirectoryWrite -- wp-content/cache/, not the plugin's own directory.
			rename( $log_file, $backup );
		}

		// Append to log file.
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen, PluginCheck.CodeAnalysis.WriteFile.PluginDirectoryWrite -- wp-content/cache/, not the plugin's own directory.
		$fp = fopen( $log_file, 'a' );
		if ( $fp ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite, PluginCheck.CodeAnalysis.WriteFile.PluginDirectoryWrite -- wp-content/cache/, not the plugin's own directory.
			fwrite( $fp, $entry );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
			fclose( $fp );
		}
	}

	/**
	 * Convenience: log a cache hit.
	 *
	 * @param string $url Requested URL.
	 * @return void
	 */
	public static function cache_hit( $url ) {
		self::log( 'Cache HIT', self::LEVEL_HIT, array( 'url' => $url ) );
	}

	/**
	 * Convenience: log a cache miss.
	 *
	 * @param string $url Requested URL.
	 * @param string $reason Why it missed (optional).
	 * @return void
	 */
	public static function cache_miss( $url, $reason = '' ) {
		$msg = 'Cache MISS';
		if ( $reason ) {
			$msg .= ' — ' . $reason;
		}
		self::log(
			$msg,
			self::LEVEL_MISS,
			array(
				'url'    => $url,
				'reason' => $reason,
			)
		);
	}

	/**
	 * Convenience: log a cache error.
	 *
	 * @param string $message Error message.
	 * @param string $context Additional context.
	 * @return void
	 */
	public static function cache_error( $message, $context = '' ) {
		self::log( 'Cache ERROR: ' . $message, self::LEVEL_ERROR, array( 'context' => $context ) );
	}

	/**
	 * Get the last N log lines.
	 *
	 * @param int $lines Number of lines to return (default 50).
	 * @return string Log content.
	 */
	public static function get_last_lines( $lines = 50 ) {
		$log_file = WP_CONTENT_DIR . '/cache/cacheloop/' . self::LOG_FILE;

		if ( ! file_exists( $log_file ) ) {
			return '';
		}

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_get_contents
		$content = file_get_contents( $log_file );
		if ( empty( $content ) ) {
			return '';
		}

		$all_lines = explode( "\n", trim( $content ) );
		$last_n    = array_slice( $all_lines, - $lines );

		return implode( "\n", $last_n );
	}

	/**
	 * Clear the debug log.
	 *
	 * @return bool True on success.
	 */
	public static function clear_log() {
		$log_file = WP_CONTENT_DIR . '/cache/cacheloop/' . self::LOG_FILE;

		if ( ! file_exists( $log_file ) ) {
			return true;
		}

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen, PluginCheck.CodeAnalysis.WriteFile.PluginDirectoryWrite -- wp-content/cache/, not the plugin's own directory.
		$fp = fopen( $log_file, 'w' );
		if ( $fp ) {
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fwrite, PluginCheck.CodeAnalysis.WriteFile.PluginDirectoryWrite -- wp-content/cache/, not the plugin's own directory.
			fwrite( $fp, '' );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
			fclose( $fp );
			return true;
		}

		return false;
	}
}
