<?php
/**
 * PS_Label_Enrollment — self-service registration with the VPS.
 *
 * On plugin activation (and as a safety net on every request that needs the
 * webhook secret), POSTs the site URL + contact email to
 * https://map.parcelselector.com/embed/label/enroll and stores the returned
 * per-merchant credentials in wp_options. The VPS is the source of truth —
 * the plugin never invents its own secrets.
 *
 * Stored options:
 *   parcselect_installation_id   — public merchant identifier, sent as X-PS-Installation
 *   ps_label_proxy_secret    — per-merchant HMAC secret for outbound calls
 *   ps_label_webhook_secret  — symmetric secret for incoming webhook verification
 *   ps_label_enrolled_at     — ISO8601 timestamp returned by the VPS
 *
 * @package ParcelSelector
 */

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

class Parcselect_Label_Enrollment {

	const ENROLL_URL_DEFAULT = 'https://map.parcelselector.com/embed/label/enroll';

	public static function init() {
		add_action( 'admin_init', array( __CLASS__, 'maybe_enroll' ) );
	}

	/**
	 * If we already have per-merchant credentials, nothing to do. Otherwise
	 * call the VPS enroll endpoint and save whatever it returns.
	 */
	public static function maybe_enroll() {
		if ( self::is_enrolled() ) {
			return;
		}
		// Throttle to once per 15 minutes — prevents a request storm if the VPS
		// is unreachable and every admin pageload retries.
		if ( get_transient( 'parcselect_label_enroll_backoff' ) ) {
			return;
		}
		set_transient( 'parcselect_label_enroll_backoff', 1, 15 * MINUTE_IN_SECONDS );

		// v4.2.1 BEZWZGLEDNY consent gate — enrollment WYLACZNIE po Stage 1 click.
		// Auto-enroll przy plugin init NIE WYSYLA danych do VPS bez consent.
		if ( ! class_exists( 'Parcselect_Label_Consent_Gate' ) || ! Parcselect_Label_Consent_Gate::is_valid() ) {
			return new WP_Error( 'consent_required', 'Enrollment blocked: Stage 1 consent not given' );
		}
		$result = self::enroll();
		if ( is_wp_error( $result ) ) {
			// Store error for admin notice — so admin knows why labels aren't working.
			set_transient( 'parcselect_label_enroll_error', $result->get_error_message(), HOUR_IN_SECONDS );
			return;
		}
		delete_transient( 'parcselect_label_enroll_error' );
	}

