"""Tests for MemGPT-style memory blocks."""

import json

import pytest

from autodebug.agent.memory import (
    BLOCK_LIMITS,
    VALID_BLOCK_NAMES,
    MemoryBlocks,
    load_blocks_from_disk,
    persist_blocks_to_disk,
    _parse_structured_memory,
    _summarize_findings,
)


class TestMemoryBlocks:
    def test_initial_blocks_empty(self):
        blocks = MemoryBlocks()
        for name in VALID_BLOCK_NAMES:
            assert blocks.get(name) == ""

    def test_insert_appends_content(self):
        blocks = MemoryBlocks()
        blocks.insert("session_state", "first")
        blocks.insert("session_state", "second")
        assert "first" in blocks.get("session_state")
        assert "second" in blocks.get("session_state")

    def test_insert_rejects_over_limit(self):
        blocks = MemoryBlocks()
        big = "x" * (BLOCK_LIMITS["user_preferences"] + 1)
        result = blocks.insert("user_preferences", big)
        assert "Error" in result
        assert blocks.get("user_preferences") == ""

    def test_insert_rejects_auto_block(self):
        blocks = MemoryBlocks()
        result = blocks.insert("active_findings", "test")
        assert "auto-populated" in result

    def test_insert_rejects_unknown_block(self):
        blocks = MemoryBlocks()
        result = blocks.insert("nonexistent", "test")
        assert "Error" in result

    def test_replace_targeted_edit(self):
        blocks = MemoryBlocks()
        blocks.rethink("project_context", "FastAPI backend with Supabase DB")
        blocks.replace("project_context", "Supabase DB", "PostgreSQL")
        assert "PostgreSQL" in blocks.get("project_context")
        assert "Supabase" not in blocks.get("project_context")

    def test_replace_old_content_not_found(self):
        blocks = MemoryBlocks()
        blocks.rethink("project_context", "hello world")
        result = blocks.replace("project_context", "missing text", "new")
        assert "Error" in result
        assert "not found" in result

    def test_replace_rejects_over_limit(self):
        blocks = MemoryBlocks()
        blocks.rethink("user_preferences", "short")
        big = "x" * (BLOCK_LIMITS["user_preferences"] + 1)
        result = blocks.replace("user_preferences", "short", big)
        assert "Error" in result

    def test_rethink_complete_rewrite(self):
        blocks = MemoryBlocks()
        blocks.rethink("session_state", "old content")
        blocks.rethink("session_state", "brand new content")
        assert blocks.get("session_state") == "brand new content"

    def test_rethink_rejects_over_limit(self):
        blocks = MemoryBlocks()
        big = "x" * (BLOCK_LIMITS["session_state"] + 1)
        result = blocks.rethink("session_state", big)
        assert "Error" in result

    def test_rethink_rejects_auto_block(self):
        blocks = MemoryBlocks()
        result = blocks.rethink("active_findings", "test")
        assert "auto-populated" in result

    def test_render_for_prompt_includes_all_blocks(self):
        blocks = MemoryBlocks()
        rendered = blocks.render_for_prompt()
        for name in VALID_BLOCK_NAMES:
            assert name in rendered

    def test_render_for_prompt_shows_content(self):
        blocks = MemoryBlocks()
        blocks.rethink("project_context", "My awesome project")
        rendered = blocks.render_for_prompt()
        assert "My awesome project" in rendered

    def test_render_for_prompt_shows_empty_marker(self):
        blocks = MemoryBlocks()
        rendered = blocks.render_for_prompt()
        assert "[empty]" in rendered

    def test_total_chars(self):
        blocks = MemoryBlocks()
        blocks.rethink("project_context", "hello")
        blocks.rethink("session_state", "world")
        assert blocks.total_chars() == 10

    def test_session_id_is_set(self):
        blocks = MemoryBlocks()
        assert len(blocks.session_id) > 0
        assert "-" in blocks.session_id  # UUID format

    def test_get_unknown_block_raises(self):
        blocks = MemoryBlocks()
        with pytest.raises(ValueError, match="Unknown block"):
            blocks.get("nonexistent")


