# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""Tests for installation knowledge capture and recall."""

import json
from pathlib import Path
from unittest.mock import patch

import pytest

# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture()
def knowledge_dir(tmp_path):
    """Override INSTALL_MEMORY_DIR to a temp directory."""
    d = tmp_path / "install_memories"
    d.mkdir()
    with patch("familiar.skills.host_setup.knowledge.INSTALL_MEMORY_DIR", d):
        yield d


# ---------------------------------------------------------------------------
# capture_install_knowledge
# ---------------------------------------------------------------------------


class TestCaptureInstallKnowledge:
    """Tests for capture_install_knowledge()."""

    def test_captures_successful_package_install(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge

        result = capture_install_knowledge(
            "install_package",
            {"packages": ["nmap"], "manager": "dnf", "reason": "network scanning"},
            {
                "installed": True,
                "packages": ["nmap"],
                "manager": "dnf",
                "output": "Installed: nmap-7.94",
            },
        )
        assert result is not None
        assert result.exists()
        content = result.read_text()
        assert "training_eligible: true" in content
        assert "nmap" in content
        assert "dnf" in content
        assert 'pair_type: "howto"' in content

    def test_skips_failed_package_install(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge

        result = capture_install_knowledge(
            "install_package",
            {"packages": ["badpkg"]},
            {"installed": False, "error": "not found"},
        )
        assert result is None

    def test_captures_successful_binary_install(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge

        result = capture_install_knowledge(
            "setup_binary",
            {"url": "https://example.com/rclone", "name": "rclone", "reason": "cloud sync"},
            {
                "installed": True,
                "name": "rclone",
                "path": "/home/user/.local/bin/rclone",
                "message": "rclone installed",
            },
        )
        assert result is not None
        content = result.read_text()
        assert "rclone" in content
        assert 'install_method: "binary"' in content

    def test_skips_failed_binary_install(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge

        result = capture_install_knowledge(
            "setup_binary",
            {"url": "https://example.com/tool", "name": "tool"},
            {"installed": False, "error": "download failed"},
        )
        assert result is None

    def test_captures_successful_script_install(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge

        result = capture_install_knowledge(
            "run_setup_script",
            {"script_path": "/tmp/install.sh", "interpreter": "bash"},
            {"success": True, "script": "/tmp/install.sh", "output": "Setup complete"},
        )
        assert result is not None
        content = result.read_text()
        assert 'install_method: "script"' in content
        assert 'difficulty: "advanced"' in content

    def test_skips_failed_script_install(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge

        result = capture_install_knowledge(
            "run_setup_script",
            {"script_path": "/tmp/install.sh"},
            {"success": False, "error": "script failed"},
        )
        assert result is None

    def test_handles_json_string_result(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge

        json_result = json.dumps(
            {
                "installed": True,
                "packages": ["git"],
                "manager": "apt-get",
                "output": "installed git 2.40",
            }
        )
        result = capture_install_knowledge("install_package", {"packages": ["git"]}, json_result)
        assert result is not None
        content = result.read_text()
        assert "git" in content

    def test_handles_invalid_json_string(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge

        result = capture_install_knowledge("install_package", {}, "not json at all")
        assert result is None

    def test_handles_non_dict_result(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge

        result = capture_install_knowledge("install_package", {}, 42)
        assert result is None

    def test_frontmatter_has_required_training_fields(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge

        result = capture_install_knowledge(
            "install_package",
            {"packages": ["curl"]},
            {"installed": True, "packages": ["curl"], "manager": "dnf", "output": "ok"},
        )
        content = result.read_text()
        assert "domain:" in content
        assert "subdomain:" in content
        assert "consent_version:" in content
        assert "created_at:" in content
        assert "pairs:" in content

    def test_multiple_packages_in_name(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge

        result = capture_install_knowledge(
            "install_package",
            {"packages": ["nmap", "iperf3"]},
            {"installed": True, "packages": ["nmap", "iperf3"], "manager": "dnf", "output": "ok"},
        )
        content = result.read_text()
        assert "nmap" in content
        assert "iperf3" in content

    def test_unknown_tool_name_returns_none(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge

        result = capture_install_knowledge("unknown_tool", {}, {"success": True})
        assert result is None


# ---------------------------------------------------------------------------
# search_install_knowledge
# ---------------------------------------------------------------------------


class TestSearchInstallKnowledge:
    """Tests for search_install_knowledge()."""

    def test_finds_matching_install(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import (
            capture_install_knowledge,
            search_install_knowledge,
        )

        capture_install_knowledge(
            "install_package",
            {"packages": ["postgresql"]},
            {"installed": True, "packages": ["postgresql"], "manager": "dnf", "output": "ok"},
        )
        matches = search_install_knowledge("postgresql")
        assert len(matches) == 1
        assert "postgresql" in matches[0].get("topic", "").lower()

    def test_returns_empty_for_no_match(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import search_install_knowledge

        matches = search_install_knowledge("nonexistent-package-xyz")
        assert matches == []

    def test_returns_empty_for_empty_query(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import search_install_knowledge

        matches = search_install_knowledge("")
        assert matches == []

    def test_returns_empty_when_dir_missing(self):
        from familiar.skills.host_setup.knowledge import search_install_knowledge

        with patch(
            "familiar.skills.host_setup.knowledge.INSTALL_MEMORY_DIR",
            Path("/nonexistent/path"),
        ):
            matches = search_install_knowledge("anything")
            assert matches == []

    def test_sorted_newest_first(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import (
            capture_install_knowledge,
            search_install_knowledge,
        )

        # Create two installs of related packages
        capture_install_knowledge(
            "install_package",
            {"packages": ["python-nmap"]},
            {"installed": True, "packages": ["python-nmap"], "manager": "pip", "output": "ok"},
        )
        capture_install_knowledge(
            "install_package",
            {"packages": ["nmap"]},
            {"installed": True, "packages": ["nmap"], "manager": "dnf", "output": "ok"},
        )
        matches = search_install_knowledge("nmap")
        assert len(matches) == 2
        # Newest should be first (both contain "nmap")
        assert matches[0].get("created_at", "") >= matches[1].get("created_at", "")


# ---------------------------------------------------------------------------
# search_install_history tool handler
# ---------------------------------------------------------------------------


class TestSearchInstallHistoryTool:
    """Tests for the search_install_history tool handler."""

    def test_returns_matches(self, knowledge_dir):
        from familiar.skills.host_setup.knowledge import capture_install_knowledge
        from familiar.skills.host_setup.skill import search_install_history

        capture_install_knowledge(
            "install_package",
            {"packages": ["redis"]},
            {"installed": True, "packages": ["redis"], "manager": "dnf", "output": "ok"},
        )
        result = search_install_history({"query": "redis"})
        assert result["count"] == 1
        assert len(result["matches"]) == 1

    def test_returns_empty_on_no_match(self, knowledge_dir):
        from familiar.skills.host_setup.skill import search_install_history

        result = search_install_history({"query": "nonexistent"})
        assert result["count"] == 0
        assert result["matches"] == []

    def test_requires_query(self):
        from familiar.skills.host_setup.skill import search_install_history

        result = search_install_history({})
        assert "error" in result


# ---------------------------------------------------------------------------
# TOOLS registry
# ---------------------------------------------------------------------------


class TestToolsRegistry:
    """Verify the new tool is properly registered."""

    def test_search_install_history_in_tools(self):
        from familiar.skills.host_setup.skill import TOOLS

        names = [t["name"] for t in TOOLS]
        assert "search_install_history" in names

    def test_tool_has_required_fields(self):
        from familiar.skills.host_setup.skill import TOOLS

        tool = next(t for t in TOOLS if t["name"] == "search_install_history")
        assert "description" in tool
        assert "input_schema" in tool
        assert "handler" in tool
        assert "category" in tool
        assert tool["category"] == "host_setup"

    def test_tool_count(self):
        from familiar.skills.host_setup.skill import TOOLS

        assert len(TOOLS) == 9  # 8 original + 1 new


# ---------------------------------------------------------------------------
# Taxonomy
# ---------------------------------------------------------------------------


class TestTaxonomy:
    """Verify system_administration is in the training taxonomy."""

    def test_system_administration_subdomain_exists(self):
        from familiar.skills.training.taxonomy import DOMAIN_TAXONOMY

        practical = DOMAIN_TAXONOMY.get("practical", {})
        subdomains = practical.get("subdomains", {})
        assert "system_administration" in subdomains

    def test_system_administration_validates(self):
        from familiar.skills.training.taxonomy import validate_domain

        valid, msg = validate_domain("practical", "system_administration")
        assert valid, msg


# ---------------------------------------------------------------------------
# Helper job class
# ---------------------------------------------------------------------------


class TestHelperSkillWeights:
    """Verify Helper has the new search_install_history weight."""

    def test_has_search_install_history(self):
        from familiar.jobs.helper import HELPER

        assert "search_install_history" in HELPER.skill_weights
        assert HELPER.skill_weights["search_install_history"] > 0
