package com.correx.infrastructure.tools.extract

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

class HtmlMarkdownExtractorTest {

    // minContentLength=1 isolates conversion correctness from the quality threshold.
    private val structure = HtmlMarkdownExtractor(minContentLength = 1)

    private fun md(html: String): String = structure.extract(html, "text/html").markdown

    // ---- structural strip (§5.1) ----

    @Test
    fun `strips script style nav footer and form`() {
        val out = md(
            """
            <html><body>
              <nav>Home About Contact</nav>
              <script>var x = 1;</script>
              <style>.a{color:red}</style>
              <article><p>Real content here.</p></article>
              <footer>Copyright 2026</footer>
              <form><input name="q"></form>
            </body></html>
            """.trimIndent(),
        )
        assertEquals("Real content here.", out)
        assertFalse(out.contains("Home About"), "nav leaked")
        assertFalse(out.contains("Copyright"), "footer leaked")
        assertFalse(out.contains("var x"), "script leaked")
    }

    @Test
    fun `removes nodes matched by class-id blocklist`() {
        val out = md(
            """
            <body>
              <div class="cookie-banner">Accept cookies</div>
              <div class="content"><p>The actual article body that matters.</p></div>
              <aside class="sidebar related">Related links junk</aside>
            </body>
            """.trimIndent(),
        )
        assertTrue(out.contains("actual article body"), "main content lost: $out")
        assertFalse(out.contains("Accept cookies"), "cookie banner leaked")
        assertFalse(out.contains("Related links"), "sidebar leaked")
    }

    // ---- density / main-content selection (§5.2) ----

    @Test
    fun `density picks prose over link-heavy chrome`() {
        val out = md(
            """
            <body>
              <div class="links-row"><a href="/1">L1</a><a href="/2">L2</a><a href="/3">L3</a></div>
              <div class="main-text"><p>A paragraph of genuine prose with enough words to win on text density.</p></div>
            </body>
            """.trimIndent(),
        )
        assertTrue(out.contains("genuine prose"), "prose not selected: $out")
        assertFalse(out.contains("[L1]"), "nav links selected over prose")
    }

    @Test
    fun `prefers semantic article element`() {
        val out = md("<body><div>noise</div><article><h2>Heading</h2><p>Body.</p></article></body>")
        assertTrue(out.startsWith("## Heading"), "article not preferred: $out")
    }

    // ---- markdown conversion ----

    @Test
    fun `headings render with hash level`() {
        assertEquals("### Deep", md("<article><h3>Deep</h3></article>"))
    }

    @Test
    fun `unordered and ordered lists`() {
        assertEquals("- a\n- b", md("<article><ul><li>a</li><li>b</li></ul></article>"))
        assertEquals("1. x\n2. y", md("<article><ol><li>x</li><li>y</li></ol></article>"))
    }

    @Test
    fun `nested list is indented`() {
        assertEquals(
            "- a\n  - b",
            md("<article><ul><li>a<ul><li>b</li></ul></li></ul></article>"),
        )
    }

    @Test
    fun `links emphasis and inline code`() {
        val link = """<a href="http://e.com">here</a>"""
        assertEquals("see [here](http://e.com)", md("<article><p>see $link</p></article>"))
        assertEquals("a **b** *c*", md("<article><p>a <strong>b</strong> <em>c</em></p></article>"))
        assertEquals("use `f()`", md("<article><p>use <code>f()</code></p></article>"))
    }

    @Test
    fun `pre becomes a fenced code block`() {
        assertEquals("```\nline1\nline2\n```", md("<article><pre><code>line1\nline2</code></pre></article>"))
    }

    @Test
    fun `blockquote and hr`() {
        assertEquals("> quoted", md("<article><blockquote>quoted</blockquote></article>"))
        assertEquals("a\n\n---\n\nb", md("<article><p>a</p><hr><p>b</p></article>"))
    }

    @Test
    fun `table renders header separator and body`() {
        val table = "<table><tr><th>A</th><th>B</th></tr><tr><td>1</td><td>2</td></tr></table>"
        val out = md("<article>$table</article>")
        assertEquals("| A | B |\n| --- | --- |\n| 1 | 2 |", out)
    }

    @Test
    fun `consecutive duplicate blocks are deduplicated`() {
        assertEquals("repeat", md("<article><p>repeat</p><p>repeat</p></article>"))
    }

    // ---- content-type dispatch (§5.3) ----

    @Test
    fun `json passes through unchanged`() {
        val r = structure.extract("""{"a":1}""", "application/json; charset=utf-8")
        assertEquals("""{"a":1}""", r.markdown)
        assertEquals(ExtractionQuality.OK, r.quality)
    }

    @Test
    fun `binary and media content types are rejected`() {
        for (ct in listOf("image/png", "application/pdf", "application/octet-stream", "video/mp4")) {
            val r = structure.extract("...bytes...", ct)
            assertEquals(ExtractionQuality.REJECTED, r.quality, "should reject $ct")
            assertTrue(r.markdown.isEmpty())
        }
    }

    @Test
    fun `null content type is treated as html best-effort`() {
        assertEquals("Hello.", structure.extract("<article><p>Hello.</p></article>", null).markdown)
    }

    // ---- quality threshold + version pinning (§5) ----

    @Test
    fun `short extraction is flagged low quality`() {
        val r = HtmlMarkdownExtractor().extract("<article><p>tiny</p></article>", "text/html")
        assertEquals(ExtractionQuality.LOW_QUALITY, r.quality)
        assertTrue(r.reason!!.contains("below minimum"))
    }

    @Test
    fun `sufficiently long extraction is ok`() {
        val body = "word ".repeat(60).trim()
        val r = HtmlMarkdownExtractor().extract("<article><p>$body</p></article>", "text/html")
        assertEquals(ExtractionQuality.OK, r.quality)
    }

    @Test
    fun `extractor version is pinned on every result`() {
        assertEquals("html-md-1", structure.extract("<p>x</p>", "text/html").extractorVersion)
        assertEquals("html-md-1", structure.extract("x", "image/png").extractorVersion)
    }
}