class TestLoadBlocksFromDisk:
    def test_loads_structured_memory_md(self, tmp_path):
        hikaflow = tmp_path / ".hikaflow"
        hikaflow.mkdir()
        (hikaflow / "memory.md").write_text(
            "<!-- block:project_context -->\nFastAPI backend\n<!-- /block:project_context -->\n\n"
            "<!-- block:user_preferences -->\nverbose output\n<!-- /block:user_preferences -->"
        )
        blocks = load_blocks_from_disk(str(tmp_path))
        assert blocks.get("project_context") == "FastAPI backend"
        assert blocks.get("user_preferences") == "verbose output"

    def test_loads_legacy_freeform_memory_md(self, tmp_path):
        hikaflow = tmp_path / ".hikaflow"
        hikaflow.mkdir()
        (hikaflow / "memory.md").write_text("This is legacy freeform content.")
        blocks = load_blocks_from_disk(str(tmp_path))
        assert blocks.get("project_context") == "This is legacy freeform content."
        assert blocks.get("user_preferences") == ""

    def test_missing_memory_md_returns_empty_blocks(self, tmp_path):
        blocks = load_blocks_from_disk(str(tmp_path))
        for name in VALID_BLOCK_NAMES:
            assert blocks.get(name) == ""

    def test_auto_populates_active_findings(self, tmp_path):
        hikaflow = tmp_path / ".hikaflow"
        hikaflow.mkdir()
        findings = [
            {"severity": "HIGH", "issue_id": "B-01", "file": "core.py",
             "line": 42, "description": "Race condition in cache", "status": "open"},
            {"severity": "LOW", "issue_id": "B-02", "file": "utils.py",
             "line": 10, "description": "Unused import", "status": "fixed"},
        ]
        (hikaflow / "findings.json").write_text(json.dumps(findings))
        blocks = load_blocks_from_disk(str(tmp_path))
        af = blocks.get("active_findings")
        assert "B-01" in af
        assert "B-02" not in af  # fixed issues excluded

    def test_session_state_always_empty_on_load(self, tmp_path):
        hikaflow = tmp_path / ".hikaflow"
        hikaflow.mkdir()
        (hikaflow / "memory.md").write_text("some content")
        blocks = load_blocks_from_disk(str(tmp_path))
        assert blocks.get("session_state") == ""

    def test_caps_project_context_to_limit(self, tmp_path):
        hikaflow = tmp_path / ".hikaflow"
        hikaflow.mkdir()
        big = "x" * 5000
        (hikaflow / "memory.md").write_text(big)
        blocks = load_blocks_from_disk(str(tmp_path))
        assert len(blocks.get("project_context")) == BLOCK_LIMITS["project_context"]


class TestPersistBlocksToDisk:
    def test_writes_structured_memory_md(self, tmp_path):
        blocks = MemoryBlocks()
        blocks.rethink("project_context", "FastAPI backend")
        blocks.rethink("user_preferences", "verbose")
        persist_blocks_to_disk(blocks, str(tmp_path))
        content = (tmp_path / ".hikaflow" / "memory.md").read_text()
        assert "<!-- block:project_context -->" in content
        assert "FastAPI backend" in content
        assert "<!-- block:user_preferences -->" in content
        assert "verbose" in content

    def test_archives_session_state_to_jsonl(self, tmp_path):
        blocks = MemoryBlocks()
        blocks.rethink("session_state", "Analyzed auth flow")
        persist_blocks_to_disk(blocks, str(tmp_path))
        session_file = tmp_path / ".hikaflow" / "sessions" / f"{blocks.session_id}.jsonl"
        assert session_file.is_file()
        entry = json.loads(session_file.read_text().strip())
        assert entry["session_state"] == "Analyzed auth flow"

    def test_creates_hikaflow_dir_if_missing(self, tmp_path):
        blocks = MemoryBlocks()
        blocks.rethink("project_context", "test")
        persist_blocks_to_disk(blocks, str(tmp_path))
        assert (tmp_path / ".hikaflow").is_dir()

    def test_does_not_archive_empty_session_state(self, tmp_path):
        blocks = MemoryBlocks()
        persist_blocks_to_disk(blocks, str(tmp_path))
        sessions_dir = tmp_path / ".hikaflow" / "sessions"
        assert not sessions_dir.exists() or len(list(sessions_dir.iterdir())) == 0

    def test_roundtrip_structured_format(self, tmp_path):
        blocks = MemoryBlocks()
        blocks.rethink("project_context", "FastAPI + Supabase")
        blocks.rethink("user_preferences", "concise output")
        persist_blocks_to_disk(blocks, str(tmp_path))
        loaded = load_blocks_from_disk(str(tmp_path))
        assert loaded.get("project_context") == "FastAPI + Supabase"
        assert loaded.get("user_preferences") == "concise output"


class TestParseStructuredMemory:
    def test_parses_multiple_blocks(self):
        raw = (
            "<!-- block:project_context -->\nContent A\n<!-- /block:project_context -->\n\n"
            "<!-- block:user_preferences -->\nContent B\n<!-- /block:user_preferences -->"
        )
        result = _parse_structured_memory(raw)
        assert result is not None
        assert result["project_context"] == "Content A"
        assert result["user_preferences"] == "Content B"

    def test_returns_none_for_no_markers(self):
        assert _parse_structured_memory("just plain text") is None

    def test_ignores_unknown_block_names(self):
        raw = "<!-- block:unknown_block -->\nStuff\n<!-- /block:unknown_block -->"
        assert _parse_structured_memory(raw) is None


class TestSummarizeFindings:
    def test_summarizes_open_findings(self):
        findings = [
            {"severity": "HIGH", "issue_id": "X-01", "file": "a.py",
             "line": 1, "description": "Bug here", "status": "open"},
        ]
        result = _summarize_findings(findings, 500)
        assert "X-01" in result
        assert "1 open" in result

    def test_excludes_fixed_findings(self):
        findings = [
            {"severity": "LOW", "issue_id": "X-02", "file": "b.py",
             "line": 2, "description": "Fixed bug", "status": "fixed"},
        ]
        result = _summarize_findings(findings, 500)
        assert result == ""

    def test_caps_at_max_chars(self):
        findings = [
            {"severity": "HIGH", "issue_id": f"X-{i:02d}", "file": f"file{i}.py",
             "line": i, "description": "A" * 60, "status": "open"}
            for i in range(20)
        ]
        result = _summarize_findings(findings, 200)
        assert len(result) <= 200
