<?php

namespace ZenoChatbot\Services\Chat\Embedding;

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

use ZenoChatbot\Services\Chat\Contracts\EmbeddingProviderInterface;
use ZenoChatbot\Services\HttpClient;
use ZenoChatbot\Helper\Settings;
use ZenoChatbot\Helper\Security;

/**
 * OpenAI Embedding Provider
 *
 * Uses OpenAI's text-embedding-3-small model to generate embeddings.
 * This model produces 1536-dimensional vectors optimized for semantic similarity.
 *
 * @see https://platform.openai.com/docs/guides/embeddings
 */
class OpenAIEmbeddingProvider implements EmbeddingProviderInterface
{
    public const API_URL = 'https://api.openai.com/v1/embeddings';
    public const MODEL_SMALL = 'text-embedding-3-small';
    public const MODEL_LARGE = 'text-embedding-3-large';
    public const MODEL_ADA = 'text-embedding-ada-002';

    /**
     * Embedding calls run inside visitor chat requests — keep the timeout
     * well below HttpClient's 30s default so a slow API cannot stall chat.
     */
    public const REQUEST_TIMEOUT = 10;

    protected HttpClient $httpClient;
    protected string $apiKey;
    protected string $model;

    /**
     * @param string|null $apiKey API key (uses LLM settings if not provided)
     * @param string $model Embedding model to use
     */
    public function __construct(?string $apiKey = null, string $model = self::MODEL_SMALL)
    {
        $this->httpClient = new HttpClient([], static::REQUEST_TIMEOUT);
        $this->apiKey = $apiKey ?? $this->getApiKeyFromSettings();
        $this->model = $model;
    }

    /**
     * Generate embedding vector for text
     *
     * @param string $text Text to embed
     * @return array Float array of embedding dimensions (1536 for small model)
     * @throws \Exception If API call fails
     */
    public function embed(string $text): array
    {
        if (empty(trim($text))) {
            return [];
        }

        if (empty($this->apiKey)) {
            throw new \Exception('OpenAI API key is not configured');
        }

        $response = $this->httpClient->post(
            self::API_URL,
            [
                'input' => $text,
                'model' => $this->model,
            ],
            [
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type' => 'application/json',
            ]
        );

        // HttpClient returns wrapped response with body key
        $body = $response['body'] ?? $response;

        if (!isset($body['data'][0]['embedding'])) {
            throw new \Exception('Invalid embedding response from OpenAI');
        }

        return $body['data'][0]['embedding'];
    }

    /**
     * Generate embeddings for multiple texts in batch
     *
     * @param array $texts Array of texts to embed
     * @return array Array of embedding vectors
     * @throws \Exception If API call fails
     */
    public function embedBatch(array $texts): array
    {
        if (empty($texts)) {
            return [];
        }

        // Filter out empty texts
        $texts = array_filter($texts, fn($t) => !empty(trim($t)));
        if (empty($texts)) {
            return [];
        }

        if (empty($this->apiKey)) {
            throw new \Exception('OpenAI API key is not configured');
        }

        $response = $this->httpClient->post(
            self::API_URL,
            [
                'input' => array_values($texts),
                'model' => $this->model,
            ],
            [
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type' => 'application/json',
            ]
        );

        // HttpClient returns wrapped response with body key
        $body = $response['body'] ?? $response;

        if (!isset($body['data']) || !is_array($body['data'])) {
            throw new \Exception('Invalid batch embedding response from OpenAI');
        }

        // Sort by index to maintain order
        $embeddings = [];
        foreach ($body['data'] as $item) {
            $embeddings[$item['index']] = $item['embedding'];
        }
        ksort($embeddings);

        return array_values($embeddings);
    }

    /**
     * Get API key from LLM settings
     */
    protected function getApiKeyFromSettings(): string
    {
        return Settings::getApiKey('llm.api_key', '');
    }

    /**
     * Get the model being used
     */
    public function getModel(): string
    {
        return $this->model;
    }

    /**
     * Get embedding dimensions for the current model
     */
    public function getDimensions(): int
    {
        if ($this->model === self::MODEL_LARGE) {
            return 3072;
        }

        return 1536;
    }
}

