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

import Testing
import Foundation
@testable import Confab

@Suite("OpenRouterModels decoding")
struct OpenRouterModelsTests {

    // MARK: - Request-side: APIMessage.MessageContent (string-or-array union)

    @Test("Request message content decodes a plain string")
    func requestContentPlainString() throws {
        let json = #"{"role":"user","content":"hello"}"#.data(using: .utf8)!
        let message = try JSONDecoder().decode(OpenRouterChatRequest.APIMessage.self, from: json)
        guard case .string(let text) = message.content else {
            Issue.record("Expected .string case")
            return
        }
        #expect(text == "hello")
    }

    @Test("Request message content decodes an array of content items")
    func requestContentArray() throws {
        let json = #"{"role":"user","content":[{"type":"text","text":"hi"}]}"#.data(using: .utf8)!
        let message = try JSONDecoder().decode(OpenRouterChatRequest.APIMessage.self, from: json)
        guard case .array(let items) = message.content else {
            Issue.record("Expected .array case")
            return
        }
        #expect(items.count == 1)
    }

    // MARK: - Request-side: ContentItem (text / image union, with string fallback)

    @Test("ContentItem decodes a text block")
    func contentItemText() throws {
        let json = #"{"type":"text","text":"hi there"}"#.data(using: .utf8)!
        let item = try JSONDecoder().decode(OpenRouterChatRequest.APIMessage.ContentItem.self, from: json)
        guard case .text(let text) = item else {
            Issue.record("Expected .text case")
            return
        }
        #expect(text == "hi there")
    }

    @Test("ContentItem decodes an image_url block")
    func contentItemImage() throws {
        let json = #"{"type":"image_url","image_url":{"url":"https://example.com/x.png"}}"#.data(using: .utf8)!
        let item = try JSONDecoder().decode(OpenRouterChatRequest.APIMessage.ContentItem.self, from: json)
        guard case .image(let image) = item else {
            Issue.record("Expected .image case")
            return
        }
        #expect(image.imageUrl.url == "https://example.com/x.png")
    }

    @Test("ContentItem falls back to .text for a bare string")
    func contentItemBareStringFallback() throws {
        let json = #""just text""#.data(using: .utf8)!
        let item = try JSONDecoder().decode(OpenRouterChatRequest.APIMessage.ContentItem.self, from: json)
        guard case .text(let text) = item else {
            Issue.record("Expected .text fallback case")
            return
        }
        #expect(text == "just text")
    }

    // MARK: - Response-side: Choice.MessageContent (plain string vs content-block array)

    @Test("Response message content decodes a plain string with no image blocks")
    func responseContentPlainString() throws {
        let json = #"{"role":"assistant","content":"the answer"}"#.data(using: .utf8)!
        let content = try JSONDecoder().decode(OpenRouterChatResponse.Choice.MessageContent.self, from: json)
        #expect(content.content == "the answer")
        #expect(content.contentBlockImages.isEmpty)
    }

    @Test("Response message content extracts text and images from a content-block array")
    func responseContentBlockArrayWithImage() throws {
        let json = """
        {
            "role": "assistant",
            "content": [
                {"type": "text", "text": "here you go"},
                {"type": "image_url", "image_url": {"url": "https://example.com/out.png"}}
            ]
        }
        """.data(using: .utf8)!
        let content = try JSONDecoder().decode(OpenRouterChatResponse.Choice.MessageContent.self, from: json)
        #expect(content.content == "here you go")
        #expect(content.contentBlockImages.count == 1)
        #expect(content.contentBlockImages.first?.imageUrl.url == "https://example.com/out.png")
    }

    @Test("Response message content with no content field at all decodes to nil content, no images")
    func responseContentMissing() throws {
        let json = #"{"role":"assistant"}"#.data(using: .utf8)!
        let content = try JSONDecoder().decode(OpenRouterChatResponse.Choice.MessageContent.self, from: json)
        #expect(content.content == nil)
        #expect(content.contentBlockImages.isEmpty)
    }

    // MARK: - Streaming: StreamChoice.Delta (same string-or-block-array shape, streamed)

    @Test("Stream delta decodes a plain string content chunk")
    func streamDeltaPlainString() throws {
        let json = #"{"role":"assistant","content":"partial"}"#.data(using: .utf8)!
        let delta = try JSONDecoder().decode(OpenRouterStreamChunk.StreamChoice.Delta.self, from: json)
        #expect(delta.content == "partial")
        #expect(delta.contentBlockImages.isEmpty)
    }

    @Test("Stream delta extracts images from a content-block array")
    func streamDeltaContentBlockArrayWithImage() throws {
        let json = """
        {
            "content": [
                {"type": "image_url", "image_url": {"url": "https://example.com/stream.png"}}
            ]
        }
        """.data(using: .utf8)!
        let delta = try JSONDecoder().decode(OpenRouterStreamChunk.StreamChoice.Delta.self, from: json)
        #expect(delta.contentBlockImages.count == 1)
        #expect(delta.contentBlockImages.first?.imageUrl.url == "https://example.com/stream.png")
    }

    // MARK: - Model list: ModelData.created (release date)

    @Test("ModelData decodes a created timestamp when present")
    func modelDataDecodesCreated() throws {
        let json = """
        {
            "id": "anthropic/claude-sonnet-4-5",
            "name": "Claude Sonnet 4.5",
            "description": null,
            "context_length": 200000,
            "pricing": {"prompt": "0.000003", "completion": "0.000015"},
            "created": 1735689600
        }
        """.data(using: .utf8)!
        let model = try JSONDecoder().decode(OpenRouterModelsResponse.ModelData.self, from: json)
        #expect(model.created == 1735689600)
    }

    @Test("ModelData decodes to nil created when the field is absent")
    func modelDataMissingCreated() throws {
        let json = """
        {
            "id": "some/model",
            "name": "Some Model",
            "description": null,
            "context_length": 4096,
            "pricing": {"prompt": "0", "completion": "0"}
        }
        """.data(using: .utf8)!
        let model = try JSONDecoder().decode(OpenRouterModelsResponse.ModelData.self, from: json)
        #expect(model.created == nil)
    }
}
