# 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

"""
News Aggregation Skill

Search and browse news from multiple sources.
Primary: GNews API. Fallback: Newsdata.io.

Setup:
    GNEWS_API_KEY=your_key     (get at https://gnews.io/ — 100 req/day free)
    NEWSDATA_API_KEY=your_key  (get at https://newsdata.io/ — 200 req/day free)

At least one API key is required.
"""

import logging
from datetime import datetime, timedelta, timezone

from familiar.core.api_cache import cached
from familiar.skills._api_utils import (
    RATE_LIMITERS,
    APIError,
    api_get,
    get_env,
)

logger = logging.getLogger(__name__)

GNEWS_BASE = "https://gnews.io/api/v4"
NEWSDATA_BASE = "https://newsdata.io/api/1"

_gnews_limiter = RATE_LIMITERS["gnews"]
_newsdata_limiter = RATE_LIMITERS["newsdata"]


def _gnews_key() -> str:
    return get_env("GNEWS_API_KEY") or ""


def _newsdata_key() -> str:
    return get_env("NEWSDATA_API_KEY") or ""


def _has_any_key() -> bool:
    return bool(_gnews_key() or _newsdata_key())


# ---------------------------------------------------------------------------
# GNews backend
# ---------------------------------------------------------------------------


@cached(prefix="gnews_headlines", ttl=1800)
def _gnews_headlines(category: str, country: str, lang: str, max_results: int) -> dict:
    params = {
        "apikey": _gnews_key(),
        "lang": lang,
        "max": max_results,
    }
    if category:
        params["category"] = category
    if country:
        params["country"] = country
    return api_get(
        f"{GNEWS_BASE}/top-headlines",
        params=params,
        service_name="GNews",
    )


@cached(prefix="gnews_search", ttl=1800)
def _gnews_search(query: str, lang: str, max_results: int, from_date: str) -> dict:
    params = {
        "apikey": _gnews_key(),
        "q": query,
        "lang": lang,
        "max": max_results,
    }
    if from_date:
        params["from"] = from_date
    return api_get(
        f"{GNEWS_BASE}/search",
        params=params,
        service_name="GNews",
    )


# ---------------------------------------------------------------------------
# Newsdata.io backend
# ---------------------------------------------------------------------------


@cached(prefix="newsdata_latest", ttl=1800)
def _newsdata_latest(category: str, country: str, lang: str, size: int) -> dict:
    params = {
        "apikey": _newsdata_key(),
        "language": lang,
        "size": size,
    }
    if category:
        params["category"] = category
    if country:
        params["country"] = country
    return api_get(
        f"{NEWSDATA_BASE}/latest",
        params=params,
        service_name="Newsdata.io",
    )


@cached(prefix="newsdata_search", ttl=1800)
def _newsdata_search(query: str, lang: str, size: int) -> dict:
    params = {
        "apikey": _newsdata_key(),
        "q": query,
        "language": lang,
        "size": size,
    }
    return api_get(
        f"{NEWSDATA_BASE}/latest",
        params=params,
        service_name="Newsdata.io",
    )


# ---------------------------------------------------------------------------
# Result formatting
# ---------------------------------------------------------------------------


def _format_gnews_articles(articles: list, limit: int) -> list:
    """Format GNews articles into display lines."""
    lines = []
    for i, a in enumerate(articles[:limit], 1):
        title = a.get("title", "Untitled")
        url = a.get("url", "")
        description = a.get("description", "")
        source = a.get("source", {}).get("name", "")
        published = a.get("publishedAt", "")

        lines.append(f"{i}. **{title}**")
        if source:
            lines.append(f"   Source: {source}")
        if published:
            lines.append(f"   Published: {published[:16]}")
        if url:
            lines.append(f"   {url}")
        if description:
            lines.append(f"   {description[:250]}")
        lines.append("")
    return lines


def _format_newsdata_articles(articles: list, limit: int) -> list:
    """Format Newsdata.io articles into display lines."""
    lines = []
    for i, a in enumerate(articles[:limit], 1):
        title = a.get("title", "Untitled")
        link = a.get("link", "")
        description = a.get("description", "")
        source = a.get("source_id", "")
        published = a.get("pubDate", "")

        lines.append(f"{i}. **{title}**")
        if source:
            lines.append(f"   Source: {source}")
        if published:
            lines.append(f"   Published: {published[:16]}")
        if link:
            lines.append(f"   {link}")
        if description:
            desc = description[:250] if description else ""
            if desc:
                lines.append(f"   {desc}")
        lines.append("")
    return lines


# ---------------------------------------------------------------------------
# Tool handlers
# ---------------------------------------------------------------------------


def top_headlines(data: dict) -> str:
    """Get top news headlines by category and/or country."""
    if not _has_any_key():
        return (
            "No news API key configured.\n"
            "Set GNEWS_API_KEY (https://gnews.io/) or "
            "NEWSDATA_API_KEY (https://newsdata.io/)."
        )

    category = data.get("category", "").lower()
    country = data.get("country", "").lower()
    lang = data.get("language", "en").lower()
    limit = min(int(data.get("limit", 10)), 20)

    # Valid GNews categories
    valid_categories = [
        "general",
        "world",
        "nation",
        "business",
        "technology",
        "entertainment",
        "sports",
        "science",
        "health",
    ]
    if category and category not in valid_categories:
        return f"Invalid category '{category}'. Valid categories: " + ", ".join(valid_categories)

    # Try GNews first
    if _gnews_key() and _gnews_limiter.acquire():
        try:
            result = _gnews_headlines(category, country, lang, limit)
            articles = result.get("articles", [])
            if articles:
                header = "**Top Headlines"
                if category:
                    header += f" — {category.title()}"
                if country:
                    header += f" ({country.upper()})"
                header += ":**"
                lines = [header, ""]
                lines.extend(_format_gnews_articles(articles, limit))
                return "\n".join(lines)
        except (APIError, Exception) as e:
            logger.warning("GNews headlines failed: %s", e)

    # Fallback to Newsdata.io
    if _newsdata_key() and _newsdata_limiter.acquire():
        try:
            result = _newsdata_latest(category, country, lang, limit)
            articles = result.get("results", [])
            if articles:
                header = "**Top Headlines"
                if category:
                    header += f" — {category.title()}"
                header += ":**"
                lines = [header, ""]
                lines.extend(_format_newsdata_articles(articles, limit))
                return "\n".join(lines)
        except (APIError, Exception) as e:
            logger.warning("Newsdata headlines failed: %s", e)

    return "Could not fetch headlines. API rate limit may be reached."


