<?php
/**
 * Logger utility for Analytics.
 *
 * @package PrivexPasswordProtect
 */

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

/**
 * Class PPMSTR_Logger
 * Handles tracking and storing unlock attempts.
 */
class PPMSTR_Logger {

	/**
	 * Log an access attempt.
	 *
	 * @param string $type      Type of protection (e.g., 'site', 'post', 'category').
	 * @param int    $target_id The ID of the protected item (e.g., post ID or term ID).
	 * @param string $status    'success' or 'failed'.
	 */
	public static function log_attempt( $type, $target_id, $status ) {
		// Ensure settings allow logging (optional future toggle).
		$settings = get_option( 'ppmstr_settings', array() );

		global $wpdb;
		$table_name = $wpdb->prefix . 'ppmstr_access_logs';

		// Get IP and User Agent, ensuring GDPR compliance via hashing if requested (default to masked to be safe).
		$raw_ip = self::get_client_ip();
		$ip     = self::mask_ip( $raw_ip );
		$ua     = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';

		// Temporarily suppress errors so that missing table doesn't print HTML to the AJAX JSON response
		$suppress = $wpdb->suppress_errors( true );

		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
		$wpdb->insert(
			$table_name,
			array(
				'type'       => sanitize_key( $type ),
				'target_id'  => absint( $target_id ),
				'status'     => sanitize_key( $status ),
				'ip_address' => $ip,
				'user_agent' => $ua,
				'created_at' => current_time( 'mysql' ),
			),
			array(
				'%s',
				'%d',
				'%s',
				'%s',
				'%s',
				'%s',
			)
		);

		$wpdb->suppress_errors( $suppress );
	}

	/**
	 * Get the client's IP address safely.
	 *
	 * @return string
	 */
	private static function get_client_ip() {
		$ip = '127.0.0.1';
		if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
			$ip = sanitize_text_field( wp_unslash( $_SERVER['HTTP_CLIENT_IP'] ) );
		} elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
			$ips = explode( ',', sanitize_text_field( wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) );
			$ip  = trim( $ips[0] );
		} elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
			$ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) );
		}
		return filter_var( $ip, FILTER_VALIDATE_IP ) ? $ip : '127.0.0.1';
	}

	/**
	 * Mask IP for GDPR compliance (e.g., 192.168.1.xxx or 2001:0db8::).
	 *
	 * @param string $ip Raw IP.
	 * @return string Masked IP.
	 */
	private static function mask_ip( $ip ) {
		if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
			return preg_replace( '/\.\d+$/', '.xxx', $ip );
		}
		if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
			return preg_replace( '/(:[0-9a-f]{1,4}){2}$/i', ':xxxx:xxxx', $ip );
		}
		return 'unknown';
	}

	/**
	 * Get analytics stats.
	 *
	 * @param int $days Number of days to look back.
	 * @return array
	 */
	public static function get_stats( $days = 7 ) {
		global $wpdb;
		$table_name = $wpdb->prefix . 'ppmstr_access_logs';

		// Check if table exists (in case plugin didn't run activator).
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
		if ( $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table_name ) ) !== $table_name ) {
			if ( class_exists( 'PPMSTR_Activator' ) ) {
				PPMSTR_Activator::activate();
			}
			return array(
				'total_success' => 0,
				'total_failed'  => 0,
				'daily_data'    => array(),
				'recent_logs'   => array(),
			);
		}

		$date_limit = gmdate( 'Y-m-d H:i:s', strtotime( "-{$days} days" ) );

		// 1. Overall counts in period.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$success = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table_name} WHERE status = 'success' AND created_at >= %s", $date_limit ) );
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$failed  = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$table_name} WHERE status = 'failed' AND created_at >= %s", $date_limit ) );

		// 2. Daily data for chart.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
		$daily = $wpdb->get_results( $wpdb->prepare( "SELECT DATE(created_at) as log_date, SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as success_count, SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed_count FROM {$table_name} WHERE created_at >= %s GROUP BY DATE(created_at) ORDER BY log_date ASC", $date_limit ), ARRAY_A );

		// Fill in missing days so chart looks consistent.
		$daily_filled = array();
		for ( $i = $days - 1; $i >= 0; $i-- ) {
			$d = gmdate( 'Y-m-d', strtotime( "-{$i} days" ) );
			$daily_filled[ $d ] = array( 'date' => $d, 'success' => 0, 'failed' => 0 );
		}
		foreach ( $daily as $row ) {
			$d = $row['log_date'];
			if ( isset( $daily_filled[ $d ] ) ) {
				$daily_filled[ $d ]['success'] = (int) $row['success_count'];
				$daily_filled[ $d ]['failed']  = (int) $row['failed_count'];
			}
		}

		// 3. Recent logs.
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.NotPrepared
		$recent = $wpdb->get_results( "SELECT * FROM {$table_name} ORDER BY created_at DESC LIMIT 15", ARRAY_A );

		return array(
			'total_success' => $success,
			'total_failed'  => $failed,
			'daily_data'    => array_values( $daily_filled ),
			'recent_logs'   => (array) $recent,
		);
	}
}
