# 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 Sprint 2 skills: Brave Search, News, Google Trends, Websearch enhancements."""

from unittest.mock import MagicMock, patch

HTTPX_GET = "familiar.skills._api_utils.httpx.get"
CACHE_GET = "familiar.core.api_cache.get_cached"
CACHE_SET = "familiar.core.api_cache.set_cached"
HTTP_GET = "familiar.skills.websearch.skill._http_get"


def _mock_response(data, status=200):
    resp = MagicMock()
    resp.status_code = status
    resp.json.return_value = data
    resp.content = b'{"ok": true}'
    return resp


# ── Brave Search ─────────────────────────────────────────────────────────


class TestBraveSearch:
    def test_no_query(self):
        from familiar.skills.brave_search.skill import brave_search

        result = brave_search({"query": ""})
        assert "provide a search query" in result.lower()

    def test_no_api_key(self):
        from familiar.skills.brave_search.skill import brave_search

        with patch("familiar.skills.brave_search.skill.get_env", return_value=""):
            result = brave_search({"query": "test"})
        assert "not configured" in result.lower()

    def test_success(self):
        from familiar.skills.brave_search.skill import brave_search

        resp = _mock_response(
            {
                "web": {
                    "results": [
                        {
                            "title": "Python Official Site",
                            "url": "https://python.org",
                            "description": "Official Python programming language site",
                            "age": "2 days ago",
                        },
                        {
                            "title": "Python Tutorial",
                            "url": "https://docs.python.org/tutorial",
                            "description": "The Python Tutorial",
                        },
                    ]
                }
            }
        )

        with (
            patch("familiar.skills.brave_search.skill.get_env", return_value="test_key"),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = brave_search({"query": "python"})
        assert "Python Official Site" in result
        assert "python.org" in result

    def test_no_results(self):
        from familiar.skills.brave_search.skill import brave_search

        resp = _mock_response({"web": {"results": []}})

        with (
            patch("familiar.skills.brave_search.skill.get_env", return_value="test_key"),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = brave_search({"query": "zzzznonexistent"})
        assert "No results found" in result


class TestBraveNews:
    def test_no_query(self):
        from familiar.skills.brave_search.skill import brave_news

        result = brave_news({"query": ""})
        assert "provide a news search query" in result.lower()

    def test_success(self):
        from familiar.skills.brave_search.skill import brave_news

        resp = _mock_response(
            {
                "results": [
                    {
                        "title": "AI Breakthrough",
                        "url": "https://example.com/ai",
                        "description": "Major AI advancement announced",
                        "meta_url": {"hostname": "example.com"},
                        "age": "1 hour ago",
                    }
                ]
            }
        )

        with (
            patch("familiar.skills.brave_search.skill.get_env", return_value="test_key"),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = brave_news({"query": "AI"})
        assert "AI Breakthrough" in result
        assert "example.com" in result


class TestBraveSuggest:
    def test_no_query(self):
        from familiar.skills.brave_search.skill import brave_suggest

        result = brave_suggest({"query": ""})
        assert "provide a query" in result.lower()

    def test_success(self):
        from familiar.skills.brave_search.skill import brave_suggest

        resp = _mock_response(
            {"results": [{"query": "python tutorial"}, {"query": "python download"}]}
        )

        with (
            patch("familiar.skills.brave_search.skill.get_env", return_value="test_key"),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = brave_suggest({"query": "python"})
        assert "python tutorial" in result


class TestBraveSearchTools:
    def test_tools_registered(self):
        from familiar.skills.brave_search.skill import TOOLS

        names = [t["name"] for t in TOOLS]
        assert "brave_search" in names
        assert "brave_news" in names
        assert "brave_suggest" in names

    def test_all_have_handlers(self):
        from familiar.skills.brave_search.skill import TOOLS

        for tool in TOOLS:
            assert callable(tool["handler"])


# ── News Aggregation ─────────────────────────────────────────────────────


class TestTopHeadlines:
    def test_no_api_key(self):
        from familiar.skills.news.skill import top_headlines

        with (
            patch("familiar.skills.news.skill.get_env", return_value=""),
        ):
            result = top_headlines({})
        assert "no news api key" in result.lower()

    def test_invalid_category(self):
        from familiar.skills.news.skill import top_headlines

        with patch("familiar.skills.news.skill.get_env", side_effect=lambda k: "key"):
            result = top_headlines({"category": "invalid_cat"})
        assert "Invalid category" in result

    def test_gnews_success(self):
        from familiar.skills.news.skill import top_headlines

        resp = _mock_response(
            {
                "articles": [
                    {
                        "title": "Breaking News Story",
                        "url": "https://news.example.com/story",
                        "description": "A major story happened today",
                        "source": {"name": "Example News"},
                        "publishedAt": "2026-03-06T12:00:00Z",
                    }
                ]
            }
        )

        with (
            patch("familiar.skills.news.skill.get_env", side_effect=lambda k: "gnews_key"),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = top_headlines({"category": "technology"})
        assert "Breaking News Story" in result
        assert "Example News" in result

    def test_newsdata_fallback(self):
        from familiar.skills.news.skill import top_headlines

        resp = _mock_response(
            {
                "results": [
                    {
                        "title": "Newsdata Story",
                        "link": "https://newsdata.example.com/story",
                        "description": "From newsdata.io",
                        "source_id": "newsdata_source",
                        "pubDate": "2026-03-06 12:00:00",
                    }
                ]
            }
        )

        def env_side_effect(k):
            if k == "GNEWS_API_KEY":
                return ""
            return "newsdata_key"

        with (
            patch("familiar.skills.news.skill.get_env", side_effect=env_side_effect),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = top_headlines({})
        assert "Newsdata Story" in result


class TestSearchNews:
    def test_no_query(self):
        from familiar.skills.news.skill import search_news

        result = search_news({"query": ""})
        assert "provide a search query" in result.lower()

    def test_success(self):
        from familiar.skills.news.skill import search_news

        resp = _mock_response(
            {
                "totalArticles": 50,
                "articles": [
                    {
                        "title": "Climate Report 2026",
                        "url": "https://example.com/climate",
                        "description": "New climate findings",
                        "source": {"name": "Science Daily"},
                        "publishedAt": "2026-03-05T10:00:00Z",
                    }
                ],
            }
        )

        with (
            patch("familiar.skills.news.skill.get_env", side_effect=lambda k: "gnews_key"),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = search_news({"query": "climate change"})
        assert "Climate Report 2026" in result


class TestNewsSources:
    def test_lists_categories(self):
        from familiar.skills.news.skill import news_sources

        with patch("familiar.skills.news.skill.get_env", return_value=""):
            result = news_sources({})
        assert "technology" in result.lower()
        assert "business" in result.lower()
        assert "API Status" in result


class TestNewsTools:
    def test_tools_registered(self):
        from familiar.skills.news.skill import TOOLS

        names = [t["name"] for t in TOOLS]
        assert "top_headlines" in names
        assert "search_news" in names
        assert "news_sources" in names


# ── Google Trends ────────────────────────────────────────────────────────


class TestTrendingNow:
    def test_no_api_key(self):
        from familiar.skills.google_trends.skill import trending_now

        with patch("familiar.skills.google_trends.skill.get_env", return_value=""):
            result = trending_now({})
        assert "not configured" in result.lower()

    def test_success(self):
        from familiar.skills.google_trends.skill import trending_now

        resp = _mock_response(
            {
                "trendingSearches": [
                    {
                        "title": "AI News",
                        "formattedTraffic": "500K+",
                        "relatedQueries": ["machine learning", "ChatGPT"],
                    },
                    {
                        "title": "Election Results",
                        "formattedTraffic": "200K+",
                    },
                ]
            }
        )

        with (
            patch("familiar.skills.google_trends.skill.get_env", return_value="test_key"),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = trending_now({"geo": "US"})
        assert "AI News" in result
        assert "500K+" in result


class TestInterestOverTime:
    def test_no_terms(self):
        from familiar.skills.google_trends.skill import interest_over_time

        with patch("familiar.skills.google_trends.skill.get_env", return_value="test_key"):
            result = interest_over_time({"terms": []})
        assert "provide one or more" in result.lower()

    def test_too_many_terms(self):
        from familiar.skills.google_trends.skill import interest_over_time

        with patch("familiar.skills.google_trends.skill.get_env", return_value="test_key"):
            result = interest_over_time({"terms": ["a", "b", "c", "d", "e", "f"]})
        assert "Maximum 5" in result

    def test_success(self):
        from familiar.skills.google_trends.skill import interest_over_time

        resp = _mock_response(
            {
                "timelineData": [
                    {
                        "formattedTime": "Jan 2026",
                        "value": [75],
                    },
                    {
                        "formattedTime": "Feb 2026",
                        "value": [90],
                    },
                ]
            }
        )

        with (
            patch("familiar.skills.google_trends.skill.get_env", return_value="test_key"),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = interest_over_time({"terms": ["Python"]})
        assert "Python" in result
        assert "Jan 2026" in result


class TestRelatedQueries:
    def test_no_term(self):
        from familiar.skills.google_trends.skill import related_queries

        with patch("familiar.skills.google_trends.skill.get_env", return_value="test_key"):
            result = related_queries({"term": ""})
        assert "provide a search term" in result.lower()

    def test_success(self):
        from familiar.skills.google_trends.skill import related_queries

        resp = _mock_response(
            {
                "top": [
                    {"query": "python tutorial", "value": "100"},
                    {"query": "python download", "value": "85"},
                ],
                "rising": [
                    {"query": "python 3.13", "value": "Breakout"},
                ],
            }
        )

        with (
            patch("familiar.skills.google_trends.skill.get_env", return_value="test_key"),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = related_queries({"term": "python"})
        assert "python tutorial" in result
        assert "python 3.13" in result
        assert "Rising" in result


class TestGoogleTrendsTools:
    def test_tools_registered(self):
        from familiar.skills.google_trends.skill import TOOLS

        names = [t["name"] for t in TOOLS]
        assert "trending_now" in names
        assert "interest_over_time" in names
        assert "related_queries" in names


# ── Websearch Enhancements ───────────────────────────────────────────────


class TestInstantAnswer:
    def test_no_query(self):
        from familiar.skills.websearch.skill import instant_answer

        result = instant_answer({"query": ""})
        assert "provide a query" in result.lower()

    def test_abstract_response(self):
        from familiar.skills.websearch.skill import instant_answer

        ddg_response = (
            '{"Heading": "Python (programming language)",'
            '"AbstractText": "Python is a high-level programming language.",'
            '"AbstractSource": "Wikipedia",'
            '"AbstractURL": "https://en.wikipedia.org/wiki/Python_(programming_language)",'
            '"Answer": "",'
            '"Definition": "",'
            '"Infobox": {},'
            '"RelatedTopics": []}'
        )

        with patch(HTTP_GET, return_value=ddg_response):
            result = instant_answer({"query": "Python programming"})
        assert "Python (programming language)" in result
        assert "high-level programming language" in result
        assert "Wikipedia" in result

    def test_infobox_response(self):
        from familiar.skills.websearch.skill import instant_answer

        ddg_response = (
            '{"Heading": "Albert Einstein",'
            '"AbstractText": "Physicist",'
            '"AbstractSource": "Wikipedia",'
            '"AbstractURL": "",'
            '"Answer": "",'
            '"Definition": "",'
            '"Infobox": {"content": [{"label": "Born", "value": "1879"}]},'
            '"RelatedTopics": []}'
        )

        with patch(HTTP_GET, return_value=ddg_response):
            result = instant_answer({"query": "Albert Einstein"})
        assert "Albert Einstein" in result
        assert "Born" in result
        assert "1879" in result

    def test_no_answer_available(self):
        from familiar.skills.websearch.skill import instant_answer

        ddg_response = (
            '{"Heading": "",'
            '"AbstractText": "",'
            '"Answer": "",'
            '"Definition": "",'
            '"Infobox": {},'
            '"RelatedTopics": []}'
        )

        with patch(HTTP_GET, return_value=ddg_response):
            result = instant_answer({"query": "asdfghjkl123"})
        assert "No instant answer" in result

    def test_api_failure(self):
        from familiar.skills.websearch.skill import instant_answer

        with patch(HTTP_GET, side_effect=Exception("connection error")):
            result = instant_answer({"query": "test"})
        assert "Could not get instant answer" in result


class TestSearxngSearch:
    def test_not_configured(self):
        from familiar.skills.websearch.skill import searxng_search

        with patch("familiar.skills.websearch.skill._CONFIG", {"backend": "duckduckgo"}):
            result = searxng_search({"query": "test"})
        assert "not configured" in result.lower()

    def test_no_query(self):
        from familiar.skills.websearch.skill import searxng_search

        with patch(
            "familiar.skills.websearch.skill._CONFIG",
            {"backend": "searxng", "searxng_url": "http://localhost:8888", "timeout": 15},
        ):
            result = searxng_search({"query": ""})
        assert "provide a search query" in result.lower()

    def test_invalid_category(self):
        from familiar.skills.websearch.skill import searxng_search

        with patch(
            "familiar.skills.websearch.skill._CONFIG",
            {"backend": "searxng", "searxng_url": "http://localhost:8888", "timeout": 15},
        ):
            result = searxng_search({"query": "test", "category": "invalid"})
        assert "Invalid category" in result

    def test_success(self):
        from familiar.skills.websearch.skill import searxng_search

        searxng_response = (
            '{"results": ['
            '{"title": "Result 1", "url": "https://example.com/1", "content": "First result"},'
            '{"title": "Result 2", "url": "https://example.com/2", "content": "Second result"}'
            "]}"
        )

        with (
            patch(
                "familiar.skills.websearch.skill._CONFIG",
                {
                    "backend": "searxng",
                    "searxng_url": "http://localhost:8888",
                    "timeout": 15,
                    "user_agent": "test",
                },
            ),
            patch(HTTP_GET, return_value=searxng_response),
        ):
            result = searxng_search({"query": "test", "category": "general"})
        assert "Result 1" in result
        assert "Result 2" in result

    def test_image_category_fields(self):
        from familiar.skills.websearch.skill import searxng_search

        searxng_response = (
            '{"results": ['
            '{"title": "Cat Photo", "url": "https://example.com/cat",'
            '"img_src": "https://example.com/cat.jpg", "resolution": "1920x1080",'
            '"content": ""}'
            "]}"
        )

        with (
            patch(
                "familiar.skills.websearch.skill._CONFIG",
                {
                    "backend": "searxng",
                    "searxng_url": "http://localhost:8888",
                    "timeout": 15,
                    "user_agent": "test",
                },
            ),
            patch(HTTP_GET, return_value=searxng_response),
        ):
            result = searxng_search({"query": "cat", "category": "images"})
        assert "Cat Photo" in result
        assert "1920x1080" in result


class TestWebsearchEnhancedTools:
    def test_new_tools_registered(self):
        from familiar.skills.websearch.skill import TOOLS

        names = [t["name"] for t in TOOLS]
        assert "instant_answer" in names
        assert "searxng_search" in names
        # Existing tools still present
        assert "web_search" in names
        assert "web_read" in names
        assert "search_nonprofit" in names