def search_news(data: dict) -> str:
    """Search news articles by keyword."""
    query = data.get("query", "").strip()
    if not query:
        return "Please provide a search query."

    if not _has_any_key():
        return "No news API key configured.\nSet GNEWS_API_KEY or NEWSDATA_API_KEY."

    lang = data.get("language", "en").lower()
    limit = min(int(data.get("limit", 10)), 20)
    days_back = int(data.get("days_back", 7))

    from_date = ""
    if days_back > 0:
        dt = datetime.now(timezone.utc) - timedelta(days=days_back)
        from_date = dt.strftime("%Y-%m-%dT%H:%M:%SZ")

    # Try GNews first
    if _gnews_key() and _gnews_limiter.acquire():
        try:
            result = _gnews_search(query, lang, limit, from_date)
            articles = result.get("articles", [])
            if articles:
                lines = [f"**News: {query}**", ""]
                lines.extend(_format_gnews_articles(articles, limit))
                total = result.get("totalArticles", len(articles))
                if total > limit:
                    lines.append(f"_Showing {limit} of {total} articles._")
                return "\n".join(lines)
        except (APIError, Exception) as e:
            logger.warning("GNews search failed: %s", e)

    # Fallback to Newsdata.io
    if _newsdata_key() and _newsdata_limiter.acquire():
        try:
            result = _newsdata_search(query, lang, limit)
            articles = result.get("results", [])
            if articles:
                lines = [f"**News: {query}**", ""]
                lines.extend(_format_newsdata_articles(articles, limit))
                return "\n".join(lines)
        except (APIError, Exception) as e:
            logger.warning("Newsdata search failed: %s", e)

    return f"No news found for '{query}'. API rate limit may be reached."


def news_sources(data: dict) -> str:
    """List available news source categories."""
    # This is informational — no API call needed
    categories = [
        ("general", "General news and current events"),
        ("world", "International news"),
        ("nation", "National/domestic news"),
        ("business", "Business and finance"),
        ("technology", "Tech industry and gadgets"),
        ("entertainment", "Movies, music, celebrity"),
        ("sports", "Sports news and scores"),
        ("science", "Scientific discoveries and research"),
        ("health", "Health and medical news"),
    ]

    lines = ["**Available News Categories:**", ""]
    for cat, desc in categories:
        lines.append(f"  - **{cat}**: {desc}")

    lines.append("")
    lines.append("Use these with `top_headlines` (category parameter).")
    lines.append("")

    # Show API status
    gnews = "configured" if _gnews_key() else "not set"
    newsdata = "configured" if _newsdata_key() else "not set"
    lines.append(f"**API Status:** GNews: {gnews} | Newsdata: {newsdata}")

    return "\n".join(lines)


# ---------------------------------------------------------------------------
# Tool definitions
# ---------------------------------------------------------------------------

TOOLS = [
    {
        "name": "top_headlines",
        "description": (
            "Get top news headlines, optionally filtered by category and country."
            " Categories: general, world, business, technology, entertainment,"
            " sports, science, health. Uses GNews or Newsdata.io."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "category": {
                    "type": "string",
                    "description": (
                        "News category: general, world, nation, business,"
                        " technology, entertainment, sports, science, health"
                    ),
                    "default": "general",
                },
                "country": {
                    "type": "string",
                    "description": "Two-letter country code (e.g., 'us', 'gb', 'de')",
                    "default": "",
                },
                "language": {
                    "type": "string",
                    "description": "Language code (e.g., 'en', 'es', 'fr')",
                    "default": "en",
                },
                "limit": {
                    "type": "integer",
                    "description": "Max results (1-20, default 10)",
                    "default": 10,
                },
            },
        },
        "handler": top_headlines,
        "category": "news",
    },
    {
        "name": "search_news",
        "description": (
            "Search news articles by keyword across multiple sources."
            " Returns recent articles with titles, sources, dates, and summaries."
            " Supports time filtering via days_back parameter."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "News search query",
                },
                "language": {
                    "type": "string",
                    "description": "Language code (default 'en')",
                    "default": "en",
                },
                "limit": {
                    "type": "integer",
                    "description": "Max results (1-20, default 10)",
                    "default": 10,
                },
                "days_back": {
                    "type": "integer",
                    "description": "Search articles from the past N days (default 7)",
                    "default": 7,
                },
            },
            "required": ["query"],
        },
        "handler": search_news,
        "category": "news",
    },
    {
        "name": "news_sources",
        "description": (
            "List available news categories and check API configuration status."
            " Shows which news APIs are configured and ready to use."
        ),
        "input_schema": {
            "type": "object",
            "properties": {},
        },
        "handler": news_sources,
        "category": "news",
    },
]
