"""Tests for alive.core.skill_registry."""

import json

import pytest

from alive.core.skill_registry import SkillInfo, SkillRegistry, _wilson_score


# ── Wilson score ─────────────────────────────────────────────────────────────


def test_wilson_score_no_data():
    assert _wilson_score(0, 0) == 0.5


def test_wilson_score_all_success():
    # 1 success should not give full confidence (lower bound is conservative)
    score_1 = _wilson_score(1, 0)
    assert 0.0 < score_1 < 0.5

    # 50 successes should give high confidence
    score_50 = _wilson_score(50, 0)
    assert score_50 > 0.9

    # Score should increase with more successes
    assert _wilson_score(10, 0) > _wilson_score(1, 0)


def test_wilson_score_all_failures():
    score = _wilson_score(0, 10)
    assert score < 0.1


def test_wilson_score_mixed():
    score = _wilson_score(5, 5)
    assert 0.2 < score < 0.6


def test_wilson_score_monotonic():
    # More successes with same total should increase score
    assert _wilson_score(8, 2) > _wilson_score(5, 5)


# ── SkillRegistry basic operations ──────────────────────────────────────────


@pytest.fixture
def registry(tmp_path):
    """Create a registry pointing at a temp directory (no seed skills)."""
    # Create a fake seeds dir so ensure_seeds finds nothing to copy
    import alive.seeds._installer as installer
    original = installer._SEEDS_DIR
    installer._SEEDS_DIR = tmp_path / "empty_seeds"
    installer._SEEDS_DIR.mkdir()
    reg = SkillRegistry(skills_dir=tmp_path / "skills")
    installer._SEEDS_DIR = original
    return reg


def test_list_all_empty(registry):
    assert registry.list_all() == []


def test_register_creates_files(registry):
    info = registry.register(
        name="test_skill",
        code='def run(): return "hello"',
        description="A test skill",
        dependencies=["requests"],
    )
    skill_dir = registry._skills_dir / "test_skill"
    assert skill_dir.exists()
    assert (skill_dir / "skill.py").exists()
    assert (skill_dir / "test_skill.py").exists()
    assert (skill_dir / "metadata.json").exists()
    assert (skill_dir / "SKILL.md").exists()
    assert info.name == "test_skill"
    assert info.confidence_score == 0.5
    assert info.dependencies == ["requests"]


def test_register_custom_files(registry):
    info = registry.register(
        name="custom",
        code='def run(x): return x',
        description="Custom skill",
        test_code='def test_it(): assert True',
        skill_doc="# Custom\n\nDoes stuff.\n",
    )
    skill_dir = registry._skills_dir / "custom"
    assert (skill_dir / "test_skill.py").read_text() == 'def test_it(): assert True'
    assert (skill_dir / "SKILL.md").read_text() == "# Custom\n\nDoes stuff.\n"


def test_register_duplicate_raises(registry):
    registry.register(name="dup", code="def run(): pass", description="first")
    with pytest.raises(FileExistsError, match="already exists"):
        registry.register(name="dup", code="def run(): pass", description="second")


def test_get_skill(registry):
    registry.register(name="findme", code="def run(): pass", description="findable")
    info = registry.get_skill("findme")
    assert info.name == "findme"
    assert info.description == "findable"


def test_get_skill_missing_raises(registry):
    with pytest.raises(KeyError, match="not found"):
        registry.get_skill("nonexistent")


def test_list_all_sorted_by_usage(registry):
    registry.register(name="a", code="def run(): pass", description="first")
    registry.register(name="b", code="def run(): pass", description="second")
    registry.update_stats("a", success=True)
    registry.update_stats("a", success=True)
    registry.update_stats("b", success=True)

    result = registry.list_all()
    assert result[0].name == "a"
    assert result[0].times_used == 2
    assert result[1].name == "b"
    assert result[1].times_used == 1


# ── update_stats ─────────────────────────────────────────────────────────────


def test_update_stats_success(registry):
    registry.register(name="s", code="def run(): pass", description="test")
    info = registry.update_stats("s", success=True)
    assert info.times_used == 1
    assert info.success_count == 1
    assert info.fail_count == 0
    assert info.last_used != ""
    assert info.confidence_score > 0.0  # Wilson lower bound is conservative at n=1


def test_update_stats_failure(registry):
    registry.register(name="f", code="def run(): pass", description="test")
    info = registry.update_stats("f", success=False)
    assert info.times_used == 1
    assert info.success_count == 0
    assert info.fail_count == 1
    assert info.confidence_score < 0.5


