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

"""Tests: GuidanceLoader — stub templates + auto-generation from skills."""

from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

from familiar.core.guidance import GuidanceLoader

# ── Minimal fakes for Skill/Tool (avoids importing the full stack) ────────


@dataclass
class _FakeTool:
    name: str = "fake_tool"
    description: str = "A fake tool for testing"
    input_schema: dict = field(default_factory=dict)
    input_examples: Optional[list] = None
    handler: object = None
    category: str = "general"
    requires_confirmation: bool = False


@dataclass
class _FakeSkill:
    name: str = "fake_skill"
    path: Path = field(default_factory=lambda: Path("."))
    description: str = "A fake skill"
    summary: str = "Fake"
    tools: list = field(default_factory=list)
    config: dict = field(default_factory=dict)
    enabled: bool = True


class TestGuidanceLoaderInit:
    """GuidanceLoader has the right attributes on construction."""

    def test_has_skill_guidance(self):
        loader = GuidanceLoader()
        assert hasattr(loader, "skill_guidance")
        assert isinstance(loader.skill_guidance, dict)

    def test_skill_guidance_initially_empty(self):
        loader = GuidanceLoader()
        assert loader.skill_guidance == {}


class TestTemplateOps:
    """Template register/load/list still work after the rewrite."""

    def test_register_and_load(self):
        loader = GuidanceLoader()
        loader.register_template("intake", {"steps": ["greet", "collect"]})
        assert loader.load_template("intake") == {"steps": ["greet", "collect"]}

    def test_load_missing_returns_none(self):
        loader = GuidanceLoader()
        assert loader.load_template("nonexistent") is None

    def test_list_templates(self):
        loader = GuidanceLoader()
        loader.register_template("a", {})
        loader.register_template("b", {})
        assert sorted(loader.list_templates()) == ["a", "b"]


class TestGenerateFromSkills:
    """generate_from_skills auto-generates guidance matching ToolRouter's interface."""

    def test_empty_skills(self):
        loader = GuidanceLoader()
        loader.generate_from_skills({})
        assert loader.skill_guidance == {}

    def test_single_skill_single_tool(self):
        tool = _FakeTool(name="send_email", description="Send an email message")
        skill = _FakeSkill(name="email", tools=[tool])
        loader = GuidanceLoader()
        loader.generate_from_skills({"email": skill})

        assert "email" in loader.skill_guidance
        sg = loader.skill_guidance["email"]
        assert "send_email" in sg.tools
        assert len(sg.intent_patterns) == 1
        assert sg.intent_patterns[0].suggest == "send_email"

    def test_disabled_skill_skipped(self):
        tool = _FakeTool(name="noop")
        skill = _FakeSkill(name="disabled", tools=[tool], enabled=False)
        loader = GuidanceLoader()
        loader.generate_from_skills({"disabled": skill})
        assert loader.skill_guidance == {}

    def test_stop_words_excluded_from_keywords(self):
        tool = _FakeTool(name="get_the_data", description="Get the data for a user")
        skill = _FakeSkill(name="data", tools=[tool])
        loader = GuidanceLoader()
        loader.generate_from_skills({"data": skill})

        sg = loader.skill_guidance["data"]
        ip = sg.intent_patterns[0]
        for stop in ("get", "the", "a", "for"):
            assert stop not in ip.keywords

    def test_example_prompts_from_input_examples(self):
        tool = _FakeTool(
            name="search_docs",
            description="Search documents",
            input_examples=[{"query": "find grant guidelines"}],
        )
        skill = _FakeSkill(name="docs", tools=[tool])
        loader = GuidanceLoader()
        loader.generate_from_skills({"docs": skill})

        tg = loader.skill_guidance["docs"].tools["search_docs"]
        assert "find grant guidelines" in tg.example_prompts

    def test_keywords_capped_at_ten(self):
        # Description with many unique words to exceed cap
        desc = " ".join(f"word{i}" for i in range(20))
        tool = _FakeTool(name="big_tool", description=desc)
        skill = _FakeSkill(name="big", tools=[tool])
        loader = GuidanceLoader()
        loader.generate_from_skills({"big": skill})

        ip = loader.skill_guidance["big"].intent_patterns[0]
        assert len(ip.keywords) <= 10


class TestToolRouterInterface:
    """Generated data matches what ToolRouter._build_index() expects."""

    def test_sg_priority(self):
        tool = _FakeTool(name="t1")
        skill = _FakeSkill(name="s1", tools=[tool])
        loader = GuidanceLoader()
        loader.generate_from_skills({"s1": skill})

        sg = loader.skill_guidance["s1"]
        assert isinstance(sg.priority, int)

    def test_sg_tools_dict(self):
        tool = _FakeTool(name="t1")
        skill = _FakeSkill(name="s1", tools=[tool])
        loader = GuidanceLoader()
        loader.generate_from_skills({"s1": skill})

        sg = loader.skill_guidance["s1"]
        assert isinstance(sg.tools, dict)
        tg = sg.tools["t1"]
        assert hasattr(tg, "parsed_chains")
        assert isinstance(tg.parsed_chains, list)

    def test_intent_pattern_fields(self):
        tool = _FakeTool(name="check_inbox", description="Check email inbox")
        skill = _FakeSkill(name="email", tools=[tool])
        loader = GuidanceLoader()
        loader.generate_from_skills({"email": skill})

        sg = loader.skill_guidance["email"]
        ip = sg.intent_patterns[0]
        assert hasattr(ip, "suggest")
        assert hasattr(ip, "keywords")
        assert ip.suggest == "check_inbox"
        assert isinstance(ip.keywords, list)
