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

import Testing
import Foundation
@testable import Confab

@Suite("ConversationExportService")
struct ConversationExportServiceTests {

    // MARK: - Markdown

    @Test("Markdown format matches the established User/Assistant + --- convention")
    func markdownFormat() {
        let messages = [
            Message(role: .user, content: "hi"),
            Message(role: .assistant, content: "hello there"),
        ]
        let result = ConversationExportService.markdown(messages: messages)
        #expect(result == "**User**\n\nhi\n\n---\n\n**Assistant**\n\nhello there")
    }

    // MARK: - HTML escaping

    @Test("HTML-escapes angle brackets and ampersands instead of rendering them as markup")
    func escapesHTMLSpecialCharacters() {
        let messages = [Message(role: .assistant, content: "<script>alert('x')</script> & more")]
        let html = ConversationExportService.html(name: "Test", messages: messages)
        #expect(!html.contains("<script>alert"))
        #expect(html.contains("&lt;script&gt;"))
        #expect(html.contains("&amp;"))
    }

    @Test("HTML document includes the conversation name as title and heading")
    func includesConversationName() {
        let messages = [Message(role: .user, content: "hi")]
        let html = ConversationExportService.html(name: "My <Chat>", messages: messages)
        #expect(html.contains("<title>My &lt;Chat&gt;</title>"))
        #expect(html.contains("<h1>My &lt;Chat&gt;</h1>"))
    }

    // MARK: - Block-level markdown rendering

    @Test("Renders a header line as an <h1> tag")
    func rendersHeader() {
        let body = ConversationExportService.renderMarkdownBody("# Title")
        #expect(body.contains("<h1>Title</h1>"))
    }

    @Test("Renders a fenced code block with a language class, without applying inline formatting inside it")
    func rendersFencedCodeBlock() {
        let body = ConversationExportService.renderMarkdownBody("```swift\nlet x = **not bold**\n```")
        #expect(body.contains("<pre><code class=\"language-swift\">"))
        #expect(body.contains("let x = **not bold**"))
        #expect(!body.contains("<strong>not bold</strong>"))
    }

    @Test("Renders an unordered list as <ul><li> items")
    func rendersUnorderedList() {
        let body = ConversationExportService.renderMarkdownBody("- one\n- two")
        #expect(body.contains("<ul>"))
        #expect(body.contains("<li>one</li>"))
        #expect(body.contains("<li>two</li>"))
    }

    @Test("Renders an ordered list as <ol><li> items")
    func rendersOrderedList() {
        let body = ConversationExportService.renderMarkdownBody("1. first\n2. second")
        #expect(body.contains("<ol>"))
        #expect(body.contains("<li>first</li>"))
        #expect(body.contains("<li>second</li>"))
    }

    @Test("Renders a horizontal rule as <hr>")
    func rendersHorizontalRule() {
        let body = ConversationExportService.renderMarkdownBody("above\n\n---\n\nbelow")
        #expect(body.contains("<hr>"))
    }

    @Test("Renders a plain paragraph wrapped in <p>")
    func rendersParagraph() {
        let body = ConversationExportService.renderMarkdownBody("just some text")
        #expect(body.contains("<p>just some text</p>"))
    }

    @Test("Renders inline bold, italic, and inline code within a paragraph")
    func rendersInlineFormatting() {
        let body = ConversationExportService.renderMarkdownBody("**bold** and *italic* and `code`")
        #expect(body.contains("<strong>bold</strong>"))
        #expect(body.contains("<em>italic</em>"))
        #expect(body.contains("<code>code</code>"))
    }

    @Test("Does not apply bold/italic formatting to asterisks inside an inline code span")
    func inlineCodeProtectsItsContentFromOtherInlineFormatting() {
        let body = ConversationExportService.renderMarkdownBody("`**not bold**`")
        #expect(body.contains("<code>**not bold**</code>"))
        #expect(!body.contains("<strong>"))
    }

    // MARK: - Tables

    @Test("Renders a GFM pipe table as a proper HTML table, not raw pipe text")
    func rendersPipeTable() {
        let markdown = "| Name | Age |\n| --- | --- |\n| Alice | 30 |\n| Bob | 25 |"
        let body = ConversationExportService.renderMarkdownBody(markdown)
        #expect(body.contains("<table>"))
        #expect(body.contains("<th>Name</th>"))
        #expect(body.contains("<th>Age</th>"))
        #expect(body.contains("<td>Alice</td>"))
        #expect(body.contains("<td>30</td>"))
        #expect(body.contains("<td>Bob</td>"))
        #expect(!body.contains("|---|"))
        #expect(!body.contains("| --- |"))
    }

    @Test("Renders table column alignment from the separator row's colons")
    func rendersTableAlignment() {
        let markdown = "| Left | Center | Right |\n| :--- | :---: | ---: |\n| a | b | c |"
        let body = ConversationExportService.renderMarkdownBody(markdown)
        #expect(body.contains("text-align:left"))
        #expect(body.contains("text-align:center"))
        #expect(body.contains("text-align:right"))
    }

    @Test("A line containing a pipe with no valid separator row after it is not treated as a table")
    func doesNotTreatOrdinaryPipeTextAsTable() {
        let body = ConversationExportService.renderMarkdownBody("run `cmd1 | cmd2` to pipe output")
        #expect(!body.contains("<table>"))
    }
}
