<?php
/**
 * Privacy engine: cookieless identification, daily salt rotation, IP anonymisation.
 *
 * The visitor "identity" is a one-way HMAC of (ip + user-agent + site host)
 * keyed by a salt that is regenerated every day. Because yesterday's salt is
 * destroyed, hashes cannot be correlated across days, no cookie is set, and no
 * IP address is ever written to the database — this is what makes Defog
 * GDPR-friendly with no consent banner.
 *
 * @package Defog
 */

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

class Defog_Privacy {

	const SALT_OPTION = 'defog_daily_salt';

	/**
	 * Get today's salt, rotating (and discarding the old one) when the day changes.
	 *
	 * @return string
	 */
	public static function salt() {
		$today = defog_today();
		$store = get_option( self::SALT_OPTION );

		if ( is_array( $store ) && isset( $store['date'], $store['salt'] ) && $store['date'] === $today ) {
			return $store['salt'];
		}

		// New day (or first run): generate a fresh salt, overwrite the old one.
		try {
			$salt = bin2hex( random_bytes( 32 ) );
		} catch ( \Exception $e ) {
			$salt = wp_generate_password( 64, false, false );
		}

		update_option(
			self::SALT_OPTION,
			array(
				'date' => $today,
				'salt' => $salt,
			),
			false
		);

		return $salt;
	}

	/**
	 * Compute the cookieless visitor hash. The raw IP is used transiently here
	 * and never returned or stored.
	 *
	 * @param string $ip         Visitor IP (used only to derive the hash).
	 * @param string $user_agent Raw user-agent string.
	 * @return string 64-char hex hash.
	 */
	public static function visitor_hash( $ip, $user_agent ) {
		$host    = wp_parse_url( home_url(), PHP_URL_HOST );
		$payload = $ip . '|' . $user_agent . '|' . (string) $host;
		return hash_hmac( 'sha256', $payload, self::salt() );
	}

	/**
	 * Resolve the client IP from the request. Returns the raw IP for hashing
	 * only — callers must not persist it.
	 *
	 * @return string
	 */
	public static function client_ip() {
		// Trust common proxy headers conservatively; fall back to REMOTE_ADDR.
		$candidates = array( 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR' );
		foreach ( $candidates as $key ) {
			if ( empty( $_SERVER[ $key ] ) ) {
				continue;
			}
			$value = sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) );
			// X-Forwarded-For may contain a list; take the first.
			$value = trim( explode( ',', $value )[0] );
			if ( filter_var( $value, FILTER_VALIDATE_IP ) ) {
				return $value;
			}
		}
		return '0.0.0.0';
	}

	/**
	 * Best-effort country detection from edge/CDN headers. No GeoIP database is
	 * bundled in this dev build; production can ship a MaxMind lookup. Privacy
	 * note: only a 2-letter country code is derived, never stored alongside IP.
	 *
	 * @return string ISO 3166-1 alpha-2 or 'XX'.
	 */
	public static function country() {
		$headers = array(
			'HTTP_CF_IPCOUNTRY',                // Cloudflare.
			'HTTP_CLOUDFRONT_VIEWER_COUNTRY',   // AWS CloudFront.
			'HTTP_X_GEO_COUNTRY',               // Generic proxies / some hosts.
			'HTTP_X_COUNTRY_CODE',              // Generic proxies / some hosts.
			'GEOIP_COUNTRY_CODE',               // Apache mod_geoip.
			'MM_COUNTRY_CODE',                  // Apache mod_maxminddb.
		);
		foreach ( $headers as $h ) {
			if ( ! empty( $_SERVER[ $h ] ) ) {
				$code = strtoupper( sanitize_text_field( wp_unslash( $_SERVER[ $h ] ) ) );
				if ( preg_match( '/^[A-Z]{2}$/', $code ) && 'XX' !== $code ) {
					return $code;
				}
			}
		}
		return 'XX';
	}
}
