# -*- coding: utf-8 -*-
"""
KnowledgeCurator 测试
覆盖：使用跟踪 bump_use、按类型差异化自动过渡（preference/bugfix 永不过期，
experience/tool 90天 stale / 180天 archive）、pinned 豁免、手动归档、
.usage.json 侧边文件持久化。
"""
from datetime import datetime, timedelta, timezone
from pathlib import Path

import pytest

from abyss.knowledge import KnowledgeCurator


def _write_md(path: Path, name: str, description: str = "测试",
              body: str = "正文") -> None:
    """写一个带 frontmatter 的 .md 文件到 path，自动创建父目录。"""
    path.parent.mkdir(parents=True, exist_ok=True)
    fm = "---\n"
    fm += f"name: {name}\n"
    fm += f"description: {description}\n"
    fm += "tags: []\n"
    fm += "---\n"
    fm += body
    path.write_text(fm, encoding="utf-8")


@pytest.fixture
def curator_env(tmp_path):
    """准备 global + project 两级知识目录的测试环境，返回 (global_dir, project_dir)。"""
    global_dir = tmp_path / "global"
    project_dir = tmp_path / "project"
    global_dir.mkdir()
    project_dir.mkdir()
    return global_dir, project_dir


class TestBumpUse:
    """使用跟踪 bump_use 测试。"""

    def test_bump_use_creates_new_record(self, curator_env):
        """首次使用创建记录，use_count=1，state=active，pinned=False。"""
        global_dir, project_dir = curator_env
        _write_md(project_dir / "experiences" / "exp.md", "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        curator.bump_use("exp-1")

        usage = curator.load_usage_for_level("project")
        assert "exp-1" in usage
        assert usage["exp-1"]["use_count"] == 1
        assert usage["exp-1"]["state"] == "active"
        assert usage["exp-1"]["pinned"] is False

    def test_bump_use_increments_count(self, curator_env):
        """多次使用累计计数。"""
        global_dir, project_dir = curator_env
        _write_md(project_dir / "experiences" / "exp.md", "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        curator.bump_use("exp-1")
        curator.bump_use("exp-1")
        curator.bump_use("exp-1")

        usage = curator.load_usage_for_level("project")
        assert usage["exp-1"]["use_count"] == 3

    def test_bump_use_updates_last_used_at(self, curator_env):
        """last_used_at 被设置为当前时间。"""
        global_dir, project_dir = curator_env
        _write_md(project_dir / "experiences" / "exp.md", "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        before = datetime.now(timezone.utc)
        curator.bump_use("exp-1")

        usage = curator.load_usage_for_level("project")
        last_used_str = usage["exp-1"]["last_used_at"]
        assert last_used_str is not None
        last_used = datetime.fromisoformat(last_used_str)
        assert last_used >= before - timedelta(seconds=1)

    def test_bump_use_reactivates_stale(self, curator_env):
        """使用 stale 状态的知识时重新激活为 active，并累加 use_count。"""
        global_dir, project_dir = curator_env
        _write_md(project_dir / "experiences" / "exp.md", "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        curator.save_usage_for_level("project", {
            "exp-1": {"use_count": 5, "last_used_at": None,
                      "state": "stale", "pinned": False}
        })
        curator.bump_use("exp-1")

        usage = curator.load_usage_for_level("project")
        assert usage["exp-1"]["state"] == "active"
        assert usage["exp-1"]["use_count"] == 6

    def test_bump_use_empty_name_ignored(self, curator_env):
        """空名称静默忽略，不抛异常。"""
        global_dir, project_dir = curator_env
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        curator.bump_use("")  # 不应抛异常
        curator.bump_use(None)  # 不应抛异常


class TestAutomaticTransitions:
    """按类型差异化自动过渡测试。"""

    def test_preference_never_expires(self, curator_env):
        """preference 类型即使 last_used_at 很旧也保持 active。"""
        global_dir, project_dir = curator_env
        _write_md(global_dir / "preferences" / "p.md", "pref-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        old_time = (datetime.now(timezone.utc) - timedelta(days=365)).isoformat()
        curator.save_usage_for_level("global", {
            "pref-1": {"use_count": 1, "last_used_at": old_time,
                       "state": "active", "pinned": False}
        })

        curator.apply_automatic_transitions()
        usage = curator.load_usage_for_level("global")
        assert usage["pref-1"]["state"] == "active"

    def test_bugfix_never_expires(self, curator_env):
        """bugfix 类型即使 last_used_at 很旧也保持 active。"""
        global_dir, project_dir = curator_env
        _write_md(project_dir / "bugfixes" / "b.md", "bug-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        old_time = (datetime.now(timezone.utc) - timedelta(days=365)).isoformat()
        curator.save_usage_for_level("project", {
            "bug-1": {"use_count": 1, "last_used_at": old_time,
                      "state": "active", "pinned": False}
        })

        curator.apply_automatic_transitions()
        usage = curator.load_usage_for_level("project")
        assert usage["bug-1"]["state"] == "active"

    def test_experience_stale_after_90_days(self, curator_env):
        """experience 类型 90 天未用 → stale。"""
        global_dir, project_dir = curator_env
        _write_md(global_dir / "experiences" / "e.md", "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        old_time = (datetime.now(timezone.utc) - timedelta(days=100)).isoformat()
        curator.save_usage_for_level("global", {
            "exp-1": {"use_count": 1, "last_used_at": old_time,
                      "state": "active", "pinned": False}
        })

        counts = curator.apply_automatic_transitions()
        usage = curator.load_usage_for_level("global")
        assert usage["exp-1"]["state"] == "stale"
        assert counts["marked_stale"] >= 1

    def test_experience_archived_after_180_days(self, curator_env):
        """experience 类型 180 天未用 → archived，文件移动到 .archive/。"""
        global_dir, project_dir = curator_env
        md_path = global_dir / "experiences" / "e.md"
        _write_md(md_path, "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        old_time = (datetime.now(timezone.utc) - timedelta(days=200)).isoformat()
        curator.save_usage_for_level("global", {
            "exp-1": {"use_count": 1, "last_used_at": old_time,
                      "state": "active", "pinned": False}
        })

        counts = curator.apply_automatic_transitions()
        usage = curator.load_usage_for_level("global")
        assert usage["exp-1"]["state"] == "archived"
        assert counts["archived"] >= 1
        # 原文件已移走
        assert not md_path.exists()
        # .archive/ 下有对应文件
        archived_files = list((global_dir / ".archive").glob("*.md"))
        assert len(archived_files) == 1

    def test_tool_stale_after_90_days(self, curator_env):
        """tool 类型 90 天未用 → stale。"""
        global_dir, project_dir = curator_env
        _write_md(global_dir / "tools" / "t.md", "tool-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        old_time = (datetime.now(timezone.utc) - timedelta(days=100)).isoformat()
        curator.save_usage_for_level("global", {
            "tool-1": {"use_count": 1, "last_used_at": old_time,
                       "state": "active", "pinned": False}
        })

        curator.apply_automatic_transitions()
        usage = curator.load_usage_for_level("global")
        assert usage["tool-1"]["state"] == "stale"

    def test_tool_archived_after_180_days(self, curator_env):
        """tool 类型 180 天未用 → archived，文件移动到 .archive/。"""
        global_dir, project_dir = curator_env
        md_path = global_dir / "tools" / "t.md"
        _write_md(md_path, "tool-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        old_time = (datetime.now(timezone.utc) - timedelta(days=200)).isoformat()
        curator.save_usage_for_level("global", {
            "tool-1": {"use_count": 1, "last_used_at": old_time,
                       "state": "active", "pinned": False}
        })

        curator.apply_automatic_transitions()
        usage = curator.load_usage_for_level("global")
        assert usage["tool-1"]["state"] == "archived"
        assert not md_path.exists()

    def test_pinned_exempts_from_stale(self, curator_env):
        """pinned 知识即使超过 stale 天数也保持 active。"""
        global_dir, project_dir = curator_env
        _write_md(global_dir / "experiences" / "e.md", "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        old_time = (datetime.now(timezone.utc) - timedelta(days=100)).isoformat()
        curator.save_usage_for_level("global", {
            "exp-1": {"use_count": 1, "last_used_at": old_time,
                      "state": "active", "pinned": True}
        })

        curator.apply_automatic_transitions()
        usage = curator.load_usage_for_level("global")
        assert usage["exp-1"]["state"] == "active"

    def test_pinned_exempts_from_archive(self, curator_env):
        """pinned 知识即使超过 archive 天数也不被归档，文件保留原位。"""
        global_dir, project_dir = curator_env
        md_path = global_dir / "experiences" / "e.md"
        _write_md(md_path, "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        old_time = (datetime.now(timezone.utc) - timedelta(days=200)).isoformat()
        curator.save_usage_for_level("global", {
            "exp-1": {"use_count": 1, "last_used_at": old_time,
                      "state": "active", "pinned": True}
        })

        curator.apply_automatic_transitions()
        usage = curator.load_usage_for_level("global")
        assert usage["exp-1"]["state"] == "active"
        assert md_path.exists()


class TestPinUnpin:
    """固定/取消固定测试。"""

    def test_pin_knowledge_sets_pinned_true(self, curator_env):
        """pin_knowledge 将 pinned 设为 True。"""
        global_dir, project_dir = curator_env
        _write_md(project_dir / "experiences" / "e.md", "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        assert curator.pin_knowledge("exp-1") is True
        usage = curator.load_usage_for_level("project")
        assert usage["exp-1"]["pinned"] is True

    def test_unpin_knowledge_sets_pinned_false(self, curator_env):
        """unpin_knowledge 将 pinned 设为 False。"""
        global_dir, project_dir = curator_env
        _write_md(project_dir / "experiences" / "e.md", "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        curator.pin_knowledge("exp-1")
        assert curator.unpin_knowledge("exp-1") is True
        usage = curator.load_usage_for_level("project")
        assert usage["exp-1"]["pinned"] is False


class TestManualArchive:
    """手动归档测试。"""

    def test_archive_knowledge_moves_file(self, curator_env):
        """archive_knowledge 将 .md 移动到 .archive/ 子目录。"""
        global_dir, project_dir = curator_env
        md_path = project_dir / "experiences" / "e.md"
        _write_md(md_path, "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        assert curator.archive_knowledge("exp-1") is True
        assert not md_path.exists()
        archived_files = list((project_dir / ".archive").glob("*.md"))
        assert len(archived_files) == 1

    def test_archive_knowledge_updates_state(self, curator_env):
        """archive_knowledge 将状态设为 archived。"""
        global_dir, project_dir = curator_env
        _write_md(project_dir / "experiences" / "e.md", "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        curator.archive_knowledge("exp-1")
        usage = curator.load_usage_for_level("project")
        assert usage["exp-1"]["state"] == "archived"

    def test_archive_nonexistent_returns_false(self, curator_env):
        """归档不存在的知识返回 False。"""
        global_dir, project_dir = curator_env
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        assert curator.archive_knowledge("nonexistent") is False


class TestUsageFilePersistence:
    """使用跟踪 .usage.json 文件持久化测试。"""

    def test_usage_file_created_after_bump(self, curator_env):
        """bump_use 后对应级别的 .usage.json 文件被创建。"""
        global_dir, project_dir = curator_env
        _write_md(project_dir / "experiences" / "e.md", "exp-1")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        curator.bump_use("exp-1")
        usage_file = project_dir / ".usage.json"
        assert usage_file.exists()

    def test_global_and_project_usage_files_separate(self, curator_env):
        """全局级和项目级的 .usage.json 文件相互独立。"""
        global_dir, project_dir = curator_env
        _write_md(global_dir / "preferences" / "g.md", "global-pref")
        _write_md(project_dir / "experiences" / "p.md", "project-exp")
        curator = KnowledgeCurator({"global": global_dir, "project": project_dir})

        curator.bump_use("global-pref")
        curator.bump_use("project-exp")

        global_usage = curator.load_usage_for_level("global")
        project_usage = curator.load_usage_for_level("project")

        assert "global-pref" in global_usage
        assert "global-pref" not in project_usage
        assert "project-exp" in project_usage
        assert "project-exp" not in global_usage
