<?php
/**
 * BoostBot Logger — WP_DEBUG-gated logging utility.
 *
 * All plugin logging goes through this class so:
 * 1. Production sites with WP_DEBUG off produce no debug log output
 * 2. The phpcs:ignore for error_log lives in one place
 * 3. WordPress.org PCP compliance is maintained
 *
 * @package BoostBot
 * @since   1.0.0
 */

// Prevent direct access
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class BoostBot_Logger
 *
 * Provides WP_DEBUG-gated logging via error_log() plus optional
 * custom file logging and browser console output for admins.
 *
 * @since 1.0.0
 */
class BoostBot_Logger {

	/**
	 * Log storage for console output.
	 *
	 * @var array
	 */
	private static $logs = array();

	/**
	 * Path to log file.
	 *
	 * @var string
	 */
	private static $log_file;

	/**
	 * Whether debug mode is enabled (cached from WP_DEBUG).
	 *
	 * @var bool
	 */
	private static $is_debug = false;

	/**
	 * Whether the logger has been initialized.
	 *
	 * @var bool
	 */
	private static $initialized = false;

	/**
	 * Log levels.
	 */
	const LEVEL_DEBUG   = 'DEBUG';
	const LEVEL_INFO    = 'INFO';
	const LEVEL_WARNING = 'WARNING';
	const LEVEL_ERROR   = 'ERROR';

