<?php
/**
 * SimpleStats shared helpers.
 *
 * @package SimpleStats
 */

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

/**
 * Get app URL
 */
function simplestats_get_app_url() {
	return apply_filters( 'simplestats_app_url', 'https://app.simplestats.com' );
}

/**
 * Whitelist for the tracking-type field collected from the auth flow.
 * The SaaS independently validates with the same rule, so this is the
 * client-side first line of defence — nothing else.
 *
 * @param mixed $raw Untrusted input from $_POST.
 * @return string One of 'lightweight' | 'normal'. Defaults to 'normal'.
 */
function simplestats_sanitize_tracking_type( $raw ) {
	$value = is_scalar( $raw ) ? strtolower( trim( (string) wp_unslash( $raw ) ) ) : '';
	return in_array( $value, array( 'lightweight', 'normal' ), true ) ? $value : 'normal';
}

/**
 * Get website connection data
 */
function simplestats_get_connection() {
	$connection = get_option( 'simplestats_connection', false );

	if ( empty( $connection ) ) {
		return false;
	}

	return $connection;
}

/**
 * Get plugin-level user-facing settings, merged over defaults.
 *
 * @return array
 */
function simplestats_get_settings() {
	$defaults = array(
		'exclude_admins'           => true,
		'show_admin_bar_indicator' => true,
		'show_dashboard_widget'    => true,
	);

	return wp_parse_args( get_option( 'simplestats_settings', array() ), $defaults );
}

/**
 * Stable per-installation identity. Created on first read, persists across deactivations,
 * deleted only at uninstall. Used as identity (NOT authentication) on auth API calls.
 *
 * @return string UUID v4
 */
function simplestats_get_installation_id() {
	$id = get_option( 'simplestats_installation_id' );

	if ( empty( $id ) ) {
		$id = wp_generate_uuid4();
		update_option( 'simplestats_installation_id', $id, false );
	}

	return $id;
}

/**
 * Server keys consulted when resolving the end-user IP, most trustworthy first.
 *
 * Everything before REMOTE_ADDR is a request header and therefore chosen by the
 * client. Sites that do not sit behind a proxy or CDN can filter this down to
 * just REMOTE_ADDR; sites behind a different proxy can add its header.
 *
 * @return string[]
 */
function simplestats_trusted_ip_headers() {
	return (array) apply_filters(
		'simplestats_trusted_ip_headers',
		array( 'HTTP_CF_CONNECTING_IP', 'HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR' )
	);
}

/**
 * Resolve the real end-user IP for the current (browser → WP) request so it can be
 * forwarded to the SaaS for accurate geolocation. Account creation runs server-to-
 * server, so without this the SaaS only ever sees the WordPress server's IP.
 *
 * Walks the most-trustworthy proxy headers first, then the raw connection, and
 * returns the first syntactically-valid PUBLIC address (private/reserved ranges
 * geolocate to nothing, so they are skipped). Returns '' when none qualifies.
 *
 * IMPORTANT: the proxy headers this walks are client-supplied and therefore
 * spoofable. The return value is a geolocation hint and nothing more — it must
 * never be used for an authorization decision, a rate-limit key, or anything
 * else where an attacker choosing their own value would matter. (The forms
 * endpoint's throttle deliberately keys on REMOTE_ADDR directly instead of
 * calling this.) The only value forwarded here belongs to the administrator who
 * submitted the connect form, so spoofing it misplaces nothing but their own
 * account's location.
 *
 * @param array|null $server Defaults to $_SERVER; injectable for testing.
 * @return string A public IP address, or '' if none could be determined.
 */
function simplestats_get_client_ip( $server = null ) {
	// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each candidate is sanitized in the loop below and then validated with filter_var().
	$server = ( null === $server ) ? $_SERVER : $server;

	$candidates = array();
	foreach ( simplestats_trusted_ip_headers() as $key ) {
		if ( empty( $server[ $key ] ) ) {
			continue;
		}

		$raw = sanitize_text_field( wp_unslash( (string) $server[ $key ] ) );

		// X-Forwarded-For can be "client, proxy1, proxy2" — the client is first.
		foreach ( explode( ',', $raw ) as $part ) {
			$candidates[] = trim( $part );
		}
	}

	foreach ( $candidates as $ip ) {
		if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
			return $ip;
		}
	}

	return '';
}

/**
 * Server-to-server POST to one of the SaaS app's /wp-connector/action-* endpoints.
 * Injects identity headers, sends JSON body, decodes the Altum response envelope,
 * normalizes errors. The three identity headers are scoped to auth flows only.
 *
 * @param string $route Route under the app URL (e.g. 'wp-connector/action-login').
 * @param array  $body  Body fields. admin_url + plugin_version are merged in automatically.
 * @return array Either { ok: true, data: array } or { ok: false, error: { code, message } }.
 */
function simplestats_auth_remote_post( $route, $body = array() ) {
	$url = trailingslashit( simplestats_get_app_url() ) . ltrim( $route, '/' );

	$response = wp_remote_post( $url, array(
		// Account creation / sign-in is a foreground action the admin explicitly
		// submitted and is waiting on; a short timeout here shows a spurious
		// failure for a request that would have succeeded.
		// phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
		'timeout' => 20,
		'headers' => array(
			'Content-Type'               => 'application/json',
			'Accept'                     => 'application/json',
			'User-Agent'                 => 'SimpleStats-WordPress/' . SIMPLESTATS_PLUGIN_VERSION . ' (' . home_url() . ')',
			'X-SimpleStats-Installation' => simplestats_get_installation_id(),
			'X-SimpleStats-Site'         => home_url(),
			// Real end-user IP, forwarded so the SaaS can geolocate the actual
			// person registering instead of this WordPress server. '' when unknown.
			'X-SimpleStats-Forwarded-Ip' => simplestats_get_client_ip(),
		),
		'body'    => wp_json_encode( array_merge( array(
			'admin_url'      => admin_url(),
			'plugin_version' => SIMPLESTATS_PLUGIN_VERSION,
		), $body ) ),
	) );

	if ( is_wp_error( $response ) ) {
		return array(
			'ok'    => false,
			'error' => array(
				'code'    => 'network',
				'message' => $response->get_error_message(),
			),
		);
	}

	$code     = wp_remote_retrieve_response_code( $response );
	$raw_body = wp_remote_retrieve_body( $response );
	$decoded  = json_decode( $raw_body, true );

	// Unwrap the Altum \Altum\Response::json() 'message' envelope when present.
	$payload = ( is_array( $decoded ) && isset( $decoded['message'] ) && is_array( $decoded['message'] ) )
		? $decoded['message']
		: ( is_array( $decoded ) ? $decoded : array() );

	if ( 200 !== (int) $code ) {
		return array(
			'ok'    => false,
			'error' => array(
				'code'    => isset( $payload['code'] ) ? sanitize_text_field( $payload['code'] ) : 'server_error',
				'message' => isset( $payload['message'] ) ? sanitize_text_field( $payload['message'] ) : __( 'Something went wrong.', 'simplestats-friendly-website-analytics' ),
			),
		);
	}

	return array( 'ok' => true, 'data' => $payload );
}

/**
 * Get website connected or not
 *
 * @return bool
 */
function simplestats_is_connected() {
	$connection = simplestats_get_connection();

	if ( empty( $connection ) ) {
		return false;
	}

	if ( empty( $connection['pixel_key'] ) ) {
		return false;
	}

	if ( empty( $connection['api_key'] ) ) {
		return false;
	}

	return true;
}
