<?php
/**
 * Lightweight debug logger for local and staging environments.
 *
 * Only writes to error_log() when WP_DEBUG is true.
 * In production (WP_DEBUG false) every method is a no-op.
 *
 * @package SEOvaultSEO\Utils
 */

namespace SEOvaultSEO\Utils;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Logger {

	const PREFIX = '[SEOvault AI]';

	/**
	 * Keys whose values should never appear in logs.
	 */
	const REDACTED_KEYS = array(
		'site_key',
		'x-site-key',
		'authorization',
		'auth',
		'token',
		'password',
		'secret',
		'api_key',
		'apikey',
		'indexnow_key',
	);

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

	/**
	 * Log a general message with optional context.
	 *
	 * @param string $message Log message.
	 * @param array  $context Optional key/value context.
	 * @return void
	 */
	public static function log( $message, array $context = array() ) {
		if ( ! self::enabled() ) {
			return;
		}
		self::write( 'LOG', $message, $context );
	}

	/**
	 * Log an error message with optional context.
	 *
	 * @param string $message Error message.
	 * @param array  $context Optional key/value context.
	 * @return void
	 */
	public static function error( $message, array $context = array() ) {
		if ( ! self::enabled() ) {
			return;
		}
		self::write( 'ERROR', $message, $context );
	}

	/**
	 * Log timing information for a labelled operation.
	 *
	 * @param string $label     Timing label.
	 * @param float  $startTime Start microtime (from microtime(true)).
	 * @param array  $context   Optional key/value context.
	 * @return void
	 */
	public static function timing( $label, $startTime, array $context = array() ) {
		if ( ! self::enabled() ) {
			return;
		}
		$elapsed               = round( microtime( true ) - (float) $startTime, 4 );
		$context['elapsed_ms'] = $elapsed * 1000;
		self::write( 'TIMING', $label, $context );
	}

	/**
	 * Build and write a single log line to error_log().
	 *
	 * @param string $level   Log level tag.
	 * @param string $message Log message.
	 * @param array  $context Key/value context.
	 * @return void
	 */
	private static function write( $level, $message, array $context ) {
		$ts     = microtime( true );
		$ts_str = gmdate( 'Y-m-d H:i:s', (int) $ts ) . sprintf( '.%03d', (int) ( ( $ts - (int) $ts ) * 1000 ) );

		$meta = self::collect_meta();
		$line = self::PREFIX . ' [' . $level . '] [' . $ts_str . ']';

		if ( ! empty( $meta['method'] ) ) {
			$line .= ' [method=' . $meta['method'] . ']';
		}
		if ( isset( $meta['user_id'] ) && $meta['user_id'] > 0 ) {
			$line .= ' [user=' . $meta['user_id'] . ']';
		}
		if ( ! empty( $meta['action'] ) ) {
			$line .= ' [action=' . $meta['action'] . ']';
		}
		if ( ! empty( $meta['page'] ) ) {
			$line .= ' [page=' . $meta['page'] . ']';
		}

		$line .= ' ' . $message;

		if ( ! empty( $context ) ) {
			$encoded = self::encode_context( $context );
			if ( '' !== $encoded ) {
				$line .= ' ' . $encoded;
			}
		}

		// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Intentional plugin debug log when WP_DEBUG_LOG is enabled.
		error_log( $line );
	}

	/**
	 * Collect request metadata.
	 *
	 * @return array
	 */
	private static function collect_meta() {
		$meta = array(
			'method'  => '',
			'user_id' => 0,
			'action'  => '',
			'page'    => '',
		);

		if ( isset( $_SERVER['REQUEST_METHOD'] ) ) {
			$meta['method'] = sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) );
		}

		if ( function_exists( 'get_current_user_id' ) ) {
			$meta['user_id'] = get_current_user_id();
		}

		// phpcs:disable WordPress.Security.NonceVerification.Recommended -- Read-only request metadata for log context; values sanitized.
		if ( isset( $_REQUEST['action'] ) ) {
			$meta['action'] = sanitize_text_field( wp_unslash( $_REQUEST['action'] ) );
		}

		if ( isset( $_REQUEST['page'] ) ) {
			$meta['page'] = sanitize_text_field( wp_unslash( $_REQUEST['page'] ) );
		}
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		return $meta;
	}

	/**
	 * Safely JSON-encode context, redacting secret keys.
	 *
	 * @param array $context Key/value context.
	 * @return string
	 */
	private static function encode_context( array $context ) {
		$safe = self::redact( $context );

		$json = wp_json_encode( $safe );
		if ( false === $json ) {
			// Fallback: serialize individual values as strings.
			$parts = array();
			foreach ( $safe as $key => $value ) {
				if ( is_array( $value ) || is_object( $value ) ) {
					$inner = wp_json_encode( $value );
					if ( false === $inner ) {
						$inner = serialize( $value );
					}
					$parts[] = $key . '=' . $inner;
				} else {
					$parts[] = $key . '=' . (string) $value;
				}
			}
			return '{' . implode( ', ', $parts ) . '}';
		}

		return $json;
	}

	/**
	 * Recursively redact secret keys from a context array.
	 *
	 * @param array $data Context data.
	 * @return array
	 */
	private static function redact( array $data ) {
		$out = array();
		foreach ( $data as $key => $value ) {
			$lower_key = strtolower( (string) $key );
			if ( in_array( $lower_key, self::REDACTED_KEYS, true ) ) {
				$out[ $key ] = '[REDACTED]';
				continue;
			}
			if ( is_array( $value ) ) {
				$out[ $key ] = self::redact( $value );
			} elseif ( is_object( $value ) ) {
				$out[ $key ] = self::redact( (array) $value );
			} elseif ( is_resource( $value ) ) {
				$out[ $key ] = '[resource]';
			} else {
				$out[ $key ] = $value;
			}
		}
		return $out;
	}
}