	/**
	 * Initialize the logger.
	 *
	 * @return void
	 */
	public static function init() {
		if ( self::$initialized ) {
			return;
		}

		self::$is_debug = defined( 'WP_DEBUG' ) && WP_DEBUG;
		$upload_dir     = wp_upload_dir();
		self::$log_file = $upload_dir['basedir'] . '/boostbot/boostbot.log';

		if ( self::$is_debug ) {
			$log_dir = dirname( self::$log_file );
			if ( ! file_exists( $log_dir ) ) {
				wp_mkdir_p( $log_dir );

				$htaccess = $log_dir . '/.htaccess';
				if ( ! file_exists( $htaccess ) ) {
					file_put_contents( $htaccess, 'deny from all' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
				}

				$index = $log_dir . '/index.php';
				if ( ! file_exists( $index ) ) {
					file_put_contents( $index, '<?php // Silence is golden.' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
				}
			}
		}

		add_action( 'admin_footer', array( __CLASS__, 'output_console_logs' ), 9999 );
		add_action( 'wp_footer', array( __CLASS__, 'output_console_logs' ), 9999 );

		self::$initialized = true;
	}

	/**
	 * Log a message.
	 *
	 * @param string $message The message to log.
	 * @param string $level   The log level (DEBUG, INFO, WARNING, ERROR).
	 * @param array  $context Optional. Additional context data.
	 * @return void
	 */
	public static function log( $message, $level = self::LEVEL_INFO, $context = array() ) {
		$is_error = ( self::LEVEL_ERROR === $level );
		$debug_on = defined( 'WP_DEBUG' ) && WP_DEBUG;

		// Skip non-error logs when WP_DEBUG is off (except ERROR which always fires).
		if ( ! $is_error && ! $debug_on ) {
			return;
		}

		$timestamp = function_exists( 'current_time' ) ? current_time( 'mysql' ) : gmdate( 'Y-m-d H:i:s' );

		$entry = array(
			'timestamp' => $timestamp,
			'level'     => $level,
			'message'   => $message,
			'context'   => $context,
		);

		self::$logs[] = $entry;

		self::write_to_log( $entry );
	}

	/**
	 * Log a debug message (only when WP_DEBUG is on).
	 *
	 * @param string $message The message to log.
	 * @param array  $context Optional. Additional context data.
	 * @return void
	 */
	public static function debug( $message, $context = array() ) {
		self::log( $message, self::LEVEL_DEBUG, $context );
	}

	/**
	 * Log an info message (only when WP_DEBUG is on).
	 *
	 * @param string $message The message to log.
	 * @param array  $context Optional. Additional context data.
	 * @return void
	 */
	public static function info( $message, $context = array() ) {
		self::log( $message, self::LEVEL_INFO, $context );
	}

	/**
	 * Log a warning message (only when WP_DEBUG is on).
	 *
	 * @param string $message The message to log.
	 * @param array  $context Optional. Additional context data.
	 * @return void
	 */
	public static function warning( $message, $context = array() ) {
		self::log( $message, self::LEVEL_WARNING, $context );
	}

	/**
	 * Log an error. Always active regardless of WP_DEBUG since errors
	 * indicate real failures that site owners should know about.
	 *
	 * @param string $message The message to log.
	 * @param array  $context Optional. Additional context data.
	 * @return void
	 */
	public static function error( $message, $context = array() ) {
		self::log( $message, self::LEVEL_ERROR, $context );
	}

	/**
	 * Write log entry to error_log and optional custom log file.
	 *
	 * ERROR-level messages always fire. Other levels are gated by WP_DEBUG.
	 * The phpcs:ignore for error_log is centralized here — no other file
	 * in the plugin should call error_log() directly.
	 *
	 * @param array $entry The log entry.
	 * @return void
	 */
	private static function write_to_log( $entry ) {
		$line = sprintf(
			'[BoostBot][%s] %s',
			$entry['level'],
			$entry['message']
		);

		if ( ! empty( $entry['context'] ) ) {
			$line .= ' | Context: ' . wp_json_encode( $entry['context'] );
		}

		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Centralized BoostBot logging. ERROR always active; other levels gated by WP_DEBUG.
		error_log( $line );

		$debug_on = defined( 'WP_DEBUG' ) && WP_DEBUG;
		if ( $debug_on && ! empty( self::$log_file ) ) {
			$file_line = sprintf( '[%s] %s', $entry['timestamp'], $line );
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
			file_put_contents( self::$log_file, $file_line . PHP_EOL, FILE_APPEND | LOCK_EX );
		}
	}

	/**
	 * Output logs to browser console.
	 *
	 * @return void
	 */
	public static function output_console_logs() {
		if ( empty( self::$logs ) || ! ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ) {
			return;
		}

		if ( ! current_user_can( 'manage_options' ) ) {
			return;
		}

		$js_lines = array();
		$js_lines[] = '(function(){';
		$js_lines[] = 'if(typeof console==="undefined")return;';
		$js_lines[] = 'console.group("%c\\ud83e\\udd16 BoostBot Debug Logs","color:#3BC2D0;font-weight:bold;font-size:14px;");';

		foreach ( self::$logs as $log ) {
			$json_flags = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT;
			$msg_json   = wp_json_encode( '[' . $log['timestamp'] . '] ' . $log['message'], $json_flags );
			$ctx_json   = wp_json_encode( ! empty( $log['context'] ) ? $log['context'] : null, $json_flags );

			if ( $log['level'] === self::LEVEL_ERROR ) {
				$method = 'error';
			} elseif ( $log['level'] === self::LEVEL_WARNING ) {
				$method = 'warn';
			} elseif ( $log['level'] === self::LEVEL_DEBUG ) {
				$method = 'debug';
			} else {
				$method = 'log';
			}

			$js_lines[] = sprintf( 'console.%s(%s,%s);', $method, $msg_json, $ctx_json );
		}

		$js_lines[] = 'console.groupEnd();';
		$js_lines[] = '})();';

		$handle = is_admin() ? 'boostbot-admin' : 'boostbot-widget';
		wp_add_inline_script( $handle, implode( "\n", $js_lines ) );
	}

	/**
	 * Get all logs.
	 *
	 * @return array
	 */
	public static function get_logs() {
		return self::$logs;
	}

	/**
	 * Clear in-memory logs.
	 *
	 * @return void
	 */
	public static function clear_logs() {
		self::$logs = array();
	}

	/**
	 * Clear the log file.
	 *
	 * @return bool True on success, false on failure.
	 */
	public static function clear_log_file() {
		if ( file_exists( self::$log_file ) ) {
			return wp_delete_file( self::$log_file );
		}
		return true;
	}

	/**
	 * Get log file contents.
	 *
	 * @param int $lines Optional. Number of lines to return (from end). Default 100.
	 * @return string|false The log contents or false on failure.
	 */
	public static function get_log_file_contents( $lines = 100 ) {
		if ( ! file_exists( self::$log_file ) ) {
			return '';
		}

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
		$content = file_get_contents( self::$log_file );

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

		$all_lines  = explode( PHP_EOL, $content );
		$last_lines = array_slice( $all_lines, -$lines );

		return implode( PHP_EOL, $last_lines );
	}

	/**
	 * Get log file size.
	 *
	 * @return int|false File size in bytes or false on failure.
	 */
	public static function get_log_file_size() {
		if ( ! file_exists( self::$log_file ) ) {
			return 0;
		}

		return filesize( self::$log_file );
	}

	/**
	 * Check if logging is enabled.
	 *
	 * @return bool
	 */
	public static function is_enabled() {
		return defined( 'WP_DEBUG' ) && WP_DEBUG;
	}
}
