"""Unit tests for the auto-memory commit helper used by close_session (#161).

These tests depend only on `:automemory` (which transitively pulls in
`:models` and `:utils`) so they re-run only when those modules
themselves change — handler edits don't invalidate them.
"""
from __future__ import annotations

from pathlib import Path

import pytest

from active_claude_github_mcp.automemory import (
    commit_session_state,
    compose_session_state_memory,
    compute_memory_dir,
)
from active_claude_github_mcp.models import GithubConfig
from active_claude_github_mcp.utils import SessionActivity


def _config(**overrides) -> GithubConfig:
    """Build a GithubConfig from the canonical fixture, with overrides."""
    base = {
        "version": "1.0.0",
        "skill": "active-claude-github",
        "account_type": "personal",
        "owner": "telejester",
        "repos": {
            "public": "telejester/claude-skills",
            "private": "telejester/claude-skills-dev",
        },
        "project_board": {
            "id": "PVT_kwHOAAcyI84BVONW",
            "number": 1,
            "url": "https://github.com/users/telejester/projects/1",
        },
        "project_discussion_id": "D_kwDOSIO9Xs4Al2KX",
        "session_lock_discussion_id": "D_kwDOSIO9Xs4Al2KY",
        "discussion_categories": {
            "Architecture": {"id": "DIC_kwDOSIO9Xs4Cl1AA", "isAnswerable": True},
            "UX / UI": {"id": "DIC_kwDOSIO9Xs4Cl1AB", "isAnswerable": True},
            "Ideas": {"id": "DIC_kwDOSIO9Xs4Cl1AC", "isAnswerable": False},
            "claude-use-only": {
                "id": "DIC_kwDOSIO9Xs4Cl1AD",
                "isAnswerable": False,
            },
        },
        "skill_version_at_bootstrap": "1.0.0",
        "bootstrapped_at": "2026-04-21T12:00:00Z",
    }
    base.update(overrides)
    return GithubConfig.model_validate(base)


class TestComputeMemoryDir:
    def test_replaces_slashes_with_dashes(self):
        path = compute_memory_dir(cwd="/home/me/project")
        assert path.parts[-3] == "projects"
        assert path.parts[-2] == "-home-me-project"
        assert path.parts[-1] == "memory"

    def test_root_path(self):
        path = compute_memory_dir(cwd="/")
        assert path.parts[-2] == "-"

    def test_uses_real_cwd_when_none(self, monkeypatch):
        # Defaults to os.getcwd(); confirm the function reads it.
        monkeypatch.chdir("/")
        path = compute_memory_dir()
        assert path.parts[-2] == "-"


class TestComposeSessionStateMemory:
    def test_contains_required_frontmatter_fields(self):
        cfg = _config()
        body = compose_session_state_memory(
            cfg, SessionActivity(), 0, False
        )
        assert body.startswith("---\n")
        assert "name: " in body
        assert "type: project" in body
        # Description must be present and contain the trust-but-verify hint.
        assert "Verify against current state" in body

    def test_includes_repo_pair_and_account_type(self):
        cfg = _config()
        body = compose_session_state_memory(
            cfg, SessionActivity(), 0, False
        )
        assert "telejester/claude-skills" in body
        assert "telejester/claude-skills-dev" in body
        assert "personal" in body

    def test_renders_related_repos(self):
        cfg = _config(
            related_repos=[
                {
                    "repo": "other/sibling",
                    "relationship": "sibling",
                    "description": "peer",
                }
            ]
        )
        body = compose_session_state_memory(
            cfg, SessionActivity(), 0, False
        )
        assert "other/sibling" in body
        assert "sibling" in body
        assert "peer" in body

    def test_empty_related_repos_renders_none(self):
        cfg = _config()
        body = compose_session_state_memory(
            cfg, SessionActivity(), 0, False
        )
        assert "_None._" in body

    def test_activity_summary_line(self):
        cfg = _config()
        activity = SessionActivity(
            turns_posted=3,
            turns_by_thread={"D_a": 2, "I_b": 1},
            issues_created=1,
        )
        body = compose_session_state_memory(cfg, activity, 0, False)
        assert "3 turn(s) across 2 thread(s)" in body
        assert "1 Issue(s)" in body

    def test_pending_buffer_caveat(self):
        cfg = _config()
        body = compose_session_state_memory(
            cfg, SessionActivity(), 0, has_pending_buffer=True
        )
        assert "pending buffer" in body


