<?php
/**
 * This file is part of the Wisewand WordPress plugin.
 *
 * @package Wisewand
 * @since 1.0.0
 * @license GPL-2.0-or-later
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */
namespace Wisewand\Logging;

// Exit if accessed directly
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

class Logger {

	private $request_id;
	private $table_name;

	public function __construct() {
		global $wpdb;
		$this->table_name = $wpdb->prefix . 'wisewand_logs';
		$this->request_id = $this->generate_request_id();
	}

	private function generate_request_id() {
		return 'ww_' . bin2hex( random_bytes( 16 ) );
	}

	public function get_request_id() {
		return $this->request_id;
	}

	public function emergency( $message, $context = array() ) {
		$this->log( 'emergency', $message, $context );
	}

	public function alert( $message, $context = array() ) {
		$this->log( 'alert', $message, $context );
	}

	public function critical( $message, $context = array() ) {
		$this->log( 'critical', $message, $context );
	}

	public function error( $message, $context = array() ) {
		$this->log( 'error', $message, $context );
	}

	public function warning( $message, $context = array() ) {
		$this->log( 'warning', $message, $context );
	}

	public function notice( $message, $context = array() ) {
		$this->log( 'notice', $message, $context );
	}

	public function info( $message, $context = array() ) {
		$this->log( 'info', $message, $context );
	}

	public function debug( $message, $context = array() ) {
		$debug_mode = get_option( 'wisewand_debug_mode', false );
		if ( $debug_mode ) {
			$this->log( 'debug', $message, $context );
		}
	}

	private function log( $level, $message, $context = array() ) {
		// Check if logging is enabled
		$logging_enabled = get_option( 'wisewand_logging_enabled', true );
		if ( ! $logging_enabled ) {
			return;
		}

		global $wpdb;

		// Sanitize context for storage
		$context = $this->sanitize_context( $context );

		// Get request info
		$ip_address = $this->get_client_ip();
		$user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
		$endpoint   = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
		$method     = isset( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';

		// Truncate long values
		$user_agent = substr( $user_agent, 0, 500 );
		$endpoint   = substr( $endpoint, 0, 100 );

		$data = array(
			'request_id'  => $this->request_id,
			'level'       => $level,
			'message'     => $message,
			'context'     => wp_json_encode( $context ),
			'ip_address'  => $ip_address,
			'user_agent'  => $user_agent,
			'endpoint'    => $endpoint,
			'method'      => $method,
			'status_code' => http_response_code() ?: null,
		);

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery -- Custom plugin logging table
		$result = $wpdb->insert( $this->table_name, $data );

		// Self-healing: If insert failed because table doesn't exist, try to create it
		if ( false === $result && $wpdb->last_error && strpos( $wpdb->last_error, "doesn't exist" ) !== false ) {
			// Prevent infinite recursion
			static $retrying = false;
			if ( ! $retrying ) {
				$retrying = true;
				if ( class_exists( 'Wisewand\\Database\\Database_Manager' ) ) {
					\Wisewand\Database\Database_Manager::initialize();
					// Retry insert once
					$wpdb->insert( $this->table_name, $data );
				}
				$retrying = false;
			}
		}

		// Clean up old logs if we exceed max entries
		$this->cleanup_old_logs();
	}

	private function sanitize_context( $context ) {
		if ( ! is_array( $context ) ) {
			return $context;
		}

		$debug_mode = get_option( 'wisewand_debug_mode', false );

		foreach ( $context as $key => $value ) {
			// Remove sensitive data unless debug mode is on with explicit consent
			if ( ! $debug_mode && $this->is_sensitive_key( $key ) ) {
				$context[ $key ] = '[REDACTED]';
				continue;
			}

			// Recursively sanitize arrays
			if ( is_array( $value ) ) {
				$context[ $key ] = $this->sanitize_context( $value );
			}

			// Truncate long strings
			if ( is_string( $value ) && strlen( $value ) > 1000 ) {
				$context[ $key ] = substr( $value, 0, 1000 ) . '... [TRUNCATED]';
			}
		}

		return $context;
	}

	private function is_sensitive_key( $key ) {
		$sensitive_keys = array(
			'password',
			'passwd',
			'secret',
			'key',
			'token',
			'auth',
			'authorization',
			'cookie',
			'session',
			'private',
			'hmac_secret',
			'api_key',
			'email',
			'phone',
			'address',
		);

		$key_lower = strtolower( $key );
		foreach ( $sensitive_keys as $sensitive ) {
			if ( strpos( $key_lower, $sensitive ) !== false ) {
				return true;
			}
		}

		return false;
	}

	private function get_client_ip() {
		$ip_keys = array( 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR' );

		foreach ( $ip_keys as $key ) {
			if ( array_key_exists( $key, $_SERVER ) === true ) {
				$ip_list = explode( ',', sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) ) );
				foreach ( $ip_list as $ip ) {
					$ip = trim( $ip );
					if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
						return $ip;
					}
				}
			}
		}

