<?php

namespace CelerSearch\Utilities;

/**
 * Channel-based logger with file rotation support
 *
 * Only logs when WP_DEBUG is true. Writes to separate files by channel
 * with automatic size-based rotation.
 *
 * Usage:
 *   Logger::channel('observers')->info('Post synced', ['post_id' => 123]);
 *   Logger::channel('queries')->debug('Search executed', ['term' => 'test']);
 *   Logger::channel('errors')->error('Sync failed', ['exception' => $e->getMessage()]);
 */
class Logger {

	/**
	 * Log channels
	 */
	const CHANNEL_OBSERVERS = 'observers';
	const CHANNEL_QUERIES   = 'queries';
	const CHANNEL_ERRORS    = 'errors';
	const CHANNEL_DEBUG     = 'debug';
	const CHANNEL_QUEUE     = 'queue';

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

	/**
	 * Maximum file size before rotation (5MB)
	 */
	const MAX_FILE_SIZE = 5 * 1024 * 1024;

	/**
	 * Maximum number of rotated files to keep
	 */
	const MAX_ROTATED_FILES = 5;

	/**
	 * Base log directory relative to uploads directory
	 */
	const LOG_DIR = 'celersearch/logs';

	/**
	 * Current channel
	 *
	 * @var string
	 */
	private string $channel;

	/**
	 * Cached log directory path
	 *
	 * @var string|null
	 */
	private static ?string $log_dir = null;

	/**
	 * Filesystem instance
	 *
	 * @var Filesystem|null
	 */
	private static ?Filesystem $filesystem = null;

	/**
	 * Valid channels
	 *
	 * @var array
	 */
	private static array $valid_channels = [
		self::CHANNEL_OBSERVERS,
		self::CHANNEL_QUERIES,
		self::CHANNEL_ERRORS,
		self::CHANNEL_DEBUG,
		self::CHANNEL_QUEUE,
	];

	/**
	 * Private constructor - use channel() factory
	 *
	 * @param string $channel
	 */
	private function __construct( string $channel ) {
		$this->channel = $channel;
	}

	/**
	 * Get the filesystem instance
	 *
	 * @return Filesystem
	 */
	private static function filesystem(): Filesystem {
		if ( self::$filesystem === null ) {
			self::$filesystem = new Filesystem();
		}

		return self::$filesystem;
	}

	/**
	 * Get a logger instance for a specific channel
	 *
	 * @param string $channel One of: observers, queries, errors
	 *
	 * @return self
	 */
	public static function channel( string $channel ): self {
		if ( ! in_array( $channel, self::$valid_channels, true ) ) {
			$channel = self::CHANNEL_ERRORS;
		}

		return new self( $channel );
	}

	/**
	 * Check if logging is enabled
	 *
	 * Logging is enabled when WP_DEBUG is true or CELERSEARCH_DEBUG is true.
	 *
	 * @return bool
	 */
	public static function is_enabled(): bool {
		// Plugin-specific override
		if ( defined( 'CELERSEARCH_DEBUG' ) && CELERSEARCH_DEBUG === true ) {
			return true;
		}

		// Standard WP_DEBUG check
		return defined( 'WP_DEBUG' ) && WP_DEBUG === true;
	}

	/**
	 * Log a debug message
	 *
	 * @param string $message
	 * @param array  $context
	 *
	 * @return void
	 */
	public function debug( string $message, array $context = [] ): void {
		$this->log( self::LEVEL_DEBUG, $message, $context );
	}

	/**
	 * Log an info message
	 *
	 * @param string $message
	 * @param array  $context
	 *
	 * @return void
	 */
	public function info( string $message, array $context = [] ): void {
		$this->log( self::LEVEL_INFO, $message, $context );
	}

	/**
	 * Log a warning message
	 *
	 * @param string $message
	 * @param array  $context
	 *
	 * @return void
	 */
	public function warning( string $message, array $context = [] ): void {
		$this->log( self::LEVEL_WARNING, $message, $context );
	}

	/**
	 * Log an error message
	 *
	 * @param string $message
	 * @param array  $context
	 *
	 * @return void
	 */
	public function error( string $message, array $context = [] ): void {
		$this->log( self::LEVEL_ERROR, $message, $context );
	}