def test_update_stats_persists_to_disk(registry):
    registry.register(name="persist", code="def run(): pass", description="test")
    registry.update_stats("persist", success=True)

    # Reload from disk
    meta = json.loads((registry._skills_dir / "persist" / "metadata.json").read_text())
    assert meta["times_used"] == 1
    assert meta["success_count"] == 1


def test_update_stats_missing_skill(registry):
    with pytest.raises(KeyError):
        registry.update_stats("ghost", success=True)


# ── search ───────────────────────────────────────────────────────────────────


def test_search_by_name(registry):
    registry.register(name="web_fetch", code="def run(): pass", description="fetches URLs")
    registry.register(name="file_read", code="def run(): pass", description="reads files")

    results = registry.search("web")
    assert len(results) == 1
    assert results[0].name == "web_fetch"


def test_search_by_description(registry):
    registry.register(name="downloader", code="def run(): pass", description="fetches web pages")
    results = registry.search("web")
    assert len(results) == 1
    assert results[0].name == "downloader"


def test_search_by_skill_md(registry):
    registry.register(
        name="custom",
        code="def run(): pass",
        description="does things",
        skill_doc="# custom\n\nThis skill handles spreadsheet parsing.\n",
    )
    results = registry.search("spreadsheet")
    assert len(results) == 1
    assert results[0].name == "custom"


def test_search_empty_query(registry):
    registry.register(name="a", code="def run(): pass", description="first")
    results = registry.search("")
    assert len(results) == 1


def test_search_no_match(registry):
    registry.register(name="a", code="def run(): pass", description="first")
    results = registry.search("zzzznonexistent")
    assert len(results) == 0


def test_search_scores_ranked(registry):
    registry.register(name="web_fetch", code="def run(): pass", description="fetch web content from URL")
    registry.register(name="reader", code="def run(): pass", description="read web pages")

    results = registry.search("web fetch")
    # web_fetch matches both tokens in name+description, reader matches only "web"
    assert results[0].name == "web_fetch"


# ── seed installer ───────────────────────────────────────────────────────────


def test_ensure_seeds(tmp_path):
    """Seed skills are installed into an empty directory."""
    from alive.seeds import ensure_seeds

    skills_dir = tmp_path / "skills"
    ensure_seeds(skills_dir)

    for name in ["web_fetch", "file_reader", "shell_run"]:
        skill_dir = skills_dir / name
        assert skill_dir.exists(), f"Seed skill {name} not installed"
        assert (skill_dir / "skill.py").exists()
        assert (skill_dir / "test_skill.py").exists()
        assert (skill_dir / "metadata.json").exists()
        assert (skill_dir / "SKILL.md").exists()


def test_seeds_not_overwritten(tmp_path):
    """Existing skills are not overwritten by ensure_seeds."""
    from alive.seeds import ensure_seeds

    skills_dir = tmp_path / "skills"
    skills_dir.mkdir()
    custom_dir = skills_dir / "web_fetch"
    custom_dir.mkdir()
    marker = custom_dir / "metadata.json"
    marker.write_text('{"name": "web_fetch", "description": "custom"}')

    ensure_seeds(skills_dir)

    # Should still have the custom content, not the seed content
    data = json.loads(marker.read_text())
    assert data["description"] == "custom"


# ── registry with seeds ─────────────────────────────────────────────────────


def test_registry_loads_seeds(tmp_path):
    """A fresh registry should auto-install and load seed skills."""
    registry = SkillRegistry(skills_dir=tmp_path / "skills")
    names = [s.name for s in registry.list_all()]
    assert "web_fetch" in names
    assert "file_reader" in names
    assert "shell_run" in names


def test_registry_seed_confidence(tmp_path):
    """Seed skills should have 0.5 (neutral) confidence."""
    registry = SkillRegistry(skills_dir=tmp_path / "skills")
    for s in registry.list_all():
        assert s.confidence_score == 0.5


# ── Additional edge case tests ──────────────────────────────────────────────


def test_wilson_score_high_count():
    """Wilson score should approach 1.0 with many successes."""
    score = _wilson_score(1000, 0)
    assert score > 0.99


def test_wilson_score_bounded():
    """Wilson score should always be between 0 and 1."""
    for s, f in [(0, 0), (1, 0), (0, 1), (100, 0), (0, 100), (50, 50), (999, 1)]:
        score = _wilson_score(s, f)
        assert 0.0 <= score <= 1.0


