<?php

declare(strict_types=1);

namespace App\Domain\Ai;

use App\Contracts\LlmProvider;
use App\Settings\AiSettings;
use Illuminate\Support\Facades\Http;
use RuntimeException;

class OpenAiCompatibleLlmProvider implements LlmProvider
{
    public function __construct(
        protected AiSettings $settings,
    ) {}

    public function complete(string $prompt, array $context = []): array
    {
        $title = (string) ($context['title'] ?? '');
        $format = (string) ($context['content_format'] ?? 'markdown');
        $formatHint = $format === 'html'
            ? 'Keep valid HTML (no markdown). Preserve existing tags where useful.'
            : 'Keep Markdown (no raw HTML unless already present). Preserve headings/lists/code fences.';

        $response = $this->chat([
            [
                'role' => 'system',
                'content' => implode("\n", [
                    'You are an editor polishing blog posts for LaraBlog (Chinese and English OK).',
                    'Improve clarity, flow, and grammar without inventing facts or changing the author\'s intent.',
                    $formatHint,
                    'Respond with JSON only (no markdown fences):',
                    '{"summary":"short overview","description":"SEO description <=160 chars","polished_content":"full polished body","suggestions":["tip1","tip2"]}',
                ]),
            ],
            [
                'role' => 'user',
                'content' => "Title: {$title}\nFormat: {$format}\n\n---\n{$prompt}",
            ],
        ], preferJsonObject: true);

        $content = data_get($response, 'choices.0.message.content');
        $decoded = is_string($content) ? $this->decodeJsonObject($content) : null;

        if (! is_array($decoded)) {
            throw new RuntimeException('LLM completion response is not valid JSON.');
        }

        $suggestions = $decoded['suggestions'] ?? [];
        if (! is_array($suggestions)) {
            $suggestions = [];
        }

        return [
            'summary' => (string) ($decoded['summary'] ?? ''),
            'description' => (string) ($decoded['description'] ?? ''),
            'polished_content' => (string) ($decoded['polished_content'] ?? $decoded['content'] ?? ''),
            'suggestions' => array_values(array_map('strval', $suggestions)),
        ];
    }

    public function moderate(string $content): array
    {
        $response = $this->chat([
            [
                'role' => 'system',
                'content' => 'You moderate Chinese and English blog comments for spam, ads, scams, abuse, and irrelevant promo. Respond with JSON only (no markdown): {"status":"approved|rejected|needs_human","reason":"..."}. Reject clear spam/ads; approve normal discussion; use needs_human when unsure.',
            ],
            [
                'role' => 'user',
                'content' => $content,
            ],
        ], preferJsonObject: true);

        $payload = data_get($response, 'choices.0.message.content');
        $decoded = is_string($payload) ? $this->decodeJsonObject($payload) : null;

        if (! is_array($decoded) || ! isset($decoded['status'])) {
            return [
                'status' => 'needs_human',
                'reason' => 'Unable to parse moderation response.',
            ];
        }

        $status = (string) $decoded['status'];
        if (! in_array($status, ['approved', 'rejected', 'needs_human'], true)) {
            $status = 'needs_human';
        }

        return [
            'status' => $status,
            'reason' => isset($decoded['reason']) ? (string) $decoded['reason'] : null,
        ];
    }

    /**
     * @param  list<array{role: string, content: string}>  $messages
     * @return array<string, mixed>
     */
    protected function chat(array $messages, bool $preferJsonObject = false): array
    {
        $payload = [
            'model' => $this->settings->model ?? 'gpt-4o-mini',
            'messages' => $messages,
        ];

        if ($preferJsonObject) {
            try {
                return $this->request($payload + [
                    'response_format' => ['type' => 'json_object'],
                ]);
            } catch (\Throwable) {
                // Some OpenAI-compatible gateways (e.g. Agnes) reject response_format.
            }
        }

        return $this->request($payload);
    }

    /**
     * @param  array<string, mixed>  $payload
     * @return array<string, mixed>
     */
    protected function request(array $payload): array
    {
        $baseUrl = rtrim($this->settings->api_base_url ?? 'https://api.openai.com/v1', '/');

        $response = Http::withToken((string) ($this->settings->api_key ?? ''))
            ->acceptJson()
            ->timeout(90)
            ->post("{$baseUrl}/chat/completions", $payload)
            ->throw()
            ->json();

        if (! is_array($response)) {
            throw new RuntimeException('LLM provider returned an invalid response.');
        }

        return $response;
    }

    /**
     * @return array<string, mixed>|null
     */
    protected function decodeJsonObject(string $content): ?array
    {
        $trimmed = trim($content);

        if (preg_match('/```(?:json)?\s*(\{.*?\})\s*```/s', $trimmed, $matches) === 1) {
            $trimmed = $matches[1];
        } elseif (preg_match('/\{.*\}/s', $trimmed, $matches) === 1) {
            $trimmed = $matches[0];
        }

        $decoded = json_decode($trimmed, true);

        return is_array($decoded) ? $decoded : null;
    }
}