	/**
	 * Force a fresh enrollment now. Called from activation hook and from the
	 * diagnostics page "re-enroll" button.
	 *
	 * @return true|WP_Error
	 */
	public static function enroll() {
		$endpoint = defined( 'PS_LABEL_ENROLL_URL' ) && PS_LABEL_ENROLL_URL
			? (string) PS_LABEL_ENROLL_URL
			: self::ENROLL_URL_DEFAULT;

		if ( class_exists( 'Parcselect_Label_Debug' ) ) {
			Parcselect_Label_Debug::info( null, 'enroll.request', array( 'endpoint' => $endpoint, 'site_url' => home_url() ) );
		}

		$response = wp_remote_post(
			$endpoint,
			array(
				'timeout'   => 15,
				'sslverify' => apply_filters( 'parcselect_label_sslverify', true ),
				'headers'   => array(
					'Content-Type' => 'application/json',
					'Accept'       => 'application/json',
					'User-Agent'   => self::user_agent(),
				),
				'body'      => wp_json_encode(
					array(
						'site_url'      => home_url(),
						'contact_email' => (string) get_option( 'admin_email', '' ),
					)
				),
			)
		);
		if ( is_wp_error( $response ) ) {
			if ( class_exists( 'Parcselect_Label_Debug' ) ) {
				Parcselect_Label_Debug::error( null, 'enroll.network_error', array(
					'code'    => $response->get_error_code(),
					'message' => $response->get_error_message(),
				) );
			}
			return $response;
		}
		$code = (int) wp_remote_retrieve_response_code( $response );
		$body = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( 200 !== $code || empty( $body['ok'] ) || empty( $body['installation_id'] ) ) {
			$msg = is_array( $body ) && isset( $body['error'] ) ? $body['error'] : 'HTTP ' . $code;
			if ( class_exists( 'Parcselect_Label_Debug' ) ) {
				Parcselect_Label_Debug::error( null, 'enroll.http_error', array(
					'http' => $code,
					'msg'  => $msg,
				) );
			}
			return new WP_Error( 'enroll_failed', $msg, $body );
		}

		// Sanity-check lengths. The VPS generates 32-char hex installation_id and
		// 64-char hex secrets — anything shorter is a schema mismatch and should
		// not be stored.
		$iid  = (string) $body['installation_id'];
		$apis = (string) ( $body['api_secret'] ?? '' );
		$wh   = (string) ( $body['webhook_secret'] ?? '' );
		if ( strlen( $iid ) < 16 || strlen( $apis ) < 32 || strlen( $wh ) < 32 ) {
			return new WP_Error( 'bad_enroll_response', 'Credentials returned are too short.' );
		}

		update_option( 'parcselect_installation_id', $iid, false );
		update_option( 'parcselect_label_proxy_secret', $apis, false );
		update_option( 'parcselect_label_webhook_secret', $wh, false );
		update_option( 'parcselect_label_enrolled_at', (string) ( $body['enrolled_at'] ?? gmdate( 'c' ) ), false );
		// Wallet may be returned — store it so metabox shows something
		// even when session_id is lost or fallback UI needs to render.
		if ( ! empty( $body['sol_wallet'] ) ) {
			update_option( 'parcselect_label_wallet_sol', sanitize_text_field( (string) $body['sol_wallet'] ), false );
		}
		delete_transient( 'parcselect_label_enroll_backoff' );

		if ( class_exists( 'Parcselect_Label_Debug' ) ) {
			Parcselect_Label_Debug::info( null, 'enroll.ok', array(
				'installation_id'    => $iid,
				'api_secret_len'     => strlen( $apis ),
				'webhook_secret_len' => strlen( $wh ),
				'has_sol_wallet'     => ! empty( $body['sol_wallet'] ),
				'existing'           => ! empty( $body['existing'] ),
			) );
		}

		return true;
	}

	/**
	 * Quick sender-data completeness check. Returns array of missing field
	 * labels — empty array means everything shipping provider minimally needs is
	 * present. Called before session creation so we can surface a clear
	 * "fill these in" message instead of letting shipping provider reject with a
	 * cryptic 400.
	 */
	public static function missing_sender_fields() {
		$required = array(
			'parcselect_label_sender_name'     => __( 'Sender name', 'parcel-selector' ),
			'parcselect_label_sender_street'   => __( 'Sender street + number', 'parcel-selector' ),
			'parcselect_label_sender_city'     => __( 'Sender city', 'parcel-selector' ),
			'parcselect_label_sender_postcode' => __( 'Sender postcode', 'parcel-selector' ),
			'parcselect_label_sender_phone'    => __( 'Sender phone', 'parcel-selector' ),
			'parcselect_label_sender_email'    => __( 'Sender email', 'parcel-selector' ),
		);
		$missing = array();
		foreach ( $required as $opt => $label ) {
			$v = trim( (string) get_option( $opt, '' ) );
			if ( '' === $v ) {
				$missing[] = $label;
			}
		}
		return $missing;
	}

	public static function is_enrolled() {
		$iid = (string) get_option( 'parcselect_installation_id', '' );
		$apis = (string) get_option( 'parcselect_label_proxy_secret', '' );
		$wh = (string) get_option( 'parcselect_label_webhook_secret', '' );
		return ( strlen( $iid ) >= 16 && strlen( $apis ) >= 32 && strlen( $wh ) >= 32 );
	}

	protected static function user_agent() {
		global $wp_version;
		$host = wp_parse_url( home_url(), PHP_URL_HOST );
		return sprintf(
			'ParcelSelector/%s (WP/%s; PHP/%s; %s)',
			defined( 'PARCSELECT_VERSION' ) ? PARCSELECT_VERSION : 'unknown',
			isset( $wp_version ) ? $wp_version : '?',
			PHP_VERSION,
			$host ? $host : 'unknown'
		);
	}
}
