<?php

declare(strict_types=1);

namespace App\Infrastructure\AI\Ollama;

use App\Domain\AI\AiProvider;
use App\Domain\AI\AiQuery;
use App\Domain\AI\AiQueryHandlerInterface;
use App\Domain\AI\AiResponse;
use App\Domain\AI\Exception\AiProviderUnavailableException;
use App\Framework\Http\Headers;
use App\Framework\Http\Method;
use App\Framework\HttpClient\ClientOptions;
use App\Framework\HttpClient\ClientRequest;
use App\Framework\HttpClient\Exception\CurlExecutionFailed;
use App\Framework\HttpClient\HttpClient;

final readonly class OllamaQueryHandler implements AiQueryHandlerInterface
{
    public function __construct(
        private HttpClient $httpClient,
        private string $ollamaApiUrl = 'http://host.docker.internal:11434'
    ) {
    }

    public function isAvailable(): bool
    {
        #debug(file_get_contents('http://host.docker.internal:11434/api/tags'));

        try {
            $healthUrl = $this->ollamaApiUrl . '/api/tags';
            $request = new ClientRequest(
                method: Method::GET,
                url: $healthUrl,
                headers: new Headers(),
                body: '',
                options: new ClientOptions(
                    timeout       : 360,
                    verifySsl     : false,
                    connectTimeout: 60
                )
            );

            $this->httpClient->send($request);

            return true;
        } catch (CurlExecutionFailed $e) {
            return false;
        } catch (\Exception $e) {
            return false;
        }
    }

    public function getAvailableModels(): array
    {
        try {
            $tagsUrl = $this->ollamaApiUrl . '/api/tags';
            $request = ClientRequest::json(Method::GET, $tagsUrl, []);

            $response = $this->httpClient->send($request);
            $body = json_decode($response->body, true);

            return array_map(fn ($model) => $model['name'], $body['models'] ?? []);
        } catch (\Exception $e) {
            return [];
        }
    }

    public function __invoke(AiQuery $query): AiResponse
    {
        if (! $this->isAvailable()) {
            throw new AiProviderUnavailableException(
                AiProvider::OLLAMA,
                "Ollama Server ist nicht erreichbar unter {$this->ollamaApiUrl}. " .
                "Stellen Sie sicher, dass Ollama läuft oder verwenden Sie einen anderen Provider."
            );
        }

        // Prüfen ob das Modell verfügbar ist
        $availableModels = $this->getAvailableModels();
        $modelExists = in_array($query->model->value, $availableModels, true);

        if (! $modelExists) {
            throw new AiProviderUnavailableException(
                AiProvider::OLLAMA,
                "Modell '{$query->model->value}' ist nicht verfügbar. " .
                "Verfügbare Modelle: " . implode(', ', $availableModels) . ". " .
                "Installieren Sie das Modell mit: ollama pull {$query->model->value}"
            );
        }

        $url = $this->ollamaApiUrl . '/api/chat';

        // Ollama verwendet ein anderes Format als OpenAI
        $messages = $query->messages !== []
            ? $query->messages
            : [['role' => 'user', 'content' => $query->message]];

        $data = [
            'model' => $query->model->value,
            'messages' => $messages,
            'stream' => false, // Keine Streaming-Antwort
            'options' => [
                'temperature' => $query->temperature,
            ],
        ];

        if ($query->maxTokens !== null) {
            $data['options']['num_predict'] = $query->maxTokens;
        }

        try {
            $request = ClientRequest::json(
                Method::POST,
                $url,
                $data
            )->with([
                'headers' => new Headers()
                    ->with('Content-Type', 'application/json'),
            ]);

            $response = $this->httpClient->send($request);
            $body = json_decode($response->body, true);

            // Ollama API Antwortformat ist anders als OpenAI
            $content = $body['message']['content'] ?? '';

            return new AiResponse(
                content: $content,
                provider: 'ollama',
                model: $query->model->value,
                tokensUsed: $body['eval_count'] ?? null // Ollama verwendet eval_count für Token-Zählung
            );
        } catch (CurlExecutionFailed $e) {
            throw new AiProviderUnavailableException(
                AiProvider::OLLAMA,
                "Verbindung zu Ollama fehlgeschlagen: " . $e->getMessage()
            );
        }
    }
}
