<?php
/**
 * Thin client for the Aetheris hosted API. Used by the admin pages to ingest
 * knowledge base content and pull stats. Front-end embedding does NOT go
 * through here — the widget calls the API directly from the visitor's browser.
 *
 * @package AetherisAIChat
 */

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

final class Aetheris_AI_Chat_API {

	private static function base() {
		return rtrim( Aetheris_AI_Chat_Settings::get( 'api_base' ), '/' );
	}

	/**
	 * Build a complete request-args bundle for `wp_remote_*`. Centralised so
	 * every call uses the same defensive HTTP options.
	 *
	 * Why these specific defaults exist:
	 *  - `httpversion: '1.1'` — Railway's edge serves HTTP/2 by default. Some
	 *    WP installs (notably playground.wordpress.net's WASM build) misbehave
	 *    around HTTP/2 + Authorization headers; forcing 1.1 sidesteps it.
	 *  - `redirection: 0` — if the backend ever returns a 3xx, we want to see
	 *    that as a status code in the response, NOT silently follow the
	 *    redirect (where Authorization may be dropped on protocol downgrade).
	 *  - `Cache-Control: no-cache` + `Pragma: no-cache` — keeps any reverse
	 *    proxy in front of WP from serving a stale auth-error response.
	 *  - User-Agent identifies the plugin so we can correlate in server logs.
	 */
	private static function request_args( array $extra_headers = array(), array $overrides = array() ) {
		$api_key = Aetheris_AI_Chat_Settings::get( 'api_key' );

		$headers = array(
			'Accept'        => 'application/json',
			'Cache-Control' => 'no-cache',
			'Pragma'        => 'no-cache',
			'User-Agent'    => 'AetherisAIChat-WP/' . AETHERIS_AI_CHAT_VERSION . '; ' . home_url(),
		);

		// API key auth is the only path the management endpoints accept.
		// The public_key alone authenticates the widget (visitor side), not
		// admin reads/writes against /v1/ingest, /v1/stats, /v1/conversations.
		if ( ! empty( $api_key ) ) {
			$headers['Authorization'] = 'Bearer ' . $api_key;
		}

		$args = array(
			'headers'     => array_merge( $headers, $extra_headers ),
			'httpversion' => '1.1',
			'redirection' => 0,
			'sslverify'   => true,
			'timeout'     => 15,
		);

		return array_merge( $args, $overrides );
	}

	/**
	 * @return array|WP_Error
	 */
	public static function ingest_url( $url ) {
		if ( ! self::has_credentials() ) {
			return new WP_Error( 'no_auth', __( 'Add your API key in Settings to ingest URLs.', 'aetheris-ai-chat' ) );
		}

		$response = wp_remote_post(
			self::base() . '/v1/ingest/url',
			self::request_args(
				array( 'Content-Type' => 'application/json' ),
				array(
					'timeout' => 60,
					'body'    => wp_json_encode( array( 'url' => esc_url_raw( $url ) ) ),
				)
			)
		);

		return self::parse_response( $response );
	}

	/**
	 * @return array|WP_Error
	 */
	public static function ingest_file( $file_path, $title = '' ) {
		if ( ! self::has_credentials() ) {
			return new WP_Error( 'no_auth', __( 'Add your API key in Settings to upload files.', 'aetheris-ai-chat' ) );
		}
		if ( ! file_exists( $file_path ) ) {
			return new WP_Error( 'no_file', __( 'Upload failed — temp file missing.', 'aetheris-ai-chat' ) );
		}

		$boundary = wp_generate_password( 24, false );

		$body  = '';
		$body .= '--' . $boundary . "\r\n";
		$body .= 'Content-Disposition: form-data; name="title"' . "\r\n\r\n";
		$body .= sanitize_text_field( $title ) . "\r\n";

		$filename = basename( $file_path );
		$body    .= '--' . $boundary . "\r\n";
		$body    .= 'Content-Disposition: form-data; name="file"; filename="' . $filename . '"' . "\r\n";
		$body    .= 'Content-Type: application/octet-stream' . "\r\n\r\n";
		$body    .= file_get_contents( $file_path ) . "\r\n";
		$body    .= '--' . $boundary . '--' . "\r\n";

		$response = wp_remote_post(
			self::base() . '/v1/ingest/file',
			self::request_args(
				array( 'Content-Type' => 'multipart/form-data; boundary=' . $boundary ),
				array(
					'timeout' => 120,
					'body'    => $body,
				)
			)
		);

		return self::parse_response( $response );
	}

	/**
	 * @return array|WP_Error
	 */
	public static function get_stats() {
		return self::cached_get(
			'aetheris_stats_' . self::cache_suffix(),
			self::base() . '/v1/stats',
			__( 'Connect your account to view analytics.', 'aetheris-ai-chat' )
		);
	}

	/**
	 * @return array|WP_Error
	 */
	public static function get_conversations( $limit = 25 ) {
		$limit = max( 1, min( 200, (int) $limit ) );
		// Build the URL by string concat rather than `add_query_arg` so the
		// resulting URL is byte-identical to what we'd hand-write — eliminates
		// any chance of WP-side URL normalisation munging the request.
		$url = self::base() . '/v1/conversations?limit=' . $limit;
		return self::cached_get(
			'aetheris_convos_' . self::cache_suffix() . '_' . $limit,
			$url,
			__( 'Connect your account to view chat history.', 'aetheris-ai-chat' )
		);
	}

	/**
	 * Shared GET path for the read-only analytics endpoints. Pulls from a
	 * 60-second transient cache, makes one request through `request_args`,
	 * caches successful responses, and surfaces backend `detail` errors.
	 */
	private static function cached_get( $cache_key, $url, $no_auth_message ) {
		if ( ! self::has_credentials() ) {
			return new WP_Error( 'no_auth', $no_auth_message );
		}
		$cached = get_transient( $cache_key );
		if ( false !== $cached ) {
			return $cached;
		}
		$response = wp_remote_get( $url, self::request_args() );

		if ( defined( 'WP_DEBUG' ) && WP_DEBUG && defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
			$code = is_wp_error( $response ) ? 'WP_ERROR' : wp_remote_retrieve_response_code( $response );
			error_log( '[Aetheris] GET ' . $url . ' → ' . $code );
		}

		$result = self::parse_response( $response );
		if ( ! is_wp_error( $result ) ) {
			set_transient( $cache_key, $result, 60 );
		}
		return $result;
	}

	private static function cache_suffix() {
		$key = (string) Aetheris_AI_Chat_Settings::get( 'public_key' );
		return substr( md5( $key ), 0, 12 );
	}

	public static function has_credentials() {
		// Admin endpoints (ingest, stats, conversations) require an API key.
		// The public key alone authenticates the widget for visitors, not the
		// owner-side management calls this client makes from PHP.
		$ak = Aetheris_AI_Chat_Settings::get( 'api_key' );
		return ! empty( $ak );
	}

	private static function parse_response( $response ) {
		if ( is_wp_error( $response ) ) {
			return $response;
		}
		$code = wp_remote_retrieve_response_code( $response );
		$body = wp_remote_retrieve_body( $response );
		$json = json_decode( $body, true );

		if ( $code < 200 || $code >= 300 ) {
			$message = is_array( $json ) && isset( $json['detail'] )
				? $json['detail']
				/* translators: %d: HTTP status code returned by the Aetheris API. */
				: sprintf( __( 'API request failed (HTTP %d).', 'aetheris-ai-chat' ), $code );
			return new WP_Error( 'api_error', $message, array( 'status' => $code ) );
		}
		return is_array( $json ) ? $json : array( 'raw' => $body );
	}
}
