"""Tests for the project wiki primitive."""

from __future__ import annotations

from datetime import datetime, timezone

import pytest

from abreka.models import WikiPage
from abreka.prompts import format_wiki_index, get_default_template
from abreka.wiki import (
    InvalidSlugError,
    WikiError,
    WikiPageExistsError,
    WikiPageNotFoundError,
    append_history,
    collect_tags,
    delete_wiki_page,
    list_wiki_slugs,
    load_wiki_index,
    load_wiki_page,
    load_wiki_pages,
    merge_pages,
    normalize_slug,
    parse_page,
    rebuild_wiki_index,
    rename_page,
    save_wiki_page,
    serialize_page,
    wiki_page_path,
)


class TestNormalizeSlug:
    @pytest.mark.parametrize(
        "raw,expected",
        [
            ("hello", "hello"),
            ("Hello World", "hello-world"),
            ("data_set-Notes", "data-set-notes"),
            ("  spaces  around  ", "spaces-around"),
            ("Class Balance!", "class-balance"),
            ("multi   spaces", "multi-spaces"),
            ("UPPER", "upper"),
            ("a1b2", "a1b2"),
        ],
    )
    def test_normalizes(self, raw, expected):
        assert normalize_slug(raw) == expected

    def test_already_normalized(self):
        assert normalize_slug("foo-bar-baz") == "foo-bar-baz"

    def test_empty_raises(self):
        with pytest.raises(InvalidSlugError):
            normalize_slug("")

    def test_only_punctuation_raises(self):
        with pytest.raises(InvalidSlugError):
            normalize_slug("!!!")


class TestSerializeRoundtrip:
    def test_roundtrip_minimal(self):
        page = WikiPage(slug="foo", title="Foo", body="Hello.\n")
        text = serialize_page(page)
        restored = parse_page("foo", text)
        assert restored.slug == "foo"
        assert restored.title == "Foo"
        assert restored.body.strip() == "Hello."

    def test_roundtrip_full(self):
        when = datetime(2026, 4, 28, 12, 0, tzinfo=timezone.utc)
        page = WikiPage(
            slug="ds-balance",
            title="Dataset class balance",
            summary="9/10 classes uniform; class 5 ~9.0%.",
            tags=["eda", "dataset"],
            body="# Dataset class balance\n\nBody here.\n",
            created_at=when,
            updated_at=when,
            created_by_exp="0003",
            updated_by_exp="0003",
        )
        text = serialize_page(page)
        restored = parse_page(page.slug, text)
        assert restored.tags == ["eda", "dataset"]
        assert restored.summary.startswith("9/10")
        assert restored.created_by_exp == "0003"
        assert restored.updated_by_exp == "0003"
        assert restored.created_at == when
        assert restored.updated_at == when

    def test_parse_rejects_missing_frontmatter(self):
        with pytest.raises(WikiError):
            parse_page("foo", "no frontmatter here")

    def test_parse_rejects_unterminated_frontmatter(self):
        with pytest.raises(WikiError):
            parse_page("foo", "+++\nslug = \"foo\"\n# no closer")


