<?php

declare(strict_types=1);

require_once __DIR__ . '/Env.php';
require_once __DIR__ . '/Logger.php';

class OpenAIClient
{
    private string $apiKey;
    private string $model;
    private string $imageModel;

    public function __construct()
    {
        $this->apiKey = Env::get('OPENAI_API_KEY') ?? '';
        if ($this->apiKey === '') {
            throw new RuntimeException('OPENAI_API_KEY is not configured');
        }

        $this->model = Env::get('OPENAI_MODEL', 'gpt-4o-2024-08-06');
        $this->imageModel = Env::get('IMAGE_MODEL', Env::get('OPENAI_IMAGE_MODEL', 'gpt-image-1'));
    }

    public function chatCompletion(array $messages, array $params = []): string
    {
        $payload = array_merge([
            'model' => $this->model,
            'messages' => $messages,
        ], $params);

        $response = $this->request('https://api.openai.com/v1/chat/completions', $payload);
        if (empty($response['choices'][0]['message']['content'])) {
            throw new RuntimeException('OpenAI chat response missing content');
        }

        return $response['choices'][0]['message']['content'];
    }

    public function chatCompletionJson(array $messages, array $schema, array $params = []): array
    {
        $payload = array_merge([
            'model' => $this->model,
            'messages' => $messages,
            'response_format' => [
                'type' => 'json_schema',
                'json_schema' => [
                    'name' => 'output',
                    'schema' => $schema,
                    'strict' => true,
                ],
            ],
        ], $params);

        $response = $this->request('https://api.openai.com/v1/chat/completions', $payload);
        $choice = $response['choices'][0]['message']['content'] ?? null;
        if ($choice === null) {
            throw new RuntimeException('Structured OpenAI response missing content');
        }

        if (is_array($choice) && array_key_exists('value', $choice)) {
            return $choice['value'];
        }

        if (is_string($choice)) {
            $decoded = json_decode($choice, true);
            if (is_array($decoded)) {
                return $decoded;
            }
        }

        throw new RuntimeException('Structured OpenAI response invalid schema');
    }

    public function generateImage(string $prompt, string $size = '1024x1024', string $quality = 'high', int $n = 1): string
    {
        $model = $this->imageModel;
        $payload = [
            'model' => $model,
            'prompt' => $prompt,
        ];

        $isDallE = strcasecmp($model, 'dall-e-3') === 0;
        if ($isDallE) {
            $allowedSizes = ['1024x1024', '1024x1792', '1792x1024'];
            if (!in_array($size, $allowedSizes, true)) {
                $size = '1024x1024';
            }
            $normalizedQuality = match (strtolower($quality)) {
                'high' => 'hd',
                'medium', 'standard' => 'standard',
                default => 'standard',
            };

            $payload['size'] = $size;
            $payload['quality'] = $normalizedQuality;
            $payload['n'] = 1;
            $payload['response_format'] = 'b64_json';
        } else {
            $payload['size'] = $size;
            $payload['quality'] = $quality;
            $payload['n'] = $n;
        }

        $response = $this->request('https://api.openai.com/v1/images/generations', $payload);
        $data = $response['data'][0] ?? null;
        if (!is_array($data)) {
            throw new RuntimeException('OpenAI image response missing data array');
        }

        if (!empty($data['b64_json'])) {
            $decoded = base64_decode($data['b64_json'], true);
            if ($decoded === false) {
                throw new RuntimeException('Unable to decode OpenAI image payload');
            }

            return $decoded;
        }

        if (!empty($data['url'])) {
            return $this->downloadImageFromUrl($data['url']);
        }

        $fields = implode(', ', array_keys($data));
        throw new RuntimeException(sprintf(
            'OpenAI image response missing content (found fields: %s)',
            $fields !== '' ? $fields : 'none'
        ));
    }

    private function downloadImageFromUrl(string $url): string
    {
        $context = stream_context_create([
            'http' => [
                'timeout' => 20,
                'method' => 'GET',
                'header' => "User-Agent: Content Bot Blog Engine\r\n",
            ],
            'ssl' => [
                'verify_peer' => true,
                'verify_peer_name' => true,
            ],
        ]);

        $data = @file_get_contents($url, false, $context);
        if ($data === false) {
            throw new RuntimeException('Unable to download OpenAI image URL');
        }

        return $data;
    }

    private function request(string $url, array $payload): array
    {
        $ch = curl_init($url);
        $json = json_encode($payload, JSON_THROW_ON_ERROR);

        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => [
                'Authorization: Bearer ' . $this->apiKey,
                'Content-Type: application/json',
            ],
            CURLOPT_POSTFIELDS => $json,
            CURLOPT_TIMEOUT => 60,
        ]);

        $response = curl_exec($ch);
        $errno = curl_errno($ch);
        $error = curl_error($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($errno !== 0) {
            throw new RuntimeException(sprintf('OpenAI request failed: %s (%d)', $error, $errno));
        }

        if ($response === false) {
            throw new RuntimeException('OpenAI request returned no response');
        }

        $decoded = json_decode($response, true);
        if (!is_array($decoded)) {
            throw new RuntimeException('OpenAI response could not be decoded');
        }

        if ($status >= 400) {
            $message = $decoded['error']['message'] ?? 'Unknown error';
            if (stripos($message, 'json_schema') !== false) {
                Logger::log('error', 'OpenAI json_schema schema error: ' . $message);
            }
            throw new RuntimeException(sprintf('OpenAI API error (%d): %s', $status, $message));
        }

        return $decoded;
    }
}
