<?php

declare(strict_types=1);

namespace SeoScope\Services;

use RuntimeException;

/**
 * Lightweight API reachability check for configured AI providers (no insight generation).
 */
final class AiConnectionTestService
{
    private const OPENAI_MODELS_URL = 'https://api.openai.com/v1/models';

    private const ANTHROPIC_MESSAGES_URL = 'https://api.anthropic.com/v1/messages';

    private const ANTHROPIC_VERSION = '2023-06-01';

    public function __construct(
        private AiSettingsService $settings
    ) {}

    /**
     * @param array<string, mixed> $overrides Optional keys from settings form: ai_provider, openai_api_key, anthropic_api_key, openai_model, anthropic_model
     * @return array{ok:bool,message:string,provider:string,model:?string}
     */
    public function test(array $overrides = []): array
    {
        if (!extension_loaded('curl')) {
            return [
                'ok' => false,
                'message' => __('settings.ai.test_no_curl'),
                'provider' => '',
                'model' => null,
            ];
        }

        $provider = $this->settings->normalizeProviderPublic(
            (string) ($overrides['ai_provider'] ?? $this->settings->provider())
        );

        $apiKey = $this->resolveApiKey($provider, $overrides);
        if ($apiKey === null) {
            return [
                'ok' => false,
                'message' => __('settings.ai.test_missing_key'),
                'provider' => $provider,
                'model' => null,
            ];
        }

        if ($provider === AiSettingsService::PROVIDER_ANTHROPIC) {
            $model = $this->resolveAnthropicModel($overrides);

            return $this->testAnthropic($apiKey, $model, $provider);
        }

        $model = $this->resolveOpenaiModel($overrides);

        return $this->testOpenai($apiKey, $model, $provider);
    }

    /**
     * @param array<string, mixed> $overrides
     */
    private function resolveApiKey(string $provider, array $overrides): ?string
    {
        $field = $provider === AiSettingsService::PROVIDER_ANTHROPIC
            ? 'anthropic_api_key'
            : 'openai_api_key';
        $settingKey = $provider === AiSettingsService::PROVIDER_ANTHROPIC
            ? AiSettingsService::KEY_ANTHROPIC_API_KEY
            : AiSettingsService::KEY_OPENAI_API_KEY;

        $submitted = trim((string) ($overrides[$field] ?? ''));
        if ($submitted !== '' && $submitted !== AiSettingsService::MASKED_SECRET) {
            return $submitted;
        }

        return $this->settings->apiKeyForProvider($provider);
    }

    /**
     * @param array<string, mixed> $overrides
     */
    private function resolveOpenaiModel(array $overrides): string
    {
        $m = trim((string) ($overrides['openai_model'] ?? ''));

        return $m !== '' ? $m : $this->settings->openaiModel();
    }

    /**
     * @param array<string, mixed> $overrides
     */
    private function resolveAnthropicModel(array $overrides): string
    {
        $m = trim((string) ($overrides['anthropic_model'] ?? ''));

        return $m !== '' ? $m : $this->settings->anthropicModel();
    }

    /**
     * @return array{ok:bool,message:string,provider:string,model:?string}
     */
    private function testOpenai(string $apiKey, string $model, string $provider): array
    {
        try {
            $res = $this->curl(
                self::OPENAI_MODELS_URL . '?' . http_build_query(['limit' => 1]),
                [
                    'Authorization: Bearer ' . $apiKey,
                ],
                null,
                'GET'
            );
        } catch (\Throwable $e) {
            return [
                'ok' => false,
                'message' => __('settings.ai.test_failed', ['detail' => $e->getMessage()]),
                'provider' => $provider,
                'model' => $model,
            ];
        }

        if ($res['status'] === 401 || $res['status'] === 403) {
            return [
                'ok' => false,
                'message' => __('settings.ai.test_auth_failed'),
                'provider' => $provider,
                'model' => $model,
            ];
        }

        if ($res['status'] < 200 || $res['status'] >= 300) {
            return [
                'ok' => false,
                'message' => __('settings.ai.test_http_failed', [
                    'status' => (string) $res['status'],
                    'detail' => $this->shortErrorDetail($res['body']),
                ]),
                'provider' => $provider,
                'model' => $model,
            ];
        }

        return [
            'ok' => true,
            'message' => __('settings.ai.test_ok_openai', ['model' => $model]),
            'provider' => $provider,
            'model' => $model,
        ];
    }

    /**
     * @return array{ok:bool,message:string,provider:string,model:?string}
     */
    private function testAnthropic(string $apiKey, string $model, string $provider): array
    {
        $payload = json_encode([
            'model' => $model,
            'max_tokens' => 1,
            'messages' => [
                ['role' => 'user', 'content' => 'ping'],
            ],
        ], JSON_THROW_ON_ERROR);

        try {
            $res = $this->curl(
                self::ANTHROPIC_MESSAGES_URL,
                [
                    'x-api-key: ' . $apiKey,
                    'anthropic-version: ' . self::ANTHROPIC_VERSION,
                    'Content-Type: application/json',
                ],
                $payload,
                'POST'
            );
        } catch (\Throwable $e) {
            return [
                'ok' => false,
                'message' => __('settings.ai.test_failed', ['detail' => $e->getMessage()]),
                'provider' => $provider,
                'model' => $model,
            ];
        }

        if ($res['status'] === 401 || $res['status'] === 403) {
            return [
                'ok' => false,
                'message' => __('settings.ai.test_auth_failed'),
                'provider' => $provider,
                'model' => $model,
            ];
        }

        if ($res['status'] < 200 || $res['status'] >= 300) {
            return [
                'ok' => false,
                'message' => __('settings.ai.test_http_failed', [
                    'status' => (string) $res['status'],
                    'detail' => $this->shortErrorDetail($res['body']),
                ]),
                'provider' => $provider,
                'model' => $model,
            ];
        }

        return [
            'ok' => true,
            'message' => __('settings.ai.test_ok_anthropic', ['model' => $model]),
            'provider' => $provider,
            'model' => $model,
        ];
    }

    /**
     * @param list<string> $headers
     * @return array{status:int,body:string}
     */
    private function curl(string $url, array $headers, ?string $body, string $method): array
    {
        $ch = curl_init($url);
        if ($ch === false) {
            throw new RuntimeException('Failed to initialize cURL.');
        }

        $opts = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER => $headers,
            CURLOPT_TIMEOUT => 25,
            CURLOPT_CONNECTTIMEOUT => 10,
        ];
        if (strtoupper($method) === 'POST') {
            $opts[CURLOPT_POST] = true;
            $opts[CURLOPT_POSTFIELDS] = $body ?? '';
        } else {
            $opts[CURLOPT_HTTPGET] = true;
        }

        curl_setopt_array($ch, $opts);
        $responseBody = curl_exec($ch);
        $err = curl_error($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        curl_close($ch);

        if ($responseBody === false) {
            throw new RuntimeException($err !== '' ? $err : 'Empty response from API.');
        }

        return ['status' => $status, 'body' => (string) $responseBody];
    }

    private function shortErrorDetail(string $body): string
    {
        $body = trim($body);
        if ($body === '') {
            return '';
        }
        $decoded = json_decode($body, true);
        if (is_array($decoded)) {
            $msg = $decoded['error']['message'] ?? $decoded['error']['type'] ?? $decoded['message'] ?? null;
            if (is_string($msg) && $msg !== '') {
                return mb_substr($msg, 0, 200);
            }
        }

        return mb_substr(preg_replace('/\s+/', ' ', $body) ?? $body, 0, 200);
    }
}