class TestPersistence:
    def test_save_and_load(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        page = WikiPage(slug="hello", title="Hello", body="Body.")
        save_wiki_page(page, abreka_dir)
        loaded = load_wiki_page(abreka_dir, "hello")
        assert loaded.slug == "hello"
        assert loaded.title == "Hello"

    def test_save_normalizes_slug(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        page = WikiPage(slug="Hello World", title="Hello")
        save_wiki_page(page, abreka_dir)
        assert wiki_page_path(abreka_dir, "hello-world").is_file()
        assert page.slug == "hello-world"

    def test_save_writes_index_entry(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        page = WikiPage(
            slug="hello",
            title="Hi",
            summary="A greeting.",
            tags=["intro"],
        )
        save_wiki_page(page, abreka_dir)
        index = load_wiki_index(abreka_dir)
        assert "hello" in index
        assert index["hello"]["title"] == "Hi"
        assert index["hello"]["tags"] == ["intro"]
        assert index["hello"]["summary"] == "A greeting."

    def test_load_missing_raises(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        with pytest.raises(WikiPageNotFoundError):
            load_wiki_page(abreka_dir, "ghost")

    def test_delete_removes_file_and_index(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        save_wiki_page(WikiPage(slug="foo", title="Foo"), abreka_dir)
        save_wiki_page(WikiPage(slug="bar", title="Bar"), abreka_dir)
        delete_wiki_page(abreka_dir, "foo")
        assert not wiki_page_path(abreka_dir, "foo").is_file()
        assert wiki_page_path(abreka_dir, "bar").is_file()
        assert "foo" not in load_wiki_index(abreka_dir)
        assert "bar" in load_wiki_index(abreka_dir)

    def test_delete_missing_raises(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        with pytest.raises(WikiPageNotFoundError):
            delete_wiki_page(abreka_dir, "ghost")

    def test_list_slugs_sorted(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        for slug in ("zeta", "alpha", "beta"):
            save_wiki_page(WikiPage(slug=slug, title=slug), abreka_dir)
        assert list_wiki_slugs(abreka_dir) == ["alpha", "beta", "zeta"]

    def test_load_pages_iter(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        save_wiki_page(WikiPage(slug="a", title="A"), abreka_dir)
        save_wiki_page(WikiPage(slug="b", title="B"), abreka_dir)
        slugs = [p.slug for p in load_wiki_pages(abreka_dir)]
        assert slugs == ["a", "b"]


class TestAppendHistory:
    def test_creates_history_section(self):
        page = WikiPage(slug="foo", title="Foo", body="# Foo\n\nBody.")
        append_history(page, "first note", exp_id="0007")
        assert "## History" in page.body
        assert "(exp 0007): first note" in page.body
        assert page.updated_by_exp == "0007"

    def test_appends_to_existing_section(self):
        page = WikiPage(
            slug="foo",
            title="Foo",
            body="# Foo\n\nBody.\n\n## History\n\n- 2026-01-01: prior\n",
        )
        append_history(page, "next note", exp_id="0011")
        # Both lines should be present
        assert "prior" in page.body
        assert "(exp 0011): next note" in page.body
        # Only one History header
        assert page.body.count("## History") == 1


class TestMergeAndRename:
    def test_merge_concatenates_and_deletes_src(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        save_wiki_page(
            WikiPage(slug="src", title="Src", tags=["x"], body="src body"),
            abreka_dir,
        )
        save_wiki_page(
            WikiPage(slug="dst", title="Dst", tags=["y"], body="dst body"),
            abreka_dir,
        )
        result = merge_pages(abreka_dir, "src", "dst", exp_id="0042")
        assert result.slug == "dst"
        assert "src body" in result.body
        assert "dst body" in result.body
        assert set(result.tags) == {"x", "y"}
        assert not wiki_page_path(abreka_dir, "src").is_file()
        assert "src" not in load_wiki_index(abreka_dir)

    def test_merge_self_raises(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        save_wiki_page(WikiPage(slug="foo", title="Foo"), abreka_dir)
        with pytest.raises(WikiError):
            merge_pages(abreka_dir, "foo", "foo")

    def test_rename_moves_file_and_index(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        save_wiki_page(WikiPage(slug="old", title="Old"), abreka_dir)
        rename_page(abreka_dir, "old", "new")
        assert not wiki_page_path(abreka_dir, "old").is_file()
        assert wiki_page_path(abreka_dir, "new").is_file()
        idx = load_wiki_index(abreka_dir)
        assert "old" not in idx
        assert "new" in idx

    def test_rename_collision_raises(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        save_wiki_page(WikiPage(slug="a", title="A"), abreka_dir)
        save_wiki_page(WikiPage(slug="b", title="B"), abreka_dir)
        with pytest.raises(WikiPageExistsError):
            rename_page(abreka_dir, "a", "b")


class TestFormatWikiIndex:
    def test_empty_index_renders_helpful_message(self):
        rendered = format_wiki_index({})
        assert "## Project Wiki" in rendered
        assert "wiki is empty" in rendered
        assert "wiki add" in rendered

    def test_renders_summary_and_tags(self):
        index = {
            "ds-balance": {
                "slug": "ds-balance",
                "title": "Dataset balance",
                "summary": "9/10 classes uniform.",
                "tags": ["eda", "dataset"],
            },
        }
        rendered = format_wiki_index(index)
        assert "## Project Wiki" in rendered
        assert "`ds-balance`" in rendered
        assert "9/10 classes uniform." in rendered
        assert "(eda, dataset)" in rendered
        assert "wiki show" in rendered
        assert "wiki search" in rendered

    def test_falls_back_to_title_when_no_summary(self):
        index = {
            "foo": {"slug": "foo", "title": "Foo title", "summary": "", "tags": []},
        }
        rendered = format_wiki_index(index)
        assert "Foo title" in rendered

    def test_entries_sorted_by_slug(self):
        index = {
            "zeta": {"slug": "zeta", "title": "Z", "summary": "", "tags": []},
            "alpha": {"slug": "alpha", "title": "A", "summary": "", "tags": []},
        }
        rendered = format_wiki_index(index)
        assert rendered.index("alpha") < rendered.index("zeta")


class TestWikiIndexInDefaultTemplates:
    @pytest.mark.parametrize(
        "name", ["propose_exploit", "propose_explore", "implement", "test", "evaluate"]
    )
    def test_wiki_index_placeholder_present(self, name):
        assert "{{wiki_index}}" in get_default_template(name)

    @pytest.mark.parametrize(
        "name", ["propose_exploit", "propose_explore", "implement", "test", "evaluate"]
    )
    def test_memory_placeholder_absent(self, name):
        assert "{{memory}}" not in get_default_template(name)


class TestTagsAndIndex:
    def test_collect_tags(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        save_wiki_page(
            WikiPage(slug="a", title="A", tags=["x", "y"]), abreka_dir,
        )
        save_wiki_page(
            WikiPage(slug="b", title="B", tags=["x"]), abreka_dir,
        )
        save_wiki_page(WikiPage(slug="c", title="C"), abreka_dir)
        counts = collect_tags(abreka_dir)
        assert counts == {"x": 2, "y": 1}

    def test_rebuild_wiki_index(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        # Save two pages, then nuke the index.
        save_wiki_page(WikiPage(slug="a", title="A"), abreka_dir)
        save_wiki_page(WikiPage(slug="b", title="B"), abreka_dir)
        (abreka_dir / "wiki_index.json").unlink()

        rebuilt = rebuild_wiki_index(abreka_dir)
        assert set(rebuilt.keys()) == {"a", "b"}
        # Reloaded from disk shows the same.
        assert set(load_wiki_index(abreka_dir).keys()) == {"a", "b"}
