"""Tests for the abreka wiki CLI group."""

from __future__ import annotations

import json
import os

from click.testing import CliRunner

from abreka.cli import main


def _add(runner, *args):
    return runner.invoke(main, ["wiki", "add", *args])


class TestWikiAdd:
    def test_creates_page(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        result = _add(
            runner,
            "--title", "Dataset class balance",
            "--summary", "9/10 classes uniform.",
            "--tag", "eda",
            "--tag", "dataset",
            "--content", "# Dataset class balance\n\nBody.",
        )
        assert result.exit_code == 0, result.output
        slug = result.output.strip()
        assert slug == "dataset-class-balance"

        path = abreka_project / "abreka" / "wiki" / "pages" / f"{slug}.md"
        assert path.is_file()
        content = path.read_text()
        assert content.startswith("+++")
        assert "Dataset class balance" in content
        assert "Body." in content

    def test_explicit_slug_overrides_title(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        result = _add(
            runner,
            "--title", "Dataset class balance",
            "--slug", "ds-balance",
            "--content", "x",
        )
        assert result.exit_code == 0, result.output
        assert result.output.strip() == "ds-balance"

    def test_duplicate_slug_rejected(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Foo", "--content", "a")
        result = _add(runner, "--title", "Foo", "--content", "b")
        assert result.exit_code != 0
        assert "already exists" in result.output

    def test_records_index_entry(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(
            runner,
            "--title", "My page",
            "--summary", "the summary",
            "--tag", "x",
        )
        idx = json.loads((abreka_project / "abreka" / "wiki_index.json").read_text())
        assert "my-page" in idx
        assert idx["my-page"]["summary"] == "the summary"
        assert idx["my-page"]["tags"] == ["x"]

    def test_invalid_title_rejected(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        result = _add(runner, "--title", "!!!")
        assert result.exit_code != 0


class TestWikiList:
    def test_empty(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        result = runner.invoke(main, ["wiki", "list"])
        assert result.exit_code == 0
        assert "No wiki pages" in result.output

    def test_list_after_add(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Alpha", "--tag", "x")
        _add(runner, "--title", "Beta", "--tag", "y")
        result = runner.invoke(main, ["wiki", "list"])
        assert result.exit_code == 0, result.output
        assert "alpha" in result.output
        assert "beta" in result.output

    def test_filter_by_tag(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Alpha", "--tag", "x")
        _add(runner, "--title", "Beta", "--tag", "y")
        result = runner.invoke(main, ["wiki", "list", "--tag", "y"])
        assert result.exit_code == 0
        assert "beta" in result.output
        assert "alpha" not in result.output

    def test_json_output(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Alpha", "--tag", "x")
        result = runner.invoke(main, ["wiki", "list", "--json"])
        assert result.exit_code == 0
        data = json.loads(result.output)
        assert isinstance(data, list)
        assert data[0]["slug"] == "alpha"


class TestWikiShow:
    def test_show_page(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(
            runner,
            "--title", "Alpha",
            "--tag", "eda",
            "--summary", "summary line",
            "--content", "# Alpha\n\nBody here.",
        )
        result = runner.invoke(main, ["wiki", "show", "alpha"])
        assert result.exit_code == 0, result.output
        assert "Alpha" in result.output
        assert "tags: eda" in result.output
        assert "Body here." in result.output

    def test_show_raw(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Alpha", "--content", "Body")
        result = runner.invoke(main, ["wiki", "show", "alpha", "--raw"])
        assert result.exit_code == 0
        assert result.output.startswith("+++")

    def test_show_missing(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        result = runner.invoke(main, ["wiki", "show", "ghost"])
        assert result.exit_code != 0


class TestWikiAppend:
    def test_appends_history(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Alpha", "--content", "Body.")
        result = runner.invoke(
            main,
            ["wiki", "append", "alpha", "--content", "discovered X", "--exp", "0007"],
        )
        assert result.exit_code == 0, result.output
        body = (abreka_project / "abreka" / "wiki" / "pages" / "alpha.md").read_text()
        assert "## History" in body
        assert "exp 0007" in body
        assert "discovered X" in body

    def test_requires_content(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Alpha", "--content", "Body.")
        result = runner.invoke(main, ["wiki", "append", "alpha"])
        assert result.exit_code != 0


class TestWikiEdit:
    def test_edit_title_and_body(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Alpha", "--content", "old body")
        result = runner.invoke(
            main,
            [
                "wiki", "edit", "alpha",
                "--title", "Alpha v2",
                "--content", "new body",
            ],
        )
        assert result.exit_code == 0, result.output
        result2 = runner.invoke(main, ["wiki", "show", "alpha"])
        assert "Alpha v2" in result2.output
        assert "new body" in result2.output
        assert "old body" not in result2.output

    def test_edit_replaces_tags(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Alpha", "--tag", "x", "--tag", "y")
        result = runner.invoke(
            main, ["wiki", "edit", "alpha", "--tag", "z"],
        )
        assert result.exit_code == 0, result.output
        idx = json.loads((abreka_project / "abreka" / "wiki_index.json").read_text())
        assert idx["alpha"]["tags"] == ["z"]

    def test_edit_requires_a_field(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Alpha")
        result = runner.invoke(main, ["wiki", "edit", "alpha"])
        assert result.exit_code != 0


class TestWikiRenameMergeDelete:
    def test_rename(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Old", "--content", "x")
        result = runner.invoke(main, ["wiki", "rename", "old", "new"])
        assert result.exit_code == 0, result.output
        assert (abreka_project / "abreka" / "wiki" / "pages" / "new.md").is_file()
        assert not (abreka_project / "abreka" / "wiki" / "pages" / "old.md").is_file()

    def test_merge(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Src", "--content", "src body", "--tag", "x")
        _add(runner, "--title", "Dst", "--content", "dst body", "--tag", "y")
        result = runner.invoke(main, ["wiki", "merge", "src", "dst"])
        assert result.exit_code == 0, result.output
        assert not (abreka_project / "abreka" / "wiki" / "pages" / "src.md").is_file()
        body = (abreka_project / "abreka" / "wiki" / "pages" / "dst.md").read_text()
        assert "src body" in body
        assert "dst body" in body

    def test_delete_with_yes(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Alpha", "--content", "x")
        result = runner.invoke(main, ["wiki", "delete", "alpha", "--yes"])
        assert result.exit_code == 0
        assert "Deleted alpha" in result.output

    def test_delete_missing(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        result = runner.invoke(main, ["wiki", "delete", "ghost", "--yes"])
        assert result.exit_code != 0


class TestWikiSearchAndTags:
    def test_search_finds_match(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(
            runner,
            "--title", "MNIST notes",
            "--content", "Class balance is uniform across splits.",
            "--tag", "eda",
        )
        _add(runner, "--title", "Other", "--content", "irrelevant")
        result = runner.invoke(main, ["wiki", "search", "balance"])
        assert result.exit_code == 0, result.output
        assert "mnist-notes" in result.output
        assert "other" not in result.output

    def test_search_regex(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Foo", "--content", "Loss=0.005 noted.")
        result = runner.invoke(main, ["wiki", "search", r"/Loss=\d/"])
        assert result.exit_code == 0
        assert "foo" in result.output

    def test_search_filter_by_tag(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "A", "--content", "balance", "--tag", "eda")
        _add(runner, "--title", "B", "--content", "balance", "--tag", "lit")
        result = runner.invoke(main, ["wiki", "search", "balance", "--tag", "lit"])
        assert "b" in result.output
        assert "a\n" not in result.output  # rough — table cell aligns

    def test_tags(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "A", "--tag", "x", "--tag", "y")
        _add(runner, "--title", "B", "--tag", "x")
        result = runner.invoke(main, ["wiki", "tags"])
        assert result.exit_code == 0, result.output
        assert "x" in result.output
        assert "y" in result.output


class TestExpProvenance:
    def test_add_records_created_by_exp(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Alpha", "--exp", "0007")
        idx = json.loads((abreka_project / "abreka" / "wiki_index.json").read_text())
        assert idx["alpha"]["created_by_exp"] == "0007"
        assert idx["alpha"]["updated_by_exp"] == "0007"

    def test_edit_updates_updated_by_exp(self, abreka_project):
        os.chdir(abreka_project)
        runner = CliRunner()
        _add(runner, "--title", "Alpha", "--exp", "0007")
        runner.invoke(
            main,
            ["wiki", "edit", "alpha", "--summary", "new", "--exp", "0011"],
        )
        idx = json.loads((abreka_project / "abreka" / "wiki_index.json").read_text())
        assert idx["alpha"]["updated_by_exp"] == "0011"
        assert idx["alpha"]["created_by_exp"] == "0007"