		return isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
	}

	private function cleanup_old_logs() {
		global $wpdb;

		$max_entries    = get_option( 'wisewand_log_max_entries', 200 );
		$retention_days = get_option( 'wisewand_log_retention_days', 30 );

        // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin logging table cleanup operations
		// Count current entries
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is class property set internally
		$count = $wpdb->get_var( "SELECT COUNT(*) FROM {$this->table_name}" );

		if ( $count > $max_entries ) {
			// Delete oldest entries beyond max
			$delete_count = $count - $max_entries;
			$wpdb->query(
				$wpdb->prepare(
                // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Table name derived from $wpdb->prefix is safe to interpolate, custom plugin table cleanup
					"DELETE FROM {$this->table_name} ORDER BY timestamp ASC LIMIT %d",
					$delete_count
				)
			);
		}

		// Delete entries older than retention period
		$wpdb->query(
			$wpdb->prepare(
            // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Table name derived from $wpdb->prefix is safe to interpolate, custom plugin table cleanup
				"DELETE FROM {$this->table_name} WHERE timestamp < DATE_SUB(NOW(), INTERVAL %d DAY)",
				$retention_days
			)
		);
        // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
	}

	public function get_logs( $limit = 50, $offset = 0, $level = null, $search = null ) {
		global $wpdb;

		$where_conditions = array();
		$where_values     = array();

		if ( $level ) {
			$where_conditions[] = 'level = %s';
			$where_values[]     = $level;
		}

		if ( $search ) {
			$where_conditions[] = '(message LIKE %s OR context LIKE %s)';
			$where_values[]     = '%' . $wpdb->esc_like( $search ) . '%';
			$where_values[]     = '%' . $wpdb->esc_like( $search ) . '%';
		}

		// Build WHERE clause with placeholders already included
		$where_clause = ! empty( $where_conditions ) ? 'WHERE ' . implode( ' AND ', $where_conditions ) : '';

		// Build complete query with WHERE clause before prepare() - use %i for table name
		$query = "SELECT * FROM %i {$where_clause} ORDER BY timestamp DESC LIMIT %d OFFSET %d";

		// Merge table name with WHERE values, then add LIMIT and OFFSET
		$prepare_values = array_merge( array( $this->table_name ), $where_values, array( $limit, $offset ) );

		// Prepare query with all values at once - WHERE conditions already contain placeholders
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin logging table query with pagination, WHERE clause built from prepared placeholders
		return $wpdb->get_results( $wpdb->prepare( $query, $prepare_values ) );
	}

	public function get_logs_count( $level = null, $search = null ) {
		global $wpdb;

		$where_conditions = array();
		$where_values     = array();

		if ( $level ) {
			$where_conditions[] = 'level = %s';
			$where_values[]     = $level;
		}

		if ( $search ) {
			$where_conditions[] = '(message LIKE %s OR context LIKE %s)';
			$where_values[]     = '%' . $wpdb->esc_like( $search ) . '%';
			$where_values[]     = '%' . $wpdb->esc_like( $search ) . '%';
		}

		// Build WHERE clause with placeholders already included
		$where_clause = ! empty( $where_conditions ) ? 'WHERE ' . implode( ' AND ', $where_conditions ) : '';

		// Build complete query with WHERE clause before prepare() - use %i for table name
		$query = "SELECT COUNT(*) FROM %i {$where_clause}";

		// Merge table name with WHERE values
		$prepare_values = array_merge( array( $this->table_name ), $where_values );

		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin logging table count query, WHERE clause built from prepared placeholders
		return $wpdb->get_var( $wpdb->prepare( $query, $prepare_values ) );
	}

	public function purge_logs() {
		global $wpdb;
        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is class property, TRUNCATE has no user input
		return $wpdb->query( "TRUNCATE TABLE {$this->table_name}" );
	}

	public function export_logs( $format = 'json', $limit = null ) {
		global $wpdb;

		$query = "SELECT * FROM {$this->table_name} ORDER BY timestamp DESC";
		if ( $limit ) {
			$query .= $wpdb->prepare( ' LIMIT %d', $limit );
		}

        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Custom plugin logging table export query
		$logs = $wpdb->get_results( $query, ARRAY_A );

		switch ( $format ) {
			case 'csv':
				return $this->export_logs_csv( $logs );
			case 'json':
			default:
				return wp_json_encode( $logs, JSON_PRETTY_PRINT );
		}
	}

	private function export_logs_csv( $logs ) {
		if ( empty( $logs ) ) {
			return '';
		}

        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen -- Using php://temp memory stream for CSV generation
		$output = fopen( 'php://temp', 'r+' );

		// Write headers
		fputcsv( $output, array_keys( $logs[0] ) );

		// Write data
		foreach ( $logs as $log ) {
			fputcsv( $output, $log );
		}

		rewind( $output );
		$csv = stream_get_contents( $output );
        // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- Closing php://temp memory stream
		fclose( $output );

		return $csv;
	}
}
