// CssParserTest.kt
package com.aryan.reader.paginatedreader

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.em
import com.google.common.truth.Truth.assertThat
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class CssParserTest {

    @Test
    fun parseColor_handlesNamedColorsCorrectly() {
        assertThat(CssParser.parseColor("red")).isEqualTo(Color.Red)
        assertThat(CssParser.parseColor("black")).isEqualTo(Color.Black)
        assertThat(CssParser.parseColor("transparent")).isEqualTo(Color.Transparent)
    }

    @Test
    fun parseColor_handles3DigitHexCodes() {
        assertThat(CssParser.parseColor("#F0C")).isEqualTo(Color(0xFFFF00CC))
    }

    @Test
    fun parseColor_handles6DigitHexCodes() {
        assertThat(CssParser.parseColor("#FF00CC")).isEqualTo(Color(0xFFFF00CC))
    }

    @Test
    fun parseColor_handles8DigitHexCodes() {
        assertThat(CssParser.parseColor("#80FF00CC")).isEqualTo(Color(0x80FF00CC))
    }

    @Test
    fun parseColor_handlesRgbFunction() {
        assertThat(CssParser.parseColor("rgb(255, 0, 204)")).isEqualTo(Color(255, 0, 204))
    }

    @Test
    fun parseColor_handlesRgbaFunction() {
        assertThat(CssParser.parseColor("rgba(255, 0, 204, 0.5)")).isEqualTo(Color(255, 0, 204, 128))
    }

    @Test
    fun parseColor_returnsNullForInvalidInput() {
        assertThat(CssParser.parseColor("not a color")).isNull()
        assertThat(CssParser.parseColor("#12345")).isNull()
        assertThat(CssParser.parseColor("rgb(1,2)")).isNull()
    }

    private val dummyConstraints = androidx.compose.ui.unit.Constraints()
    private val baseFontSize = 16f
    private val density = 1f

    @Test
    fun parse_handlesSimpleRule() {
        val css = "p { color: red; }"
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        val rules = result.rules.byTag["p"]
        assertThat(rules).hasSize(1)
        assertThat(rules?.first()?.style?.spanStyle?.color).isEqualTo(Color.Red)
    }

    @Test
    fun parse_handlesMultipleSelectors() {
        val css = "h1, h2, h3 { font-weight: bold; }"
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        assertThat(result.rules.byTag["h1"]).hasSize(1)
        assertThat(result.rules.byTag["h2"]).hasSize(1)
        assertThat(result.rules.byTag["h3"]).hasSize(1)
        assertThat(result.rules.byTag["h1"]?.first()?.style?.spanStyle?.fontWeight).isEqualTo(FontWeight.Bold)
        assertThat(result.rules.byTag["h2"]?.first()?.style?.spanStyle?.fontWeight).isEqualTo(FontWeight.Bold)
        assertThat(result.rules.byTag["h3"]?.first()?.style?.spanStyle?.fontWeight).isEqualTo(FontWeight.Bold)
    }

    @Test
    fun parse_handlesImportantRules() {
        val css = "p { color: red !important; }"
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        val importantRule = result.rules.byTag["p"]?.find { it.selector.specificity >= 10000 }
        assertThat(importantRule).isNotNull()
        assertThat(importantRule!!.style.spanStyle.color).isEqualTo(Color.Red)
        val normalRule = result.rules.byTag["p"]?.find { it.selector.specificity < 10000 }
        assertThat(normalRule).isNull()
    }

    @Test
    fun parse_createsBothNormalAndImportantRulesWhenMixed() {
        val css = "p { color: blue; background-color: white !important; }"
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        val rules = result.rules.byTag["p"]
        assertThat(rules).hasSize(2)

        val importantRule = rules?.find { it.selector.specificity >= 10000 }
        assertThat(importantRule).isNotNull()
        assertThat(importantRule!!.style.blockStyle.backgroundColor).isEqualTo(Color.White)
        assertThat(importantRule.style.spanStyle.color.isSpecified).isFalse()

        val normalRule = rules.find { it.selector.specificity < 10000 }
        assertThat(normalRule).isNotNull()
        assertThat(normalRule!!.style.spanStyle.color).isEqualTo(Color.Blue)
        assertThat(normalRule.style.blockStyle.backgroundColor.isSpecified).isFalse()
    }

    @Test
    fun parse_extractsFontFaceRulesAndResolvesPath() {
        val css = """
        @font-face {
            font-family: "MyCustomFont";
            src: url("../fonts/myfont.ttf");
            font-weight: bold;
        }
        p { color: black; }
        """.trimIndent()
        val result = CssParser.parse(css, "/some/path/style.css", baseFontSize, density, dummyConstraints, isDarkTheme = false)
        assertThat(result.rules.byTag).containsKey("p")
        assertThat(result.fontFaces).hasSize(1)
        val fontFace = result.fontFaces.first()
        assertThat(fontFace.fontFamily).isEqualTo("mycustomfont")
        assertThat(fontFace.src).isEqualTo("/some/fonts/myfont.ttf")
        assertThat(fontFace.fontWeight).isEqualTo(FontWeight.Bold)
        assertThat(fontFace.fontStyle).isEqualTo(FontStyle.Normal)
    }

    @Test
    fun parse_handlesFontFaceWithDataUri() {
        val dataUri = "data:font/truetype;base64,AAEAAA..."
        val css = """
        @font-face {
            font-family: 'MyDataFont';
            src: url('$dataUri');
        }
        """.trimIndent()
        val result = CssParser.parse(css, "/css/style.css", baseFontSize, density, dummyConstraints, isDarkTheme = false)
        assertThat(result.fontFaces).hasSize(1)
        assertThat(result.fontFaces.first().src).isEqualTo(dataUri)
    }

    @Test
    fun parse_sanitizesPseudoClassesFromSelectors() {
        val css = "a:hover, p::first-line, button:focus { color: red; }"
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        assertThat(result.rules.byTag.keys).containsExactly("a", "p", "button")
    }

    @Test
    fun parse_calculatesSpecificityCorrectly() {
        val css = """
        #myId { color: red; } /* 100 */
        p.myClass { color: green; } /* 11 */
        p { color: blue; } /* 1 */
        div p { color: yellow; } /* 2 */
        """.trimIndent()
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        val idRule = result.rules.byId["myId"]?.first()
        val classRule = result.rules.otherComplex.find { it.selector.selector == "p.myClass" }
        val elementRule = result.rules.byTag["p"]?.first()
        val descendantRule = result.rules.otherComplex.find { it.selector.selector == "div p" }

        assertThat(idRule?.selector?.specificity).isEqualTo(100)
        assertThat(classRule?.selector?.specificity).isEqualTo(11)
        assertThat(elementRule?.selector?.specificity).isEqualTo(1)
        assertThat(descendantRule?.selector?.specificity).isEqualTo(2)
    }

    @Test
    fun parse_ignoresComments() {
        val css = """
        /* This is a comment */
        p {
            color: /* another comment */ blue; /* block comment */
        }
        """.trimIndent()
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        val rules = result.rules.byTag["p"]
        assertThat(rules).hasSize(1)
        assertThat(rules?.first()?.style?.spanStyle?.color).isEqualTo(Color.Blue)
    }

    @Test
    fun parse_handlesBorderShorthand() {
        val css = "div { border: 2px solid red; }"
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        val style = result.rules.byTag["div"]?.first()?.style?.blockStyle
        assertThat(style?.border).isNotNull()
        assertThat(style?.border?.width).isEqualTo(2.dp)
        assertThat(style?.border?.style).isEqualTo("solid")
        assertThat(style?.border?.color).isEqualTo(Color.Red)
    }

    @Test
    fun parse_handlesMarginAndPaddingShorthand() {
        val css = "p { margin: 10px 20px; padding: 1em 2em 3em 4em; }"
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        val style = result.rules.byTag["p"]?.first()?.style?.blockStyle
        assertThat(style?.margin?.top).isEqualTo(10.dp)
        assertThat(style?.margin?.right).isEqualTo(20.dp)
        assertThat(style?.margin?.bottom).isEqualTo(10.dp)
        assertThat(style?.margin?.left).isEqualTo(20.dp)

        assertThat(style?.padding?.top).isEqualTo(16.dp) // 1em
        assertThat(style?.padding?.right).isEqualTo(32.dp) // 2em
        assertThat(style?.padding?.bottom).isEqualTo(48.dp) // 3em
        assertThat(style?.padding?.left).isEqualTo(64.dp) // 4em
    }

    @Test
    fun parse_handlesFontSizeWithEmUnits() {
        val css = "p { font-size: 1.2em; }"
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        val style = result.rules.byTag["p"]?.first()?.style
        assertThat(style?.fontSize?.isEm).isTrue()
        assertThat(style?.fontSize?.value).isEqualTo(1.2f)
    }

    @Test
    fun parse_optimizationCategorizesRulesCorrectly() {
        val css = """
            p { color: blue; }
            .myClass { color: green; }
            #myId { color: red; }
            div > p { color: yellow; }
        """.trimIndent()
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        assertThat(result.rules.byTag).containsKey("p")
        assertThat(result.rules.byClass).containsKey("myClass")
        assertThat(result.rules.byId).containsKey("myId")
        assertThat(result.rules.otherComplex).hasSize(1)
        assertThat(result.rules.otherComplex.first().selector.selector).isEqualTo("div > p")
    }

    @Test
    fun parse_mediaQueryAppliesDarkThemeRules() {
        val css = """
            p { color: black; }
            @media (prefers-color-scheme: dark) {
                p { color: white; }
            }
        """.trimIndent()
        val lightResult = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        assertThat(lightResult.rules.byTag["p"]?.first()?.style?.spanStyle?.color).isEqualTo(Color.Black)

        val darkResult = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = true)
        assertThat(darkResult.rules.byTag["p"]?.last()?.style?.spanStyle?.color).isEqualTo(Color.White)
    }

    @Test
    fun parse_fontFaceSelectsPreferredSourceFormat() {
        val css = """
        @font-face {
            font-family: "MyFont";
            src: url("font.woff2") format("woff2"),
                 url("font.otf") format("opentype"),
                 url("font.ttf") format("truetype");
        }
        """.trimIndent()
        val result = CssParser.parse(css, "/css/style.css", baseFontSize, density, dummyConstraints, isDarkTheme = false)
        assertThat(result.fontFaces).hasSize(1)
        assertThat(result.fontFaces.first().src).isEqualTo("/css/font.otf")
    }

    @Test
    fun parse_propertiesHandlesVariousUnitsAndValues() {
        val css = """
            p {
                font-size: 150%;
                text-transform: uppercase;
                text-decoration: underline;
                text-align: center;
                page-break-inside: avoid;
                margin: 0 auto;
            }
        """.trimIndent()
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        val style = result.rules.byTag["p"]?.first()?.style
        assertThat(style?.fontSize).isEqualTo(1.5.em)
        assertThat(style?.textTransform).isEqualTo("uppercase")
        assertThat(style?.spanStyle?.textDecoration).isEqualTo(TextDecoration.Underline)
        assertThat(style?.paragraphStyle?.textAlign).isEqualTo(TextAlign.Center)
        assertThat(style?.blockStyle?.pageBreakInsideAvoid).isTrue()
        assertThat(style?.blockStyle?.horizontalAlign).isEqualTo("center")
    }

    @Test
    fun parse_themeAdaptationAdaptsColorsCorrectlyForDarkTheme() {
        val css = "p { color: #111; background-color: #EEE; }" // very dark text, very light bg
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = true)
        val style = result.rules.byTag["p"]?.first()?.style

        assertThat(style?.spanStyle?.color).isEqualTo(Color.White.copy(alpha = 0.87f))
        assertThat(style?.blockStyle?.backgroundColor).isEqualTo(Color.Transparent)
    }

    @Test
    fun parse_dataUriWithSemicolonParsesCorrectly() {
        val dataUri = "data:font/opentype;base64,d09GMgABAAAAAAPs...;something=else"
        val css = """
        @font-face {
            font-family: 'MyDataFont';
            src: url('$dataUri');
        }
        p { color: red; }
        """.trimIndent()
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        assertThat(result.fontFaces).hasSize(1)
        assertThat(result.fontFaces.first().src).isEqualTo(dataUri)
        assertThat(result.rules.byTag).containsKey("p")
    }

    @Test
    fun parse_textEmphasisParsesCorrectly() {
        val css = "p { -epub-text-emphasis-style: filled dot; -epub-text-emphasis-color: red; }"
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        val emphasis = result.rules.byTag["p"]?.first()?.style?.textEmphasis
        assertThat(emphasis).isNotNull()
        assertThat(emphasis?.style).isEqualTo("dot")
        assertThat(emphasis?.fill).isEqualTo("filled")
        assertThat(emphasis?.color).isEqualTo(Color.Red)
    }

    @Test
    fun parse_lineHeightClampsSmallEmValues() {
        val css = "p { line-height: 1.1; }" // This is treated as 1.1em
        val result = CssParser.parse(css, null, baseFontSize, density, dummyConstraints, isDarkTheme = false)
        val style = result.rules.byTag["p"]?.first()?.style
        assertThat(style?.paragraphStyle?.lineHeight).isEqualTo(2.0.em)
    }
}