"""Regression test: multiline string values in frontmatter no longer crash.

Root cause (before fix): when a frontmatter value contained literal newlines,
the custom YAML parser treated subsequent lines as new top-level keys.  A line
like ``1. Navigate **https://quarkle.ai**`` was parsed as a key, sanitised to
``1__Navigate___https___quarkle_ai__`` by ``_safe_ident``, and SQLite rejected
the ALTER TABLE because column names cannot start with a digit.

Defence-in-depth fixes:
1. ``_parse_yaml_subset`` skips lines whose "key" isn't a valid identifier.
2. ``_safe_ident`` prefixes digit-leading names with ``_``.
3. All dynamic column names are double-quoted in SQL (handles reserved words).
4. ``_reconstruct`` collapses newlines in string values to spaces.
"""

import pytest

from kaygee.core import KnowledgeGraph, _safe_ident, _parse_yaml_subset


MULTILINE_GOAL = (
    "**Goal:** Verify navigation and profile display\n"
    "\n"
    "### Steps\n"
    "1. Navigate **https://quarkle.ai**\n"
    "   \u2192 The Quarkle.ai landing page loads\n"
    "2. Click the profile icon\n"
    "   \u2192 The user profile modal appears"
)


# -- Original regression: multiline values in frontmatter ------------------

class TestMultilineFrontmatterCrash:
    def test_multiline_value_in_frontmatter_does_not_crash(self, kg):
        """Writing a node whose frontmatter value contains newlines must not
        crash with an OperationalError."""
        content = f"---\ntype: task\ngoal: {MULTILINE_GOAL}\n---\nBody text."
        kg.write("test-comment-user-profile", content)

        assert kg.exists("test-comment-user-profile")
        meta = kg.frontmatter("test-comment-user-profile")
        assert meta["type"] == "task"
        # The goal value should capture at least the first line
        assert "Verify navigation" in meta.get("goal", "")

    def test_sibling_entity_with_multiline_goal_does_not_poison_domain(self, kg):
        """A sibling with a multiline goal in the same domain walk must not
        crash the graph build."""
        bad_content = f"---\ntype: task\ngoal: {MULTILINE_GOAL}\n---\nBad node body."
        good_content = "---\ntype: task\ngoal: Simple single-line goal\n---\nGood node body."

        kg.write("test-comment-context-view", good_content)
        kg.write("test-comment-user-profile", bad_content)

        assert kg.exists("test-comment-context-view")
        assert kg.exists("test-comment-user-profile")

    def test_garbage_lines_not_parsed_as_keys(self):
        """Lines from multiline values that look like 'key: val' due to
        embedded colons (e.g. https:) should be skipped by the parser."""
        yaml_str = (
            "type: task\n"
            "goal: **Goal:** Verify navigation\n"
            "\n"
            "### Steps\n"
            "1. Navigate **https://quarkle.ai**\n"
            "2. Click the profile icon"
        )
        result = _parse_yaml_subset(yaml_str)
        assert "type" in result
        assert "goal" in result
        # These garbage "keys" must not appear
        assert not any(k.startswith("1") for k in result)
        assert not any("Navigate" in k for k in result)
        assert not any("Steps" in k for k in result)


# -- _safe_ident hardening ------------------------------------------------

class TestSafeIdentDigitPrefix:
    def test_digit_leading_name_gets_prefix(self):
        assert _safe_ident("1_foo") == "_1_foo"

    def test_normal_name_unchanged(self):
        assert _safe_ident("foo_bar") == "foo_bar"

    def test_sanitised_digit_prefix(self):
        """A key like '1. Navigate **https://quarkle.ai**' should produce a
        valid SQLite identifier even after sanitisation."""
        result = _safe_ident("1. Navigate **https://quarkle.ai**")
        assert result[0] == "_"
        assert result[1].isdigit()

    def test_empty_string_returns_empty(self):
        assert _safe_ident("") == ""


# -- SQLite reserved words as frontmatter keys ----------------------------

class TestReservedWordKeys:
    def test_sqlite_reserved_word_order(self, kg):
        kg.write("item", "---\ntype: task\norder: first\n---\nBody.")
        assert kg.frontmatter("item")["order"] == "first"

    def test_sqlite_reserved_word_group(self, kg):
        kg.write("item", "---\ntype: task\ngroup: alpha\n---\nBody.")
        assert kg.frontmatter("item")["group"] == "alpha"

    def test_sqlite_reserved_word_select(self, kg):
        kg.write("item", "---\ntype: task\nselect: yes\n---\nBody.")
        assert kg.frontmatter("item")["select"] == "yes"

    def test_multiple_reserved_words(self, kg):
        kg.write("item", "---\ntype: task\norder: 1\ngroup: b\nselect: c\nwhere: d\n---\nBody.")
        meta = kg.frontmatter("item")
        assert meta["order"] == "1"
        assert meta["group"] == "b"
        assert meta["select"] == "c"
        assert meta["where"] == "d"


# -- _reconstruct collapses newlines in values ----------------------------

class TestReconstructNewlineCollapse:
    def test_newlines_collapsed_to_spaces(self):
        result = KnowledgeGraph._reconstruct(
            {"type": "task", "goal": "line1\nline2\nline3"}, "Body."
        )
        assert "line1 line2 line3" in result
        # No bare newlines inside the frontmatter block
        fm_block = result.split("---")[1]
        for line in fm_block.strip().splitlines():
            assert ": " in line or line.strip() == ""

    def test_roundtrip_with_multiline_value(self, kg):
        """cat() output should be safe to pass back to write()."""
        kg.write("orig", "---\ntype: task\ngoal: simple goal\n---\nBody.")
        content = kg.cat("orig")
        kg.write("copy", content)
        assert kg.frontmatter("copy") == kg.frontmatter("orig")
        assert kg.body("copy") == kg.body("orig")
