"""Tests for the router.sx spec — client-side route matching.

Tests the bootstrapped Python router functions (from sx_ref.py) and
the SX page registry serialization (from helpers.py).
"""

import pytest
from shared.sx.ref import sx_ref


# ---------------------------------------------------------------------------
# split-path-segments
# ---------------------------------------------------------------------------

class TestSplitPathSegments:
    def test_simple(self):
        assert sx_ref.split_path_segments("/docs/hello") == ["docs", "hello"]

    def test_root(self):
        assert sx_ref.split_path_segments("/") == []

    def test_trailing_slash(self):
        assert sx_ref.split_path_segments("/docs/") == ["docs"]

    def test_no_leading_slash(self):
        assert sx_ref.split_path_segments("docs/hello") == ["docs", "hello"]

    def test_single_segment(self):
        assert sx_ref.split_path_segments("/about") == ["about"]

    def test_deep_path(self):
        assert sx_ref.split_path_segments("/a/b/c/d") == ["a", "b", "c", "d"]

    def test_empty(self):
        assert sx_ref.split_path_segments("") == []


# ---------------------------------------------------------------------------
# parse-route-pattern
# ---------------------------------------------------------------------------

class TestParseRoutePattern:
    def test_literal_only(self):
        result = sx_ref.parse_route_pattern("/docs/")
        assert len(result) == 1
        assert result[0]["type"] == "literal"
        assert result[0]["value"] == "docs"

    def test_param(self):
        result = sx_ref.parse_route_pattern("/docs/<slug>")
        assert len(result) == 2
        assert result[0] == {"type": "literal", "value": "docs"}
        assert result[1] == {"type": "param", "value": "slug"}

    def test_multiple_params(self):
        result = sx_ref.parse_route_pattern("/users/<uid>/posts/<pid>")
        assert len(result) == 4
        assert result[0]["type"] == "literal"
        assert result[1] == {"type": "param", "value": "uid"}
        assert result[2]["type"] == "literal"
        assert result[3] == {"type": "param", "value": "pid"}

    def test_root_pattern(self):
        result = sx_ref.parse_route_pattern("/")
        assert result == []


# ---------------------------------------------------------------------------
# match-route
# ---------------------------------------------------------------------------

class TestMatchRoute:
    def test_exact_match(self):
        params = sx_ref.match_route("/docs/", "/docs/")
        assert params is not None
        assert params == {}

    def test_param_match(self):
        params = sx_ref.match_route("/docs/components", "/docs/<slug>")
        assert params is not None
        assert params == {"slug": "components"}

    def test_no_match_different_length(self):
        result = sx_ref.match_route("/docs/a/b", "/docs/<slug>")
        assert result is sx_ref.NIL or result is None

    def test_no_match_literal_mismatch(self):
        result = sx_ref.match_route("/api/hello", "/docs/<slug>")
        assert result is sx_ref.NIL or result is None

    def test_root_match(self):
        params = sx_ref.match_route("/", "/")
        assert params is not None
        assert params == {}

    def test_multiple_params(self):
        params = sx_ref.match_route("/users/42/posts/7", "/users/<uid>/posts/<pid>")
        assert params is not None
        assert params == {"uid": "42", "pid": "7"}


# ---------------------------------------------------------------------------
# find-matching-route
# ---------------------------------------------------------------------------

class TestFindMatchingRoute:
    def _make_routes(self, patterns):
        """Build route entries like boot.sx does — with parsed patterns."""
        routes = []
        for name, pattern in patterns:
            route = {
                "name": name,
                "path": pattern,
                "parsed": sx_ref.parse_route_pattern(pattern),
                "has-data": False,
                "content": "(div \"test\")",
            }
            routes.append(route)
        return routes

    def test_first_match(self):
        routes = self._make_routes([
            ("home", "/"),
            ("docs-index", "/docs/"),
            ("docs-page", "/docs/<slug>"),
        ])
        match = sx_ref.find_matching_route("/docs/components", routes)
        assert match is not None
        assert match["name"] == "docs-page"
        assert match["params"] == {"slug": "components"}

    def test_exact_before_param(self):
        routes = self._make_routes([
            ("docs-index", "/docs/"),
            ("docs-page", "/docs/<slug>"),
        ])
        match = sx_ref.find_matching_route("/docs/", routes)
        assert match is not None
        assert match["name"] == "docs-index"

    def test_no_match(self):
        routes = self._make_routes([
            ("home", "/"),
            ("docs-page", "/docs/<slug>"),
        ])
        result = sx_ref.find_matching_route("/unknown/path", routes)
        assert result is sx_ref.NIL or result is None

    def test_root_match(self):
        routes = self._make_routes([
            ("home", "/"),
            ("about", "/about"),
        ])
        match = sx_ref.find_matching_route("/", routes)
        assert match is not None
        assert match["name"] == "home"

    def test_params_not_on_original(self):
        """find-matching-route should not mutate the original route entry."""
        routes = self._make_routes([("page", "/docs/<slug>")])
        match = sx_ref.find_matching_route("/docs/test", routes)
        assert match["params"] == {"slug": "test"}
        # Original should not have params key
        assert "params" not in routes[0]


