<?php
/**
 * Signed client for Niwa-owned central services.
 *
 * @package Niwa_AI
 */

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

class Niwa_AI_Calendar_OAuth_Relay_Client {

	const BASE_URL = 'https://niwachat.com/wp-json/niwa-calendar-oauth/v1';

	/** Whether this server can create the installation signing identity. */
	public function is_available() {
		return ! is_wp_error( Niwa_AI_Calendar_Credentials::site_identity() )
			&& ! is_wp_error( Niwa_AI_Calendar_Install_Attestation::create() );
	}

	/** Build the public one-click Google authorization entry URL. */
	public function authorization_url( $callback_url, $state ) {
		$result = $this->signed_request(
			'/google/start',
			array(
				'redirect_uri' => esc_url_raw( (string) $callback_url ),
				'state'        => (string) $state,
			)
		);
		return is_wp_error( $result ) || empty( $result['authorization_url'] )
			? ( is_wp_error( $result ) ? $result : new WP_Error( 'calendar_authorization_url_missing', __( 'Google authorization could not be started.', 'niwa-ai' ) ) )
			: (string) $result['authorization_url'];
	}

	/** Redeem a one-time callback code without putting connector credentials in a URL. */
	public function exchange( $exchange_code ) {
		return $this->signed_request( '/google/exchange', array( 'exchange_code' => (string) $exchange_code ) );
	}

	/** Exchange a site-bound connector for a short-lived Google access token. */
	public function access_token( $connector_token ) {
		$result = $this->signed_request( '/google/token', array( 'connector_token' => (string) $connector_token ) );
		return is_wp_error( $result ) || empty( $result['access_token'] )
			? ( is_wp_error( $result ) ? $result : new WP_Error( 'google_access_token_missing', __( 'Google did not return an access token.', 'niwa-ai' ) ) )
			: (string) $result['access_token'];
	}

	/** Revoke a connector at Google. Disconnect remains local even if this fails. */
	public function revoke( $connector_token ) {
		return $this->signed_request( '/google/revoke', array( 'connector_token' => (string) $connector_token ) );
	}

	/** Request embeddings without exposing the central OpenAI key. */
	public function embeddings( array $texts ) {
		return $this->signed_request( '/embeddings', array( 'input' => array_values( $texts ) ), 45 );
	}

	/** Validate the opaque connector shape before storing it. */
	public static function is_connector_token( $token ) {
		return is_string( $token ) && strlen( $token ) >= 80 && strlen( $token ) <= 4096 && 1 === preg_match( '/^[A-Za-z0-9_-]+$/', $token );
	}

	private function signed_request( $route, array $payload, $timeout = 20 ) {
		$identity = Niwa_AI_Calendar_Credentials::site_identity();
		if ( is_wp_error( $identity ) ) {
			return $identity;
		}
		$attestation = Niwa_AI_Calendar_Install_Attestation::create();
		if ( is_wp_error( $attestation ) ) {
			return $attestation;
		}
		$payload['install'] = $attestation;
		$body      = wp_json_encode( $payload );
		$timestamp = (string) time();
		$nonce     = Niwa_AI_Calendar_Credentials::random_token( 18 );
		$canonical = "POST\n" . $route . "\n" . $timestamp . "\n" . $nonce . "\n" . hash( 'sha256', $body );
		$signature = '';
		if ( ! openssl_sign( $canonical, $signature, $identity['secret_key'], OPENSSL_ALGO_SHA256 ) ) {
			return new WP_Error( 'calendar_signature_failed', __( 'Calendar service request could not be signed.', 'niwa-ai' ) );
		}

		$response = wp_remote_post(
			self::BASE_URL . $route,
			array(
				'timeout' => max( 1, (int) $timeout ),
				'headers' => array(
					'Content-Type'     => 'application/json; charset=UTF-8',
					'X-Niwa-Site-Key'  => Niwa_AI_Calendar_Credentials::encode_binary( $identity['public_key'] ),
					'X-Niwa-Timestamp' => $timestamp,
					'X-Niwa-Nonce'     => $nonce,
					'X-Niwa-Signature' => Niwa_AI_Calendar_Credentials::encode_binary( $signature ),
				),
				'body'    => $body,
			)
		);
		if ( is_wp_error( $response ) ) {
			return $response;
		}
		$status = (int) wp_remote_retrieve_response_code( $response );
		$data   = json_decode( (string) wp_remote_retrieve_body( $response ), true );
		if ( $status < 200 || $status >= 300 ) {
			$message = is_array( $data ) ? (string) ( $data['message'] ?? $data['error'] ?? '' ) : '';
			return new WP_Error(
				'niwa_service_relay_http',
				'' !== $message ? $message : sprintf( __( 'Niwa service returned HTTP %d.', 'niwa-ai' ), $status ),
				$status
			);
		}
		return is_array( $data ) ? $data : array();
	}
}
