import Foundation
import SwiftData
import LLMkit
import os

struct MistralProvider: CloudProvider {
    let modelProvider: ModelProvider = .mistral
    let providerKey: String = "Mistral"
    let languageCodes: [String]? = ["ar", "de", "en", "es", "fr", "hi", "it", "ja", "ko", "nl", "pt", "ru", "zh"]
    let includesAutoDetect: Bool = true

    private static let logger = Logger(subsystem: "com.noaber.proat", category: "MistralProvider")

    var models: [CloudModel] {[
        CloudModel(
            name: "voxtral-mini-latest",
            displayName: "Voxtral (Mistral)",
            description: "Mistral's Voxtral model for fast and accurate transcription",
            provider: .mistral,
            speed: 0.99,
            accuracy: 0.98,
            isMultilingual: true,
            // Streaming uit: LLMkit's streaming-client verzendt language/vocabulary
            // niet, dus biasing + taal-forceren werken alleen via de batch-pad.
            supportsStreaming: false,
            supportedLanguages: LanguageDictionary.forProvider(isMultilingual: true, provider: .mistral)
        )
    ]}

    func transcribe(audioData: Data, fileName: String, apiKey: String, model: String, language: String?, prompt: String?, customVocabulary: [String]) async throws -> String {
        // Eigen multipart POST omdat LLMkit's client geen language/prompt doorgeeft.
        // Voxtral pakt zonder language hint vaak verkeerde taal bij korte uitingen.
        let url = URL(string: "https://api.mistral.ai/v1/audio/transcriptions")!
        let boundary = "Boundary-\(UUID().uuidString)"

        var body = Data()
        func appendField(_ name: String, _ value: String) {
            body.append("--\(boundary)\r\n".data(using: .utf8)!)
            body.append("Content-Disposition: form-data; name=\"\(name)\"\r\n\r\n".data(using: .utf8)!)
            body.append("\(value)\r\n".data(using: .utf8)!)
        }
        appendField("model", model)
        let sentLanguage: String
        if let language, language != "auto", !language.isEmpty {
            appendField("language", language)
            sentLanguage = language
        } else {
            sentLanguage = "(auto/none)"
        }
        let biasedPrompt = Self.composePrompt(userPrompt: prompt, vocabulary: customVocabulary)
        if let biasedPrompt {
            appendField("prompt", biasedPrompt)
        }
        ProatDebugLog.write("BATCH transcribe | model=\(model) | language=\(sentLanguage) | vocab_in=\(customVocabulary.count) | prompt_len=\(biasedPrompt?.count ?? 0) | prompt_head=\(String((biasedPrompt ?? "").prefix(160)))")
        body.append("--\(boundary)\r\n".data(using: .utf8)!)
        body.append("Content-Disposition: form-data; name=\"file\"; filename=\"\(fileName)\"\r\n".data(using: .utf8)!)
        body.append("Content-Type: audio/wav\r\n\r\n".data(using: .utf8)!)
        body.append(audioData)
        body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
        request.setValue(apiKey, forHTTPHeaderField: "x-api-key")
        // 45s i.p.v. 120s: bij een stilgevallen Voxtral-connectie hing de UI tot
        // 2 minuten op "transcribing" zonder feedback. 45s is ruim voor lange
        // dictaties maar detecteert een stall snel genoeg.
        request.timeoutInterval = 45

        let data: Data
        let response: URLResponse
        do {
            (data, response) = try await URLSession.shared.upload(for: request, from: body)
        } catch {
            // Netwerk-/timeoutfout werd voorheen niet gelogd (alleen HTTP-fouten),
            // waardoor een hang onzichtbaar was in de debug-log.
            ProatDebugLog.write("BATCH netwerkfout | \(error.localizedDescription)")
            throw error
        }
        guard let http = response as? HTTPURLResponse else {
            throw NSError(domain: "MistralProvider", code: -1, userInfo: [NSLocalizedDescriptionKey: "No HTTP response"])
        }
        guard (200..<300).contains(http.statusCode) else {
            let msg = String(data: data, encoding: .utf8) ?? "HTTP \(http.statusCode)"
            ProatDebugLog.write("BATCH error HTTP \(http.statusCode): \(msg.prefix(200))")
            throw NSError(domain: "MistralProvider", code: http.statusCode, userInfo: [NSLocalizedDescriptionKey: msg])
        }
        struct Resp: Decodable { let text: String }
        let decoded = try JSONDecoder().decode(Resp.self, from: data)
        ProatDebugLog.write("BATCH result | chars=\(decoded.text.count) | text=\(String(decoded.text.prefix(200)))")
        return decoded.text
    }

    /// Bouwt de Voxtral biasing-prompt: gebruikersprompt + woordenboektermen.
    /// Voxtral kent geen vocabulary-veld, dus eigennamen gaan via de prompt-context.
    /// Afgekapt op een veilig teken-budget (~ Whisper-familie 224-token context).
    static func composePrompt(userPrompt: String?, vocabulary: [String]) -> String? {
        let terms = vocabulary
            .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
            .filter { !$0.isEmpty }

        // Prioriteer langere / meerwoordige eigennamen + termen met hoofdletters
        // of punten (merknamen zoals noaber.ai, BubbelBrein) — die hoort Voxtral
        // het vaakst verkeerd. Alfabetische volgorde was juist de slechtste keuze.
        let prioritized = terms.sorted { a, b in
            func score(_ s: String) -> Int {
                var n = 0
                if s.contains(" ") { n += 2 }            // meerwoordig
                if s.contains(".") { n += 2 }            // domein-achtig (noaber.ai)
                if s.range(of: "[A-Z].*[A-Z]", options: .regularExpression) != nil { n += 1 } // camel/merk
                return n
            }
            return score(a) > score(b)
        }

        guard !prioritized.isEmpty else {
            let base = (userPrompt ?? "").trimmingCharacters(in: .whitespacesAndNewlines)
            return base.isEmpty ? nil : base
        }

        // Budget ruim genoeg voor de hele pack-lijst (pack v7: ~326 termen,
        // ~3950 tekens). Generieke gebruikersprompt ("Hallo, hoe gaat het?")
        // bewust NIET meegenomen: dat is ruis en verdringt eigennamen.
        let vocabBudget = DictionaryPack.biasBudgetChars
        var picked: [String] = []
        var used = 0
        for term in prioritized {
            let cost = term.count + 2
            if used + cost > vocabBudget { break }
            picked.append(term)
            used += cost
        }

        // Natuurlijke NL-zin als context werkt beter voor Whisper-familie
        // biasing dan een kale lijst met label.
        let composed = "Transcriptie in het Nederlands. Veelgebruikte eigennamen, merknamen en termen: " + picked.joined(separator: ", ") + "."
        logger.notice("Voxtral prompt biased met \(picked.count, privacy: .public)/\(terms.count, privacy: .public) termen")
        return composed
    }

    func makeStreamingProvider(modelContext: ModelContext) -> (any StreamingTranscriptionProvider)? {
        MistralStreamingProvider()
    }

    func verifyAPIKey(_ key: String) async -> (isValid: Bool, errorMessage: String?) {
        return await MistralTranscriptionClient.verifyAPIKey(key)
    }
}
