//
//  OpenAIProviderTests.swift
//  oAITests
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen

import Testing
import Foundation
@testable import Confab

@Suite("OpenAIProvider request/response conversion")
struct OpenAIProviderTests {

    private var provider: OpenAIProvider { OpenAIProvider(apiKey: "test-key") }

    // MARK: - buildURLRequest

    @Test("Sets the Authorization header and JSON content type")
    func setsAuthAndContentTypeHeaders() throws {
        let request = ChatRequest(messages: [Message(role: .user, content: "hi")], model: "gpt-4o")
        let urlRequest = try provider.buildURLRequest(from: request, stream: false)
        #expect(urlRequest.value(forHTTPHeaderField: "Authorization") == "Bearer test-key")
        #expect(urlRequest.value(forHTTPHeaderField: "Content-Type") == "application/json")
        #expect(urlRequest.value(forHTTPHeaderField: "Accept") == nil)
    }

    @Test("Streaming requests add an SSE Accept header and stream_options")
    func streamingAddsAcceptHeaderAndUsageOption() throws {
        let request = ChatRequest(messages: [Message(role: .user, content: "hi")], model: "gpt-4o")
        let urlRequest = try provider.buildURLRequest(from: request, stream: true)
        #expect(urlRequest.value(forHTTPHeaderField: "Accept") == "text/event-stream")

        let body = try JSONSerialization.jsonObject(with: urlRequest.httpBody!) as! [String: Any]
        let streamOptions = body["stream_options"] as? [String: Any]
        #expect(streamOptions?["include_usage"] as? Bool == true)
    }

    @Test("o1/o3 reasoning models omit temperature even when one is requested")
    func reasoningModelsOmitTemperature() throws {
        let request = ChatRequest(messages: [Message(role: .user, content: "hi")], model: "o1-preview", temperature: 0.7)
        let urlRequest = try provider.buildURLRequest(from: request, stream: false)
        let body = try JSONSerialization.jsonObject(with: urlRequest.httpBody!) as! [String: Any]
        #expect(body["temperature"] == nil)
    }

    @Test("Non-reasoning models include the requested temperature")
    func nonReasoningModelsIncludeTemperature() throws {
        let request = ChatRequest(messages: [Message(role: .user, content: "hi")], model: "gpt-4o", temperature: 0.7)
        let urlRequest = try provider.buildURLRequest(from: request, stream: false)
        let body = try JSONSerialization.jsonObject(with: urlRequest.httpBody!) as! [String: Any]
        #expect(body["temperature"] as? Double == 0.7)
    }

    @Test("A message with an image attachment produces multi-part vision-format content")
    func imageAttachmentProducesMultipartContent() throws {
        let attachment = FileAttachment(path: "photo.png", type: .image, data: Data([0x01]))
        let message = Message(role: .user, content: "look at this", attachments: [attachment])
        let request = ChatRequest(messages: [message], model: "gpt-4o")
        let urlRequest = try provider.buildURLRequest(from: request, stream: false)
        let body = try JSONSerialization.jsonObject(with: urlRequest.httpBody!) as! [String: Any]
        let messages = body["messages"] as? [[String: Any]]
        let content = messages?.first?["content"] as? [[String: Any]]
        #expect(content?.count == 2)
        #expect(content?.first?["type"] as? String == "text")
        #expect(content?.last?["type"] as? String == "image_url")
    }

    // MARK: - convertToChatResponse

    @Test("Returns an empty-content response when there are no choices, rather than throwing")
    func convertToChatResponseEmptyChoicesFallback() {
        let apiResponse = OpenRouterChatResponse(id: "x", model: "m", choices: [], usage: nil, created: 0)
        let response = provider.convertToChatResponse(apiResponse)
        #expect(response.content == "")
        #expect(response.role == "assistant")
    }

    // MARK: - fallbackModels

    @Test("Fallback models are non-empty, all support tools, and are sorted by name")
    func fallbackModelsAreWellFormed() {
        let models = provider.fallbackModels()
        #expect(!models.isEmpty)
        #expect(models.allSatisfy { $0.capabilities.tools == true })
        #expect(models.allSatisfy { $0.capabilities.online == false })
        #expect(models.map(\.name) == models.map(\.name).sorted())
    }
}