# ---------------------------------------------------------------------------
# Page registry SX serialization
# ---------------------------------------------------------------------------

class TestBuildPagesSx:
    """Test the SX page registry format — serialize + parse round-trip."""

    def test_round_trip_simple(self):
        """SX dict literal round-trips through serialize → parse."""
        from shared.sx.helpers import _sx_literal
        from shared.sx.parser import parse_all

        # Build an SX dict literal like _build_pages_sx does
        entry = (
            "{:name " + _sx_literal("home")
            + " :path " + _sx_literal("/")
            + " :auth " + _sx_literal("public")
            + " :has-data false"
            + " :content " + _sx_literal("(~home-content)")
            + " :closure {}}"
        )

        parsed = parse_all(entry)
        assert len(parsed) == 1
        d = parsed[0]
        assert d["name"] == "home"
        assert d["path"] == "/"
        assert d["auth"] == "public"
        assert d["has-data"] is False
        assert d["content"] == "(~home-content)"
        assert d["closure"] == {}

    def test_round_trip_multiple(self):
        """Multiple SX dict literals parse as a list."""
        from shared.sx.helpers import _sx_literal
        from shared.sx.parser import parse_all

        entries = []
        for name, path in [("home", "/"), ("docs", "/docs/<slug>")]:
            entry = (
                "{:name " + _sx_literal(name)
                + " :path " + _sx_literal(path)
                + " :has-data false"
                + " :content " + _sx_literal("(div)")
                + " :closure {}}"
            )
            entries.append(entry)

        text = "\n".join(entries)
        parsed = parse_all(text)
        assert len(parsed) == 2
        assert parsed[0]["name"] == "home"
        assert parsed[1]["name"] == "docs"
        assert parsed[1]["path"] == "/docs/<slug>"

    def test_content_with_quotes(self):
        """Content expressions with quotes survive serialization."""
        from shared.sx.helpers import _sx_literal
        from shared.sx.parser import parse_all

        content = '(~docs/page :title "Hello \\"World\\"")'
        entry = (
            "{:name " + _sx_literal("test")
            + " :content " + _sx_literal(content)
            + " :closure {}}"
        )
        parsed = parse_all(entry)
        assert parsed[0]["content"] == content

    def test_closure_with_values(self):
        """Closure dict with various value types."""
        from shared.sx.helpers import _sx_literal
        from shared.sx.parser import parse_all

        entry = '{:name "test" :closure {:label "hello" :count 42 :active true}}'
        parsed = parse_all(entry)
        closure = parsed[0]["closure"]
        assert closure["label"] == "hello"
        assert closure["count"] == 42
        assert closure["active"] is True

    def test_has_data_true(self):
        """has-data true marks server-only pages."""
        from shared.sx.parser import parse_all

        entry = '{:name "analyzer" :path "/data" :has-data true :content "" :closure {}}'
        parsed = parse_all(entry)
        assert parsed[0]["has-data"] is True


# ---------------------------------------------------------------------------
# _sx_literal helper
# ---------------------------------------------------------------------------

class TestSxLiteral:
    def test_string(self):
        from shared.sx.helpers import _sx_literal
        assert _sx_literal("hello") == '"hello"'

    def test_string_with_quotes(self):
        from shared.sx.helpers import _sx_literal
        assert _sx_literal('say "hi"') == '"say \\"hi\\""'

    def test_string_with_newline(self):
        from shared.sx.helpers import _sx_literal
        assert _sx_literal("line1\nline2") == '"line1\\nline2"'

    def test_string_with_backslash(self):
        from shared.sx.helpers import _sx_literal
        assert _sx_literal("a\\b") == '"a\\\\b"'

    def test_int(self):
        from shared.sx.helpers import _sx_literal
        assert _sx_literal(42) == "42"

    def test_float(self):
        from shared.sx.helpers import _sx_literal
        assert _sx_literal(3.14) == "3.14"

    def test_bool_true(self):
        from shared.sx.helpers import _sx_literal
        assert _sx_literal(True) == "true"

    def test_bool_false(self):
        from shared.sx.helpers import _sx_literal
        assert _sx_literal(False) == "false"

    def test_none(self):
        from shared.sx.helpers import _sx_literal
        assert _sx_literal(None) == "nil"

    def test_empty_string(self):
        from shared.sx.helpers import _sx_literal
        assert _sx_literal("") == '""'