def test_register_with_all_optional_fields(registry):
    """Register a skill with all optional parameters set."""
    info = registry.register(
        name="full",
        code='def run(x=1): return {"v": x}',
        description="full skill",
        dependencies=["numpy"],
        input_schema={"x": "int"},
        output_schema={"v": "int"},
        sample_input={"x": 42},
        test_code='from skill import run\ndef test_full(): assert run(1)["v"] == 1',
        skill_doc="# full\n\nComplete skill.\n",
    )
    assert info.dependencies == ["numpy"]
    assert info.input_schema == {"x": "int"}
    assert info.output_schema == {"v": "int"}
    assert info.sample_input == {"x": 42}


def test_load_all_ignores_malformed_metadata(tmp_path):
    """Registry should skip skills with corrupt metadata."""
    import alive.seeds._installer as installer

    original = installer._SEEDS_DIR
    installer._SEEDS_DIR = tmp_path / "empty_seeds"
    installer._SEEDS_DIR.mkdir()

    skills_dir = tmp_path / "skills"
    skills_dir.mkdir()

    # Good skill
    good = skills_dir / "good"
    good.mkdir()
    (good / "metadata.json").write_text(json.dumps({
        "name": "good",
        "description": "works",
    }))

    # Bad JSON
    bad = skills_dir / "bad"
    bad.mkdir()
    (bad / "metadata.json").write_text("not json")

    # Missing name key
    missing = skills_dir / "missing"
    missing.mkdir()
    (missing / "metadata.json").write_text(json.dumps({"description": "no name"}))

    reg = SkillRegistry(skills_dir=skills_dir)
    installer._SEEDS_DIR = original

    names = [s.name for s in reg.list_all()]
    assert "good" in names
    assert "bad" not in names


def test_load_all_unknown_fields_ignored(tmp_path):
    """Metadata with extra fields should be loaded, ignoring unknown fields."""
    import alive.seeds._installer as installer

    original = installer._SEEDS_DIR
    installer._SEEDS_DIR = tmp_path / "empty_seeds"
    installer._SEEDS_DIR.mkdir()

    skills_dir = tmp_path / "skills"
    skills_dir.mkdir()
    skill = skills_dir / "extra"
    skill.mkdir()
    (skill / "metadata.json").write_text(json.dumps({
        "name": "extra",
        "description": "has extra fields",
        "unknown_field": "should be ignored",
        "another_extra": 42,
    }))

    reg = SkillRegistry(skills_dir=skills_dir)
    installer._SEEDS_DIR = original

    info = reg.get_skill("extra")
    assert info.name == "extra"
    assert info.description == "has extra fields"


def test_update_stats_increments_correctly(registry):
    """Multiple update_stats calls should accumulate correctly."""
    registry.register(name="counter", code="def run(): pass", description="test")
    for _ in range(5):
        registry.update_stats("counter", success=True)
    for _ in range(2):
        registry.update_stats("counter", success=False)

    info = registry.get_skill("counter")
    assert info.times_used == 7
    assert info.success_count == 5
    assert info.fail_count == 2
    assert info.confidence_score > 0.0


def test_register_saves_metadata_to_disk(registry):
    """Newly registered skill should have metadata.json on disk."""
    registry.register(name="disk_check", code="def run(): pass", description="test")
    meta_path = registry._skills_dir / "disk_check" / "metadata.json"
    assert meta_path.exists()
    data = json.loads(meta_path.read_text())
    assert data["name"] == "disk_check"
    assert data["confidence_score"] == 0.5
    assert data["created_at"] != ""


def test_skillinfo_defaults():
    """SkillInfo with minimal args should have correct defaults."""
    info = SkillInfo(name="test", description="test")
    assert info.times_used == 0
    assert info.success_count == 0
    assert info.fail_count == 0
    assert info.dependencies == []
    assert info.confidence_score == 0.0
    assert info.domain == ""
    assert info.sample_input == {}


def test_search_case_insensitive(registry):
    """Search should be case-insensitive."""
    registry.register(name="WebFetcher", code="def run(): pass", description="Fetch URL Content")
    results = registry.search("webfetcher")
    assert len(results) == 1
    assert results[0].name == "WebFetcher"


def test_search_multi_token_scoring(registry):
    """Multi-token search should rank by match count."""
    registry.register(name="a", code="def run(): pass", description="web fetch api endpoint")
    registry.register(name="b", code="def run(): pass", description="web only")

    results = registry.search("web fetch api")
    assert results[0].name == "a"  # 3 matches vs 1


def test_list_all_empty_registry(registry):
    """Empty registry returns empty list."""
    assert registry.list_all() == []
