<?php
/**
 * Logger Class
 *
 * Handles API error logging for debugging purposes.
 * Stores logs in daily files in wp-content/uploads/smartypress-ai-logs/
 * Automatically removes logs older than 7 days.
 *
 * @package SmartyPress_AI_Engine
 * @since 1.0.6
 */

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

/**
 * Logger class for API debugging
 */
class SmartyPress_AI_Logger {

	/**
	 * Option name for storing log file hash
	 */
	const HASH_OPTION = 'smartypress_ai_log_hash';

	/**
	 * Maximum days to keep logs
	 */
	const MAX_DAYS = 7;

	/**
	 * Get log directory path
	 *
	 * @return string Log directory path.
	 */
	private static function get_log_dir() {
		$upload_dir = wp_upload_dir();
		return $upload_dir['basedir'] . '/smartypress-ai-logs';
	}

	/**
	 * Get unique log file hash
	 *
	 * Generates a unique hash for the log file name based on site URL and auth keys.
	 * The hash is stored in options and reused for consistency.
	 *
	 * @return string Unique hash for log file name.
	 */
	private static function get_log_file_hash() {
		$hash = get_option( self::HASH_OPTION );

		if ( empty( $hash ) ) {
			// Generate a unique hash based on site-specific data.
			$unique_data = array(
				get_site_url(),
				defined( 'AUTH_KEY' ) ? AUTH_KEY : '',
				defined( 'SECURE_AUTH_KEY' ) ? SECURE_AUTH_KEY : '',
				wp_generate_password( 32, false ),
			);

			$hash = substr( hash( 'sha256', implode( '|', $unique_data ) ), 0, 16 );
			update_option( self::HASH_OPTION, $hash, false );
		}

		return $hash;
	}

	/**
	 * Get log file path for a specific date
	 *
	 * @param string $date Date in Y-m-d format. Defaults to today.
	 * @return string Log file path.
	 */
	private static function get_log_file( $date = '' ) {
		if ( empty( $date ) ) {
			$date = current_time( 'Y-m-d' );
		}

		$hash = self::get_log_file_hash();
		return self::get_log_dir() . '/' . $date . '-' . $hash . '.log';
	}

	/**
	 * Get all log files in the directory
	 *
	 * @return array Array of log file paths sorted by date (newest first).
	 */
	private static function get_all_log_files() {
		$log_dir = self::get_log_dir();

		if ( ! is_dir( $log_dir ) ) {
			return array();
		}

		$hash  = self::get_log_file_hash();
		$files = array();

		// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
		$dir_handle = @opendir( $log_dir );

		if ( ! $dir_handle ) {
			return array();
		}

		// phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
		while ( false !== ( $file = readdir( $dir_handle ) ) ) {
			// Match pattern: YYYY-MM-DD-{hash}.log
			if ( preg_match( '/^(\d{4}-\d{2}-\d{2})-' . preg_quote( $hash, '/' ) . '\.log$/', $file, $matches ) ) {
				$files[ $matches[1] ] = $log_dir . '/' . $file;
			}
		}

		closedir( $dir_handle );

		// Sort by date (newest first).
		krsort( $files );

		return $files;
	}

	/**
	 * Ensure log directory exists with proper protection
	 *
	 * @return bool True if directory exists or was created successfully.
	 */
	private static function ensure_log_dir() {
		$log_dir = self::get_log_dir();

		if ( ! file_exists( $log_dir ) ) {
			$created = wp_mkdir_p( $log_dir );

			if ( ! $created ) {
				return false;
			}

			// Create .htaccess for Apache protection.
			$htaccess_content = "# Deny access to all files\n";
			$htaccess_content .= "Order deny,allow\n";
			$htaccess_content .= "Deny from all\n";
			$htaccess_content .= "\n";
			$htaccess_content .= "# Also block via Apache 2.4+ syntax\n";
			$htaccess_content .= "<IfModule mod_authz_core.c>\n";
			$htaccess_content .= "    Require all denied\n";
			$htaccess_content .= "</IfModule>\n";

			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
			file_put_contents( $log_dir . '/.htaccess', $htaccess_content );

			// Create index.php to prevent directory listing.
			// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
			file_put_contents( $log_dir . '/index.php', '<?php // Silence is golden' );
		}

		return true;
	}

	/**
	 * Clean up old log files (older than MAX_DAYS)
	 */
	private static function cleanup_old_logs() {
		$files        = self::get_all_log_files();
		$cutoff_date  = gmdate( 'Y-m-d', strtotime( '-' . self::MAX_DAYS . ' days' ) );

		foreach ( $files as $date => $file_path ) {
			if ( $date < $cutoff_date ) {
				wp_delete_file( $file_path );
			}
		}
	}

