<?php
/**
 * LLM Settings.
 *
 * Stores the connection to an OpenAI-compatible chat API (Groq by default,
 * but any compatible endpoint works — Cerebras, OpenRouter, Gemini's compat
 * layer). The API key is encrypted at rest with AES-256 keyed off the site
 * salts. One source of truth for the AI features.
 *
 * @package Platform\Helm\Core
 */

declare( strict_types = 1 );

namespace Platform\Helm\Core;

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

final class LlmSettings {

	private const OPTION = 'platform_helm_llm';

	public const DEFAULT_BASE  = 'https://api.groq.com/openai/v1';
	public const DEFAULT_MODEL = 'llama-3.3-70b-versatile';

	public static function base_url(): string {
		$v = (string) ( self::raw()['base_url'] ?? '' );
		return '' !== $v ? $v : self::DEFAULT_BASE;
	}

	public static function model(): string {
		$v = (string) ( self::raw()['model'] ?? '' );
		return '' !== $v ? $v : self::DEFAULT_MODEL;
	}

	public static function key(): string {
		$enc = (string) ( self::raw()['key_enc'] ?? '' );
		return '' === $enc ? '' : self::decrypt( $enc );
	}

	public static function is_configured(): bool {
		return '' !== self::key();
	}

	/**
	 * Save settings. An empty $key keeps the existing stored key.
	 */
	public static function save( string $base_url, string $model, string $key ): void {
		$current = self::raw();
		$key_enc = (string) ( $current['key_enc'] ?? '' );
		$key     = trim( $key );
		if ( '' !== $key ) {
			$key_enc = self::encrypt( $key );
		}
		update_option( self::OPTION, [
			'base_url' => '' !== trim( $base_url ) ? esc_url_raw( trim( $base_url ) ) : self::DEFAULT_BASE,
			'model'    => '' !== trim( $model ) ? sanitize_text_field( $model ) : self::DEFAULT_MODEL,
			'key_enc'  => $key_enc,
		], false );
	}

	/** @return array<string,string> */
	private static function raw(): array {
		$v = get_option( self::OPTION, [] );
		return is_array( $v ) ? $v : [];
	}

	private static function cipher_key(): string {
		$salt = defined( 'AUTH_SALT' ) && AUTH_SALT ? AUTH_SALT : ( defined( 'AUTH_KEY' ) ? AUTH_KEY : 'platform-helm' );
		return hash( 'sha256', 'platform-helm-llm|' . $salt, true ); // 32 raw bytes for aes-256.
	}

	private static function encrypt( string $plain ): string {
		if ( '' === $plain || ! function_exists( 'openssl_encrypt' ) ) {
			return '';
		}
		$iv     = openssl_random_pseudo_bytes( 16 );
		$cipher = openssl_encrypt( $plain, 'aes-256-cbc', self::cipher_key(), OPENSSL_RAW_DATA, $iv );
		return false === $cipher ? '' : base64_encode( $iv . $cipher );
	}

	private static function decrypt( string $stored ): string {
		if ( ! function_exists( 'openssl_decrypt' ) ) {
			return '';
		}
		$raw = base64_decode( $stored, true );
		if ( false === $raw || strlen( $raw ) <= 16 ) {
			return '';
		}
		$iv    = substr( $raw, 0, 16 );
		$plain = openssl_decrypt( substr( $raw, 16 ), 'aes-256-cbc', self::cipher_key(), OPENSSL_RAW_DATA, $iv );
		return false === $plain ? '' : $plain;
	}
}
