<?php
namespace R2_Offload;

defined( 'ABSPATH' ) || exit;

final class Logger {

	public const FILE = 'logs/offload.log';
	public const ROTATE_BYTES = 5242880; // 5MB

	public function info( string $message, string $context = 'core', array $meta = [] ): void {
		$this->write( 'info', $context, $message, $meta );
	}

	public function warn( string $message, string $context = 'core', array $meta = [] ): void {
		$this->write( 'warn', $context, $message, $meta );
	}

	public function error( string $message, string $context = 'core', array $meta = [] ): void {
		$this->write( 'error', $context, $message, $meta );
	}

	private function write( string $level, string $context, string $message, array $meta ): void {
		global $wpdb;
		$wpdb->insert(
			DB::logs_table(),
			[
				'level'      => $level,
				'context'    => substr( $context, 0, 64 ),
				'message'    => $message,
				'meta'       => $meta ? wp_json_encode( $meta ) : null,
				'created_at' => current_time( 'mysql' ),
			]
		);
		$this->maybe_rotate_db();

		$this->write_file( $level, $context, $message, $meta );

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
			error_log( '[r2-offload][' . $level . '][' . $context . '] ' . $message );
		}
	}

	private function maybe_rotate_db(): void {
		global $wpdb;
		if ( wp_rand( 1, 100 ) !== 1 ) {
			return; // Only sample 1% of writes to avoid overhead.
		}
		$opts = (array) get_option( 'r2_offload_settings', [] );
		$max  = (int) ( $opts['log_db_max_rows'] ?? 10000 );
		if ( $max <= 0 ) {
			return;
		}
		$table = DB::logs_table();
		$count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$table}" );
		if ( $count <= $max ) {
			return;
		}
		$delete = $count - $max;
		$wpdb->query( $wpdb->prepare( "DELETE FROM {$table} ORDER BY id ASC LIMIT %d", $delete ) );
	}

	private function write_file( string $level, string $context, string $message, array $meta ): void {
		self::harden_log_dir();
		$file = R2_OFFLOAD_DIR . self::FILE;
		if ( file_exists( $file ) && filesize( $file ) > self::ROTATE_BYTES ) {
			@rename( $file, $file . '.1' );
		}
		$line = sprintf(
			"[%s] %-5s %s: %s%s\n",
			current_time( 'mysql' ),
			strtoupper( $level ),
			$context,
			$message,
			$meta ? ' ' . wp_json_encode( $meta ) : ''
		);
		@file_put_contents( $file, $line, FILE_APPEND | LOCK_EX );
	}

	public function recent( int $limit = 100 ): array {
		global $wpdb;
		$table = DB::logs_table();
		return (array) $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$table} ORDER BY id DESC LIMIT %d", $limit ) );
	}

	public function log_file_path(): string {
		return R2_OFFLOAD_DIR . self::FILE;
	}

	/**
	 * Create the log directory and block direct web access to it.
	 *
	 * The log lives under the plugin folder (a web-predictable path). Drop an
	 * .htaccess deny rule + an index.php so the offload.log (which contains
	 * server paths, object keys and error detail — never credentials) cannot be
	 * fetched directly on hosts that don't already deny wp-content/plugins.
	 */
	public static function harden_log_dir(): void {
		$dir = R2_OFFLOAD_DIR . 'logs';
		if ( ! is_dir( $dir ) ) {
			wp_mkdir_p( $dir );
		}
		if ( ! is_dir( $dir ) ) {
			return;
		}
		if ( ! file_exists( $dir . '/.htaccess' ) ) {
			@file_put_contents( $dir . '/.htaccess', "# Generated by R2 Media Offload — block direct access to logs.\nRequire all denied\n<IfModule !mod_authz_core.c>\nOrder allow,deny\nDeny from all\n</IfModule>\n" ); // phpcs:ignore WordPress.WP.AlternativeFunctions
		}
		if ( ! file_exists( $dir . '/index.php' ) ) {
			@file_put_contents( $dir . '/index.php', "<?php\n// Silence is golden.\n" ); // phpcs:ignore WordPress.WP.AlternativeFunctions
		}
	}
}
