# -*- coding: utf-8 -*-
"""curator 模块测试"""
import json
import tempfile
from datetime import datetime, timedelta, timezone
from pathlib import Path

import pytest

from abyss.curator import (
    STATE_ACTIVE,
    STATE_STALE,
    STATE_ARCHIVED,
    bump_use,
    load_usage,
    save_usage,
    load_state,
    save_state,
    should_run_now,
    apply_automatic_transitions,
    pin_skill,
    unpin_skill,
    restore_skill,
    list_archived,
    get_report,
)


class TestUsageTracking:
    """使用记录跟踪测试"""

    def test_bump_use_creates_new_record(self, monkeypatch, tmp_path):
        """首次使用创建记录。"""
        from abyss.curator import _usage_file

        usage_path = tmp_path / ".usage.json"
        monkeypatch.setattr("abyss.curator._usage_file", lambda: usage_path)

        bump_use("test-skill")
        data = load_usage()
        assert "test-skill" in data
        assert data["test-skill"]["use_count"] == 1
        assert data["test-skill"]["state"] == STATE_ACTIVE
        assert data["test-skill"]["pinned"] is False

    def test_bump_use_increments_count(self, monkeypatch, tmp_path):
        """多次使用累计计数。"""
        usage_path = tmp_path / ".usage.json"
        monkeypatch.setattr("abyss.curator._usage_file", lambda: usage_path)

        bump_use("test-skill")
        bump_use("test-skill")
        bump_use("test-skill")
        data = load_usage()
        assert data["test-skill"]["use_count"] == 3

    def test_bump_use_reactivates_stale(self, monkeypatch, tmp_path):
        """使用过期 skill 时重新激活。"""
        usage_path = tmp_path / ".usage.json"
        monkeypatch.setattr("abyss.curator._usage_file", lambda: usage_path)

        # 先手动创建为 stale
        save_usage({"test-skill": {"use_count": 0, "state": STATE_STALE, "pinned": False}})
        bump_use("test-skill")
        data = load_usage()
        assert data["test-skill"]["state"] == STATE_ACTIVE

    def test_bump_use_empty_name(self, monkeypatch, tmp_path):
        """空名称静默忽略。"""
        usage_path = tmp_path / ".usage.json"
        monkeypatch.setattr("abyss.curator._usage_file", lambda: usage_path)
        bump_use("")  # 不应抛异常
        bump_use(None)  # 不应抛异常


class TestStateFile:
    """状态文件读写测试"""

    def test_load_state_default(self, monkeypatch, tmp_path):
        state_path = tmp_path / ".curator_state"
        monkeypatch.setattr("abyss.curator._state_file", lambda: state_path)
        state = load_state()
        assert state["paused"] is False
        assert state["run_count"] == 0
        assert state["last_run_at"] is None

    def test_save_and_load_state(self, monkeypatch, tmp_path):
        state_path = tmp_path / ".curator_state"
        monkeypatch.setattr("abyss.curator._state_file", lambda: state_path)
        save_state({"last_run_at": "2026-01-01T00:00:00", "paused": True, "run_count": 5})
        state = load_state()
        assert state["paused"] is True
        assert state["run_count"] == 5
        assert state["last_run_at"] == "2026-01-01T00:00:00"


class TestShouldRunNow:
    """运行条件检查测试"""

    def test_first_run_defers(self, monkeypatch, tmp_path):
        """首次调用不运行，记录时间。"""
        state_path = tmp_path / ".curator_state"
        monkeypatch.setattr("abyss.curator._state_file", lambda: state_path)
        now = datetime(2026, 7, 1, 12, 0, 0, tzinfo=timezone.utc)
        assert should_run_now(now=now) is False

        state = load_state()
        assert state["last_run_at"] is not None

    def test_before_interval(self, monkeypatch, tmp_path):
        """未到 interval 不运行。"""
        state_path = tmp_path / ".curator_state"
        monkeypatch.setattr("abyss.curator._state_file", lambda: state_path)

        save_state({"last_run_at": "2026-07-01T12:00:00+00:00", "paused": False, "run_count": 1})
        now = datetime(2026, 7, 1, 23, 0, 0, tzinfo=timezone.utc)
        assert should_run_now(now=now) is False

    def test_after_interval_runs(self, monkeypatch, tmp_path):
        """超过 interval 后运行。"""
        state_path = tmp_path / ".curator_state"
        monkeypatch.setattr("abyss.curator._state_file", lambda: state_path)

        save_state({"last_run_at": "2026-07-01T12:00:00+00:00", "paused": False, "run_count": 1})
        now = datetime(2026, 7, 3, 12, 0, 0, tzinfo=timezone.utc)
        assert should_run_now(now=now) is True

    def test_paused_skips(self, monkeypatch, tmp_path):
        """暂停时跳过。"""
        state_path = tmp_path / ".curator_state"
        monkeypatch.setattr("abyss.curator._state_file", lambda: state_path)

        save_state({"last_run_at": "2026-06-01T12:00:00+00:00", "paused": True, "run_count": 1})
        now = datetime(2026, 7, 6, 12, 0, 0, tzinfo=timezone.utc)
        assert should_run_now(now=now) is False