class TestCommitSessionState:
    def test_writes_file_when_dir_exists(self, tmp_path):
        memory_dir = tmp_path / "memory"
        memory_dir.mkdir()
        cfg = _config()

        result = commit_session_state(
            cfg, SessionActivity(), 0, False, memory_dir=memory_dir
        )
        assert result["committed"] is True
        out = memory_dir / "project_session_state.md"
        assert out.exists()
        assert "type: project" in out.read_text(encoding="utf-8")

    def test_replaces_existing_file_not_appends(self, tmp_path):
        memory_dir = tmp_path / "memory"
        memory_dir.mkdir()
        out = memory_dir / "project_session_state.md"
        out.write_text("old content that should be gone", encoding="utf-8")

        cfg = _config()
        commit_session_state(
            cfg, SessionActivity(), 0, False, memory_dir=memory_dir
        )

        body = out.read_text(encoding="utf-8")
        assert "old content" not in body
        assert "type: project" in body

    def test_skipped_when_dir_absent(self, tmp_path):
        # Nonexistent subdir of tmp_path
        memory_dir = tmp_path / "doesnotexist" / "memory"
        cfg = _config()

        result = commit_session_state(
            cfg, SessionActivity(), 0, False, memory_dir=memory_dir
        )
        assert result["committed"] is False
        assert "does not exist" in result["skipped_reason"]

    def test_skipped_when_path_is_file_not_dir(self, tmp_path):
        impostor = tmp_path / "memory"
        impostor.write_text("I am a file, not a directory.", encoding="utf-8")
        cfg = _config()

        result = commit_session_state(
            cfg, SessionActivity(), 0, False, memory_dir=impostor
        )
        assert result["committed"] is False
        assert "not a directory" in result["skipped_reason"]

    def test_memory_index_pointer_added_when_index_exists(self, tmp_path):
        memory_dir = tmp_path / "memory"
        memory_dir.mkdir()
        index = memory_dir / "MEMORY.md"
        index.write_text(
            "# memory index\n\n- [Existing entry](other.md) — desc\n",
            encoding="utf-8",
        )

        cfg = _config()
        commit_session_state(
            cfg, SessionActivity(), 0, False, memory_dir=memory_dir
        )

        contents = index.read_text(encoding="utf-8")
        assert "Existing entry" in contents  # untouched
        assert "project_session_state.md" in contents

    def test_memory_index_pointer_idempotent(self, tmp_path):
        memory_dir = tmp_path / "memory"
        memory_dir.mkdir()
        index = memory_dir / "MEMORY.md"
        original = (
            "# memory index\n\n"
            "- [Project session state](project_session_state.md) — manual entry\n"
        )
        index.write_text(original, encoding="utf-8")

        cfg = _config()
        commit_session_state(
            cfg, SessionActivity(), 0, False, memory_dir=memory_dir
        )

        # The pointer line is already present (mentions the filename),
        # so the index must not be appended-to.
        assert index.read_text(encoding="utf-8") == original

    def test_no_index_means_no_index_creation(self, tmp_path):
        # If MEMORY.md does not exist, the helper does NOT create one.
        # Index is a user artifact; auto-memory commits don't author it.
        memory_dir = tmp_path / "memory"
        memory_dir.mkdir()
        cfg = _config()

        commit_session_state(
            cfg, SessionActivity(), 0, False, memory_dir=memory_dir
        )

        assert not (memory_dir / "MEMORY.md").exists()

    def test_default_memory_dir_skipped_in_test_env(self, tmp_path, monkeypatch):
        """Without an explicit memory_dir override, compute_memory_dir
        derives a path under ~/.claude/projects/<sanitized-cwd>/memory/
        — which doesn't exist for an arbitrary tmp_path. The helper
        must skip cleanly. Catches the regression where compute_memory_dir
        crashed on unexpected cwd shapes."""
        cfg = _config()
        monkeypatch.chdir(tmp_path)

        result = commit_session_state(cfg, SessionActivity(), 0, False)
        assert result["committed"] is False
        assert "skipped_reason" in result


if __name__ == "__main__":
    import sys
    sys.exit(pytest.main([__file__, "-v"]))