	/**
	 * Log an API error
	 *
	 * @param string $provider Provider name (e.g., 'openai', 'deepseek').
	 * @param string $endpoint API endpoint URL.
	 * @param array  $request Request data (sanitized).
	 * @param array  $response Response data with 'code' and 'body'.
	 * @param string $error_message Error message.
	 * @param string $error_code Error code (optional).
	 */
	public static function log_api_error( $provider, $endpoint, $request, $response, $error_message, $error_code = '' ) {
		// Check if debug mode is enabled.
		$settings = get_option( 'smartypress_ai_settings', array() );
		if ( empty( $settings['enable_debug'] ) ) {
			return;
		}

		// Ensure log directory exists.
		if ( ! self::ensure_log_dir() ) {
			return;
		}

		// Clean up old logs periodically (1% chance per request to avoid overhead).
		if ( wp_rand( 1, 100 ) === 1 ) {
			self::cleanup_old_logs();
		}

		$log_file = self::get_log_file();

		// Sanitize request data - remove sensitive info.
		$sanitized_request = self::sanitize_request( $request );

		// Truncate response body if too long.
		$response_body = isset( $response['body'] ) ? $response['body'] : '';
		if ( strlen( $response_body ) > 2000 ) {
			$response_body = substr( $response_body, 0, 2000 ) . '... [truncated]';
		}

		// Create log entry.
		$log_entry = array(
			'id'             => uniqid( 'log_' ),
			'timestamp'      => current_time( 'mysql' ),
			'timestamp_unix' => time(),
			'provider'       => sanitize_text_field( $provider ),
			'endpoint'       => esc_url_raw( $endpoint ),
			'request'        => $sanitized_request,
			'response_code'  => isset( $response['code'] ) ? absint( $response['code'] ) : 0,
			'response_body'  => $response_body,
			'error_message'  => sanitize_text_field( $error_message ),
			'error_code'     => sanitize_text_field( $error_code ),
		);

		// Write to file as JSONL (one JSON object per line).
		$json_line = wp_json_encode( $log_entry, JSON_UNESCAPED_UNICODE ) . "\n";

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
		file_put_contents( $log_file, $json_line, FILE_APPEND | LOCK_EX );
	}

	/**
	 * Log a successful API request (for debugging)
	 *
	 * @param string $provider Provider name.
	 * @param string $endpoint API endpoint URL.
	 * @param array  $request Request data.
	 * @param array  $response Response data.
	 */
	public static function log_api_success( $provider, $endpoint, $request, $response ) {
		// Check if debug mode is enabled.
		$settings = get_option( 'smartypress_ai_settings', array() );
		if ( empty( $settings['enable_debug'] ) || empty( $settings['log_successful_requests'] ) ) {
			return;
		}

		// Ensure log directory exists.
		if ( ! self::ensure_log_dir() ) {
			return;
		}

		$log_file = self::get_log_file();

		// Sanitize request data.
		$sanitized_request = self::sanitize_request( $request );

		// Truncate response content if too long.
		$response_content = isset( $response['content'] ) ? $response['content'] : '';
		if ( strlen( $response_content ) > 2000 ) {
			$response_content = substr( $response_content, 0, 2000 ) . '... [truncated]';
		}

		// Create log entry.
		$log_entry = array(
			'id'             => uniqid( 'log_' ),
			'timestamp'      => current_time( 'mysql' ),
			'timestamp_unix' => time(),
			'provider'       => sanitize_text_field( $provider ),
			'endpoint'       => esc_url_raw( $endpoint ),
			'request'        => $sanitized_request,
			'response_code'  => isset( $response['code'] ) ? absint( $response['code'] ) : 200,
			'response_body'  => $response_content,
			'usage'          => $response['usage'] ?? array(),
			'error_message'  => '',
			'error_code'     => '',
			'success'        => true,
		);

		// Write to file as JSONL.
		$json_line = wp_json_encode( $log_entry, JSON_UNESCAPED_UNICODE ) . "\n";

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
		file_put_contents( $log_file, $json_line, FILE_APPEND | LOCK_EX );
	}

	/**
	 * Log a module event (e.g. image generation stages)
	 *
	 * @param string $context Module context (e.g. 'image_generator').
	 * @param string $stage   Event stage (e.g. 'generation_started', 'metadata_generated').
	 * @param array  $data    Additional context data (attachment_id, model, etc.). No sensitive data.
	 * @param bool   $success Whether the stage completed successfully.
	 */
	public static function log_module_event( $context, $stage, $data = array(), $success = true ) {
		$settings = get_option( 'smartypress_ai_settings', array() );
		if ( empty( $settings['enable_debug'] ) ) {
			return;
		}

		if ( ! self::ensure_log_dir() ) {
			return;
		}

		if ( wp_rand( 1, 100 ) === 1 ) {
			self::cleanup_old_logs();
		}

		$log_file = self::get_log_file();

		$log_entry = array(
			'id'             => uniqid( 'log_' ),
			'type'           => 'module_event',
			'timestamp'      => current_time( 'mysql' ),
			'timestamp_unix' => time(),
			'context'        => sanitize_text_field( $context ),
			'stage'          => sanitize_text_field( $stage ),
			'success'        => (bool) $success,
			'data'           => is_array( $data ) ? $data : array(),
		);

		$json_line = wp_json_encode( $log_entry, JSON_UNESCAPED_UNICODE ) . "\n";

		// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
		file_put_contents( $log_file, $json_line, FILE_APPEND | LOCK_EX );
	}

