<?php

namespace Modules\AiCore\Entities\Services;

use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Http;
use Modules\AiCore\Entities\AiProvider;

/**
 * Generic voice → text transcription for the shared AiCore engine.
 *
 * Driver-agnostic: any provider exposing the OpenAI /v1/audio/transcriptions
 * API works (OpenAI, Groq, self-hosted whisper.cpp, …). Audio is forwarded
 * straight to the provider and never written to disk.
 *
 * Lifted from VetAI's VaiTranscriptionService::transcribe(), generalised to
 * take an {@see AiProvider} row. The (optional) domain-specific bias prompt —
 * e.g. veterinary vocabulary — is supplied BY THE CALLER, so this service stays
 * business-agnostic.
 */
class AiTranscriber
{
    /** Where each whisper-flavoured driver lives. */
    private const ENDPOINTS = [
        'whisper_openai'        => 'https://api.openai.com/v1',
        'whisper_groq'          => 'https://api.groq.com/openai/v1',
        'whisper_openai_compat' => null, // taken from base_url
    ];

    /** Default model per driver — overridable in settings. */
    private const DEFAULT_MODELS = [
        'whisper_openai'        => 'whisper-1',
        'whisper_groq'          => 'whisper-large-v3-turbo',
        'whisper_openai_compat' => 'whisper-large-v3-turbo',
    ];

    /**
     * @param  AiProvider   $settings
     * @param  UploadedFile $audio
     * @param  string|null  $languageHint  ISO-639-1 code (en, ar, …) or 'auto'
     * @param  string|null  $prompt        Whisper bias prompt (domain vocabulary).
     * @return array{text:string, duration_sec:?float, provider:string, model:string}
     */
    public static function transcribe(AiProvider $settings, UploadedFile $audio, ?string $languageHint = null, ?string $prompt = null): array
    {
        $cfg = $settings->transcriptionConfig();
        $provider = $cfg['provider'];

        if ($provider === 'webspeech') {
            // The browser handles webspeech client-side; the server should
            // never reach this path. Thrown as a safety net.
            throw new \RuntimeException('Web Speech runs client-side — call the transcribe endpoint only for server-side drivers.');
        }

        $base = $cfg['base_url'] ?: self::ENDPOINTS[$provider] ?? null;
        if (!$base) {
            throw new \RuntimeException("Transcription provider '{$provider}' has no base URL configured.");
        }
        $model = $cfg['model'] ?: (self::DEFAULT_MODELS[$provider] ?? 'whisper-1');
        $lang  = $languageHint ?: $cfg['language'];

        $finalPrompt = trim((string) $prompt);
        // Whisper caps the prompt at ~224 tokens; trim conservatively.
        if (mb_strlen($finalPrompt) > 800) {
            $finalPrompt = mb_substr($finalPrompt, 0, 800);
        }

        $response = Http::timeout((int) config('aicore.timeout', 120))
            ->withToken((string) $cfg['api_key'])
            ->attach('file', file_get_contents($audio->getRealPath()), $audio->getClientOriginalName() ?: 'audio.webm', [
                'Content-Type' => $audio->getMimeType() ?: 'audio/webm',
            ])
            ->asMultipart()
            ->post(rtrim($base, '/') . '/audio/transcriptions', array_filter([
                'model'           => $model,
                'language'        => ($lang && $lang !== 'auto') ? $lang : null,
                'prompt'          => $finalPrompt ?: null,
                'response_format' => 'json',
                'temperature'     => '0',
            ]));

        if (!$response->successful()) {
            throw new \RuntimeException('Transcription failed: ' . $response->status() . ' ' . $response->body());
        }
        $json = $response->json();

        return [
            'text'         => trim((string) ($json['text'] ?? '')),
            'duration_sec' => isset($json['duration']) ? (float) $json['duration'] : null,
            'provider'     => $provider,
            'model'        => $model,
        ];
    }
}