class TestAutomaticTransitions:
    """自动状态过渡测试"""

    @pytest.fixture
    def skill_env(self, monkeypatch, tmp_path):
        """准备测试用 skill 目录和环境。"""
        skills_dir = tmp_path / "skills"
        skills_dir.mkdir()
        usage_path = skills_dir / ".usage.json"

        monkeypatch.setattr("abyss.curator._skills_dir", lambda: skills_dir)
        monkeypatch.setattr("abyss.curator._usage_file", lambda: usage_path)
        monkeypatch.setattr("abyss.curator._archive_dir", lambda: skills_dir / ".archive")

        # 创建测试 skill
        def make_skill(dirname, name, days_ago=0):
            d = skills_dir / dirname
            d.mkdir()
            mtime = (datetime.now() - timedelta(days=days_ago)).timestamp()
            (d / "SKILL.md").write_text(
                f"---\nname: {name}\ndescription: Test\n---\n# {name}\n",
                encoding="utf-8",
            )
            # 设目录时间（apply_automatic_transitions 用 skill_dir.stat()）
            import os
            os.utime(str(d), (mtime, mtime))

        return skills_dir, make_skill

    def test_apply_stale(self, skill_env):
        """超过 stale 天数未使用 → 标记 stale。"""
        dirs, make = skill_env
        make("old-skill", "old-skill", days_ago=30)

        now = datetime.now(timezone.utc)
        from abyss.curator import DEFAULT_STALE_AFTER_DAYS
        counts = apply_automatic_transitions(now=now)
        assert counts["checked"] == 1
        assert counts["marked_stale"] == 1

        usage = load_usage()
        assert usage["old-skill"]["state"] == STATE_STALE

    def test_pinned_bypasses(self, skill_env):
        """固定 skill 不受影响。"""
        dirs, make = skill_env
        make("old-skill", "old-skill", days_ago=60)

        # 先固定
        save_usage({"old-skill": {"pinned": True, "use_count": 0, "state": STATE_ACTIVE}})

        now = datetime.now(timezone.utc)
        counts = apply_automatic_transitions(now=now)
        assert counts["marked_stale"] == 0
        assert counts["archived"] == 0


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

    def test_pin(self, monkeypatch, tmp_path):
        usage_path = tmp_path / ".usage.json"
        monkeypatch.setattr("abyss.curator._usage_file", lambda: usage_path)

        assert pin_skill("my-skill") is True
        usage = load_usage()
        assert usage["my-skill"]["pinned"] is True

    def test_unpin(self, monkeypatch, tmp_path):
        usage_path = tmp_path / ".usage.json"
        monkeypatch.setattr("abyss.curator._usage_file", lambda: usage_path)

        save_usage({"my-skill": {"pinned": True, "state": STATE_ACTIVE, "use_count": 0}})
        assert unpin_skill("my-skill") is True
        usage = load_usage()
        assert usage["my-skill"]["pinned"] is False


class TestRestore:
    """归档恢复测试"""

    def test_restore_not_archived(self, monkeypatch, tmp_path):
        """未归档 skill 无法恢复。"""
        usage_path = tmp_path / ".usage.json"
        monkeypatch.setattr("abyss.curator._usage_file", lambda: usage_path)

        assert restore_skill("nonexistent") is False


class TestGetReport:
    """报告生成测试"""

    def test_report_empty(self, monkeypatch, tmp_path):
        """无 skill 时返回空列表。"""
        skills_dir = tmp_path / "skills"
        skills_dir.mkdir()
        usage_path = skills_dir / ".usage.json"
        monkeypatch.setattr("abyss.curator._skills_dir", lambda: skills_dir)
        monkeypatch.setattr("abyss.curator._usage_file", lambda: usage_path)

        report = get_report()
        assert report == []
