<?php
/**
 * Consent posture settings and minimal admin persistence.
 *
 * @package Carticy\Conversions\Settings
 */

declare(strict_types=1);

namespace Carticy\Conversions\Settings;

use Carticy\Conversions\Domain\ConsentResolver;

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

/**
 * Stores the merchant's consent posture without weakening the consent gate.
 */
final class ConsentPostureSettings {

	public const POSTURE_OPTION        = 'carticy_conversions_consent_posture';
	public const ATTESTATION_OPTION    = 'carticy_conversions_consent_attestation';
	public const IP_MODE_OPTION        = 'carticy_conversions_ip_mode';
	public const RETENTION_DAYS_OPTION = 'carticy_conversions_retention_days';
	public const CONSENT_CONFIG_OPTION = 'carticy_conversions_consent_config';

	/**
	 * Per-service gating modes the intake layer understands.
	 *
	 * `auto` leaves the resolver's normal fail-closed allow-list in charge;
	 * `require_explicit` is an explicit intent to keep a service paused until an
	 * explicit grant is captured (documentary, the gate is already allow-list);
	 * `off` disables the service entirely.
	 */
	private const SERVICE_MODES = array( 'auto', 'require_explicit', 'off' );

	/**
	 * Services that can be gated independently.
	 */
	private const GATED_SERVICES = array( 'ga4', 'google-ads', 'meta' );

	/**
	 * Seed default options.
	 */
	public static function add_default_options(): void {
		$defaults = array(
			self::POSTURE_OPTION        => ConsentResolver::POSTURE_REQUIRED,
			self::ATTESTATION_OPTION    => array(),
			self::IP_MODE_OPTION        => 'anonymize',
			self::RETENTION_DAYS_OPTION => 180,
		);

		foreach ( $defaults as $option_name => $default_value ) {
			if ( false === get_option( $option_name ) ) {
				add_option( $option_name, $default_value );
			}
		}
	}

	/**
	 * Current posture.
	 *
	 * @return string
	 */
	public function posture(): string {
		$posture = sanitize_key( (string) get_option( self::POSTURE_OPTION, ConsentResolver::POSTURE_REQUIRED ) );

		if ( in_array( $posture, $this->valid_postures(), true ) ) {
			return $posture;
		}

		return ConsentResolver::POSTURE_REQUIRED;
	}

	/**
	 * Current IP storage mode.
	 *
	 * @return string
	 */
	public function ip_mode(): string {
		$mode = sanitize_key( (string) get_option( self::IP_MODE_OPTION, 'anonymize' ) );

		return in_array( $mode, array( 'anonymize', 'omit', 'store' ), true ) ? $mode : 'anonymize';
	}

	/**
	 * Retention window in days.
	 *
	 * @return int
	 */
	public function retention_days(): int {
		$days = abs( (int) get_option( self::RETENTION_DAYS_OPTION, 180 ) );

		return $days > 0 ? $days : 180;
	}

	/**
	 * Last merchant attestation.
	 *
	 * @return array<string, mixed>
	 */
	public function attestation(): array {
		$attestation = get_option( self::ATTESTATION_OPTION, array() );

		return is_array( $attestation ) ? $attestation : array();
	}

	/**
	 * Per-service gating + preset selection (intake configuration).
	 *
	 * This configures how consent SIGNALS are read; it never writes a consent
	 * record and never weakens {@see \Carticy\Conversions\Domain\ConsentResolver}
	 * (the send-time gate stays an explicit-grant allow-list).
	 *
	 * @return array<string, mixed>
	 */
	public function consent_config(): array {
		$stored = get_option( self::CONSENT_CONFIG_OPTION, array() );

		return $this->normalize_consent_config( is_array( $stored ) ? $stored : array() );
	}

	/**
	 * Persist posture and the dependent attestation, returning the applied value.
	 *
	 * Server-validated: an unknown posture falls back to `required` (fail closed)
	 * and `not_required` is never the silent default - only an explicit, valid
	 * `not_required` records the merchant attestation. Leaving `not_required`
	 * clears the stale attestation so a posture change cannot keep granting.
	 *
	 * @param string $posture Requested posture.
	 * @return string Applied posture.
	 */
	public function apply_posture( string $posture ): string {
		$posture = sanitize_key( $posture );
		if ( ! in_array( $posture, $this->valid_postures(), true ) ) {
			$posture = ConsentResolver::POSTURE_REQUIRED;
		}

		update_option( self::POSTURE_OPTION, $posture );

		if ( ConsentResolver::POSTURE_NOT_REQUIRED === $posture ) {
			update_option(
				self::ATTESTATION_OPTION,
				array(
					'posture'        => $posture,
					'actor_user_id'  => function_exists( 'get_current_user_id' ) ? (int) get_current_user_id() : 0,
					'attested_at'    => gmdate( 'Y-m-d H:i:s' ),
					'plugin_version' => defined( 'CARTICY_CONVERSIONS_VERSION' ) ? CARTICY_CONVERSIONS_VERSION : '',
				)
			);
		} else {
			update_option( self::ATTESTATION_OPTION, array() );
		}

		return $posture;
	}

	/**
	 * Persist the privacy/data settings.
	 *
	 * @param string $ip_mode IP storage mode.
	 * @param int    $retention_days Retention window in days.
	 */
	public function apply_privacy( string $ip_mode, int $retention_days ): void {
		$ip_mode = sanitize_key( $ip_mode );
		update_option( self::IP_MODE_OPTION, in_array( $ip_mode, array( 'anonymize', 'omit', 'store' ), true ) ? $ip_mode : 'anonymize' );

		$retention_days = abs( $retention_days );
		update_option( self::RETENTION_DAYS_OPTION, $retention_days > 0 ? $retention_days : 180 );
	}

	/**
	 * Persist the per-service gating + preset selection, returning the normalized value.
	 *
	 * @param array<string, mixed> $config Raw intake configuration.
	 * @return array<string, mixed>
	 */
	public function apply_consent_config( array $config ): array {
		$normalized = $this->normalize_consent_config( $config );
		update_option( self::CONSENT_CONFIG_OPTION, $normalized );

		return $normalized;
	}

	/**
	 * Normalize the intake configuration to its allow-listed shape.
	 *
	 * @param array<string, mixed> $config Raw configuration.
	 * @return array<string, mixed>
	 */
	private function normalize_consent_config( array $config ): array {
		$services_in = is_array( $config['services'] ?? null ) ? $config['services'] : array();
		$services    = array();
		foreach ( self::GATED_SERVICES as $service ) {
			$mode                 = sanitize_key( (string) ( $services_in[ $service ] ?? 'auto' ) );
			$services[ $service ] = in_array( $mode, self::SERVICE_MODES, true ) ? $mode : 'auto';
		}

		return array(
			'services'        => $services,
			'selected_preset' => sanitize_text_field( (string) ( $config['selected_preset'] ?? '' ) ),
		);
	}

	/**
	 * Valid posture values.
	 *
	 * @return array<int, string>
	 */
	private function valid_postures(): array {
		return array(
			ConsentResolver::POSTURE_CMP_DRIVEN,
			ConsentResolver::POSTURE_REQUIRED,
			ConsentResolver::POSTURE_NOT_REQUIRED,
		);
	}
}
