<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Logger;
use App\Models\IntegrationModel;

/**
 * OpenAiClient
 *
 * Thin wrapper around OpenAI's Chat Completions API (with tool/function
 * calling), following the same pattern as MailService: never throws on
 * a transport failure, always returns a structured
 * ['success' => bool, ...] result so callers (AiAssistantService,
 * ChatbotService) can degrade gracefully — e.g. the chatbot falls back
 * to a knowledge-base-only answer if OpenAI is unreachable, rather than
 * a hard 500.
 *
 * Reads its API key from the `integrations` table (slug 'openai'),
 * configured from Admin > Integrations — never from a hardcoded
 * constant, so rotating a key doesn't require a deploy.
 */
final class OpenAiClient
{
    private const API_URL = 'https://api.openai.com/v1/chat/completions';
    private const DEFAULT_MODEL = 'gpt-4o-mini';
    private const TIMEOUT_SECONDS = 30;

    public function __construct(private readonly IntegrationModel $integrations = new IntegrationModel())
    {
    }

    public function isConfigured(): bool
    {
        $creds = $this->integrations->credentialsFor('openai');

        return $creds !== null && !empty($creds['api_key']);
    }

    /**
     * @param array<int,array{role:string,content:?string,tool_calls?:array,tool_call_id?:string,name?:string}> $messages
     * @param array<int,array{name:string,description:string,parameters:array}> $tools
     * @return array{success:bool, message?:array, error?:string}
     */
    public function chat(array $messages, array $tools = [], ?string $model = null): array
    {
        $creds = $this->integrations->credentialsFor('openai');

        if ($creds === null || empty($creds['api_key'])) {
            return ['success' => false, 'error' => 'OpenAI is not connected. Configure it from Admin > Integrations.'];
        }

        $payload = $this->buildPayload($messages, $tools, $model ?? $creds['model'] ?? self::DEFAULT_MODEL);

        if (!function_exists('curl_init')) {
            Logger::error('OpenAiClient: the curl extension is not available.');

            return ['success' => false, 'error' => 'Server is missing the PHP curl extension.'];
        }

        $ch = curl_init(self::API_URL);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => json_encode($payload),
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/json',
                'Authorization: Bearer ' . $creds['api_key'],
            ],
            CURLOPT_TIMEOUT => self::TIMEOUT_SECONDS,
        ]);

        $responseBody = curl_exec($ch);
        $httpStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        curl_close($ch);

        if ($responseBody === false || $curlError !== '') {
            Logger::error('OpenAiClient: request failed', ['error' => $curlError]);

            return ['success' => false, 'error' => "Could not reach OpenAI: {$curlError}"];
        }

        $decoded = json_decode((string) $responseBody, true);

        if ($httpStatus !== 200 || !is_array($decoded)) {
            $apiError = $decoded['error']['message'] ?? "HTTP {$httpStatus}";
            Logger::error('OpenAiClient: API error', ['status' => $httpStatus, 'error' => $apiError]);

            return ['success' => false, 'error' => $apiError];
        }

        $message = $decoded['choices'][0]['message'] ?? null;

        if ($message === null) {
            return ['success' => false, 'error' => 'OpenAI returned no choices.'];
        }

        return ['success' => true, 'message' => $message, 'usage' => $decoded['usage'] ?? null];
    }

    /** Extracted from chat() so the exact request shape can be unit-tested without a network call. */
    public function buildPayload(array $messages, array $tools, string $model): array
    {
        $payload = [
            'model' => $model,
            'messages' => $messages,
        ];

        if ($tools !== []) {
            $payload['tools'] = array_map(
                fn (array $tool) => ['type' => 'function', 'function' => $tool],
                $tools
            );
            $payload['tool_choice'] = 'auto';
        }

        return $payload;
    }
}