	/**
	 * Read logs from a single file
	 *
	 * @param string $file_path Path to log file.
	 * @return array Array of log entries.
	 */
	private static function read_log_file( $file_path ) {
		if ( ! file_exists( $file_path ) ) {
			return array();
		}

		$logs = array();

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

		if ( empty( $content ) ) {
			return array();
		}

		$lines = explode( "\n", trim( $content ) );

		foreach ( $lines as $line ) {
			$line = trim( $line );
			if ( empty( $line ) ) {
				continue;
			}

			$entry = json_decode( $line, true );
			if ( is_array( $entry ) ) {
				$logs[] = $entry;
			}
		}

		return $logs;
	}

	/**
	 * Get all logs from all daily files
	 *
	 * @param int $limit Maximum number of logs to return (0 = all).
	 * @return array Array of log entries.
	 */
	public static function get_logs( $limit = 0 ) {
		$files    = self::get_all_log_files();
		$all_logs = array();

		foreach ( $files as $date => $file_path ) {
			$file_logs = self::read_log_file( $file_path );
			$all_logs  = array_merge( $all_logs, $file_logs );
		}

		// Sort by timestamp (newest first).
		usort(
			$all_logs,
			function ( $a, $b ) {
				$time_a = isset( $a['timestamp_unix'] ) ? $a['timestamp_unix'] : 0;
				$time_b = isset( $b['timestamp_unix'] ) ? $b['timestamp_unix'] : 0;
				return $time_b - $time_a;
			}
		);

		if ( $limit > 0 ) {
			$all_logs = array_slice( $all_logs, 0, $limit );
		}

		return $all_logs;
	}

	/**
	 * Clear all logs
	 *
	 * @return bool True on success.
	 */
	public static function clear_logs() {
		$files   = self::get_all_log_files();
		$success = true;

		foreach ( $files as $file_path ) {
			if ( ! wp_delete_file( $file_path ) ) {
				$success = false;
			}
		}

		return $success;
	}

	/**
	 * Get log count from all files
	 *
	 * @return int Number of logs.
	 */
	public static function get_log_count() {
		$files = self::get_all_log_files();
		$count = 0;

		foreach ( $files as $file_path ) {
			if ( ! file_exists( $file_path ) ) {
				continue;
			}

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

			if ( ! empty( $content ) ) {
				$lines = explode( "\n", trim( $content ) );
				$count += count( array_filter( $lines ) );
			}
		}

		return $count;
	}

	/**
	 * Sanitize request data to remove sensitive information
	 *
	 * @param array $request Raw request data.
	 * @return array Sanitized request data.
	 */
	private static function sanitize_request( $request ) {
		if ( ! is_array( $request ) ) {
			return array();
		}

		$sanitized = array();

		// Safe fields to log (no API keys, tokens, or other secrets).
		$safe_fields = array( 'model', 'temperature', 'max_tokens', 'max_completion_tokens', 'prompt' );

		foreach ( $safe_fields as $field ) {
			if ( isset( $request[ $field ] ) ) {
				$sanitized[ $field ] = $request[ $field ];
			}
		}

		// Log messages content (full content for debugging).
		// Support both OpenAI format (content) and Yandex format (text).
		if ( isset( $request['messages'] ) && is_array( $request['messages'] ) ) {
			$sanitized['messages_count'] = count( $request['messages'] );
			$sanitized['messages']       = array();
			foreach ( $request['messages'] as $msg ) {
				$content = $msg['content'] ?? $msg['text'] ?? '';
				$sanitized['messages'][] = array(
					'role'    => $msg['role'] ?? '',
					'content' => $content,
				);
			}
		}

		return $sanitized;
	}

	/**
	 * Check if debug mode is enabled
	 *
	 * @return bool True if debug mode is enabled.
	 */
	public static function is_debug_enabled() {
		$settings = get_option( 'smartypress_ai_settings', array() );
		return ! empty( $settings['enable_debug'] );
	}

	/**
	 * Get log files info for admin display
	 *
	 * @return array Info about log files.
	 */
	public static function get_log_file_info() {
		$files      = self::get_all_log_files();
		$total_size = 0;
		$file_count = count( $files );

		foreach ( $files as $file_path ) {
			if ( file_exists( $file_path ) ) {
				$total_size += filesize( $file_path );
			}
		}

		return array(
			'directory'  => self::get_log_dir(),
			'file_count' => $file_count,
			'total_size' => $total_size,
			'log_count'  => self::get_log_count(),
			'max_days'   => self::MAX_DAYS,
		);
	}
}