	/**
	 * Core logging method
	 *
	 * @param string $level
	 * @param string $message
	 * @param array  $context
	 *
	 * @return void
	 */
	private function log( string $level, string $message, array $context = [] ): void {
		if ( ! self::is_enabled() ) {
			return;
		}

		$log_dir = $this->get_log_directory();
		if ( ! $log_dir ) {
			return;
		}

		$file_path = $log_dir . '/' . $this->channel . '.log';

		// Check for rotation before writing
		$this->maybe_rotate( $file_path );

		$entry = $this->format_entry( $level, $message, $context );

		// Append entry to log file
		self::filesystem()->append( $file_path, $entry );
	}

	/**
	 * Format a log entry
	 *
	 * @param string $level
	 * @param string $message
	 * @param array  $context
	 *
	 * @return string
	 */
	private function format_entry( string $level, string $message, array $context ): string {
		$timestamp   = gmdate( 'Y-m-d H:i:s' );
		$context_str = ! empty( $context ) ? ' ' . wp_json_encode( $context, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) : '';

		return sprintf(
			"[%s] [%s] [%s] %s%s\n",
			$timestamp,
			$level,
			strtoupper( $this->channel ),
			$message,
			$context_str
		);
	}

	/**
	 * Get or create the log directory
	 *
	 * @return string|null Directory path or null on failure
	 */
	private function get_log_directory(): ?string {
		if ( self::$log_dir !== null ) {
			return self::$log_dir;
		}

		// Build path: wp-content/uploads/celersearch/logs/
		$upload_dir = wp_upload_dir();
		if ( ! empty( $upload_dir['error'] ) ) {
			return null;
		}
		$log_path   = $upload_dir['basedir'] . '/' . self::LOG_DIR;
		$filesystem = self::filesystem();

		// Create directory if it doesn't exist
		if ( ! $filesystem->exists( $log_path ) ) {
			if ( ! $filesystem->mkdir( $log_path ) ) {
				// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
				error_log( 'CelerSearch Logger: Could not create log directory: ' . $log_path );

				return null;
			}

			// Create .htaccess to prevent web access
			$htaccess = $log_path . '/.htaccess';
			if ( ! $filesystem->exists( $htaccess ) ) {
				$filesystem->put_contents( $htaccess, "Deny from all\n" );
			}

			// Create index.php for additional protection
			$index = $log_path . '/index.php';
			if ( ! $filesystem->exists( $index ) ) {
				$filesystem->put_contents( $index, "<?php // Silence is golden.\n" );
			}
		}

		// Verify directory is writable
		if ( ! $filesystem->is_writable( $log_path ) ) {
			// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
			error_log( 'CelerSearch Logger: Log directory not writable: ' . $log_path );

			return null;
		}

		self::$log_dir = $log_path;

		return self::$log_dir;
	}

	/**
	 * Rotate log file if it exceeds max size
	 *
	 * @param string $file_path
	 *
	 * @return void
	 */
	private function maybe_rotate( string $file_path ): void {
		$filesystem = self::filesystem();

		if ( ! $filesystem->exists( $file_path ) ) {
			return;
		}

		$size = $filesystem->size( $file_path );
		if ( $size === false || $size < self::MAX_FILE_SIZE ) {
			return;
		}

		// Rotate: file.log -> file.log.1, file.log.1 -> file.log.2, etc.
		for ( $i = self::MAX_ROTATED_FILES; $i >= 1; $i-- ) {
			$old_file = $file_path . '.' . $i;
			$new_file = $file_path . '.' . ( $i + 1 );

			if ( $i === self::MAX_ROTATED_FILES && $filesystem->exists( $old_file ) ) {
				$filesystem->delete( $old_file );
			} elseif ( $filesystem->exists( $old_file ) ) {
				$filesystem->move( $old_file, $new_file );
			}
		}

		// Rotate current file to .1
		$filesystem->move( $file_path, $file_path . '.1' );
	}

	/**
	 * Clear all log files
	 *
	 * @return bool
	 */
	public static function clear_all(): bool {
		$log_dir = ( new self( self::CHANNEL_ERRORS ) )->get_log_directory();
		if ( ! $log_dir ) {
			return false;
		}

		$filesystem = self::filesystem();

		foreach ( self::$valid_channels as $channel ) {
			$pattern = $log_dir . '/' . $channel . '.log*';
			foreach ( glob( $pattern ) as $file ) {
				$filesystem->delete( $file );
			}
		}

		return true;
	}

	/**
	 * Get the log directory path
	 *
	 * @return string|null
	 */
	public static function get_log_path(): ?string {
		return ( new self( self::CHANNEL_ERRORS ) )->get_log_directory();
	}
}
