from __future__ import annotations

TOOL_NAME = "interest_news_brief"
TOOL_DESCRIPTION = """Read interests from user.md, look up current internet news or facts for the most relevant topics, and return a short playful report. Uses only user.md and web sources, never email or calendar items."""
TOOL_ARGS = '{"max_topics": {"type": "integer", "default": 3, "description": "Maximum number of interest topics to cover"}, "language": {"type": "string", "default": "nl", "description": "Report language: nl or en"}, "include_links": {"type": "boolean", "default": true, "description": "Include source links under each item"}, "topics": {"type": "string", "description": "Optional comma-separated override topics instead of auto-detecting from user.md"}, "user_file": {"type": "string", "default": "user.md", "description": "Workspace-relative user profile file to read"}}'

import json
import re
import xml.etree.ElementTree as ET
from datetime import datetime
from html import unescape
from pathlib import Path

import requests
from tools._std import tool_ok, tool_error


_ROOT = Path.cwd().resolve()
_SETTINGS_FILE = _ROOT / "settings.json"

_TOPIC_PROFILES = [
    {
        "id": "ajax",
        "label_nl": "Ajax",
        "label_en": "Ajax",
        "query": "Ajax Amsterdam latest news",
        "why_nl": "sluit direct aan op je Ajax-interesse",
        "why_en": "directly matches your Ajax interest",
        "tokens": ["ajax", "voetbalclub", "amsterdam"],
    },
    {
        "id": "ai_automation",
        "label_nl": "AI en automatisering",
        "label_en": "AI and automation",
        "query": "AI automation agents latest news",
        "why_nl": "past bij je agent- en automatiseringswerk",
        "why_en": "fits your agent and automation work",
        "tokens": ["ai", "automation", "automatisering", "taskade", "bubble", "llm", "chatgpt", "kimi", "agent"],
    },
    {
        "id": "apple_china",
        "label_nl": "Apple en China App Store",
        "label_en": "Apple and China App Store",
        "query": "Apple App Store China developer latest news",
        "why_nl": "raakt je iOS-werk en China App Store focus",
        "why_en": "touches your iOS work and China App Store focus",
        "tokens": ["apple", "ios", "ipad", "china", "app store", "storefront", "developer"],
    },
    {
        "id": "insurance_health",
        "label_nl": "zorg en verzekeringen",
        "label_en": "health insurance",
        "query": "Dutch health insurance latest news",
        "why_nl": "haakt aan op je Zilverenkruis en verzekeringscontext",
        "why_en": "connects to your Zilverenkruis and insurance context",
        "tokens": ["zilverenkruis", "verzekering", "insurance", "polis", "declaratie", "claim"],
    },
    {
        "id": "flowers_garden",
        "label_nl": "bloemen en tuin",
        "label_en": "flowers and gardening",
        "query": "Netherlands gardening flower industry latest news",
        "why_nl": "past bij je bloem- en tuinachtige signalen",
        "why_en": "fits your flower and gardening signals",
        "tokens": ["boeket", "bloem", "flower", "floral", "garden", "gardening", "zaadje", "horticulture"],
    },
    {
        "id": "heemstede_local",
        "label_nl": "Heemstede en omgeving",
        "label_en": "Heemstede and local area",
        "query": "Heemstede latest news",
        "why_nl": "is lokaal relevant voor je dagelijkse context",
        "why_en": "is locally relevant to your daily context",
        "tokens": ["heemstede", "haarlem", "lindengracht", "amsterdam"],
    },
]


def run(args: dict) -> str:
    language = str(args.get("language") or "nl").strip().lower()
    dutch = language != "en"
    max_topics = _clamp_int(args.get("max_topics", 3), default=3, minimum=1, maximum=5)
    include_links = bool(args.get("include_links", True))
    user_file = str(args.get("user_file") or "user.md").strip() or "user.md"
    manual_topics = _parse_manual_topics(args.get("topics"))

    user_text = _read_user_profile(user_file)
    if user_text.startswith("Error:"):
        return tool_error(user_text)

    topic_profiles = _resolve_topics(user_text, manual_topics, max_topics)
    if not topic_profiles:
        return tool_error(
            "Ik kon in user.md nog geen duidelijke interesses vinden voor een nieuwsbriefje."
            if dutch else
            "I could not find clear interests in user.md yet for a news brief."
        )

    items = []
    for profile in topic_profiles:
        result = _search_topic(profile["query"])
        if result:
            items.append((profile, result))

    if not items:
        return tool_error(
            "Ik heb wel interesses gevonden in user.md, maar kreeg nu geen bruikbare actuele webresultaten terug."
            if dutch else
            "I found interests in user.md, but could not get usable current web results right now."
        )

    report = _format_report(items, dutch=dutch, include_links=include_links)
    return tool_ok(
        {"topics": [p["id"] for p, _ in items], "count": len(items)},
        report
    )


def _clamp_int(value, *, default: int, minimum: int, maximum: int) -> int:
    try:
        parsed = int(value)
    except Exception:
        parsed = default
    return max(minimum, min(maximum, parsed))


def _parse_manual_topics(value: object) -> list[str]:
    text = str(value or "").strip()
    if not text:
        return []
    return [part.strip().lower() for part in text.split(",") if part.strip()]


def _read_user_profile(rel_path: str) -> str:
    try:
        target = (_ROOT / rel_path).resolve()
        if not str(target).startswith(str(_ROOT.resolve())):
            return "Error: user_file is outside the workspace root."
        if not target.exists():
            return f"Error: user profile file not found: {rel_path}"
        return target.read_text(encoding="utf-8", errors="replace")
    except Exception as exc:
        return f"Error: could not read user profile: {exc}"


def _resolve_topics(user_text: str, manual_topics: list[str], max_topics: int) -> list[dict]:
    if manual_topics:
        selected = []
        for topic in manual_topics:
            matched = _match_profile(topic)
            if matched and matched not in selected:
                selected.append(matched)
        return selected[:max_topics]

    scored = []
    lowered = user_text.lower()
    for profile in _TOPIC_PROFILES:
        score = 0
        for token in profile["tokens"]:
            score += lowered.count(token.lower())
        if score:
            scored.append((score, profile))

    scored.sort(key=lambda item: (-item[0], item[1]["id"]))
    if scored:
        return [profile for _, profile in scored[:max_topics]]

    return [
        _TOPIC_PROFILES[1],
        _TOPIC_PROFILES[2],
        _TOPIC_PROFILES[5],
    ][:max_topics]


def _match_profile(text: str) -> dict | None:
    lowered = str(text or "").strip().lower()
    for profile in _TOPIC_PROFILES:
        if lowered == profile["id"]:
            return profile
        if lowered in profile["label_nl"].lower() or lowered in profile["label_en"].lower():
            return profile
        if any(lowered in token.lower() or token.lower() in lowered for token in profile["tokens"]):
            return profile
    return None


def _search_topic(query: str) -> dict | None:
    result = _brave_news_like_search(query)
    if result:
        return result
    return _bing_news_search(query)


def _load_brave_key() -> str:
    try:
        if not _SETTINGS_FILE.exists():
            return ""
        settings = json.loads(_SETTINGS_FILE.read_text(encoding="utf-8"))
        return str(settings.get("brave_api_key") or "").strip()
    except Exception:
        return ""


def _brave_news_like_search(query: str) -> dict | None:
    api_key = _load_brave_key()
    if not api_key:
        return None
    try:
        response = requests.get(
            "https://api.search.brave.com/res/v1/news/search",
            params={"q": query, "count": 5},
            headers={
                "Accept": "application/json",
                "X-Subscription-Token": api_key,
            },
            timeout=12,
        )
        if response.status_code != 200:
            return None
        results = (response.json().get("results") or [])
        for item in results:
            title = str(item.get("title") or "").strip()
            url = str(item.get("url") or "").strip()
            snippet = str(item.get("description") or "").strip()
            age = str(item.get("age") or "").strip()
            source = str((item.get("meta_url") or {}).get("hostname") or "").strip()
            if title and url:
                return {
                    "title": title,
                    "url": url,
                    "snippet": _clean_text(snippet),
                    "source": source,
                    "date": age,
                }
    except Exception:
        return None
    return None


def _bing_news_search(query: str) -> dict | None:
    browser_ua = (
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
        "AppleWebKit/537.36 (KHTML, like Gecko) "
        "Chrome/122.0.0.0 Safari/537.36"
    )
    try:
        response = requests.get(
            "https://www.bing.com/news/search",
            params={"q": query, "format": "rss"},
            headers={"User-Agent": browser_ua},
            timeout=12,
        )
        if response.status_code != 200 or "xml" not in response.headers.get("content-type", ""):
            return None
        root = ET.fromstring(response.text)
        channel = root.find("channel")
        if channel is None:
            return None
        for item in channel.findall("item"):
            title = str(item.findtext("title") or "").strip()
            url = str(item.findtext("link") or "").strip()
            snippet = _clean_text(item.findtext("description") or "")
            pub_date = str(item.findtext("pubDate") or "").strip()
            source = _clean_source_from_url(url)
            if title and url:
                return {
                    "title": title,
                    "url": url,
                    "snippet": snippet,
                    "source": source,
                    "date": pub_date,
                }
    except Exception:
        return None
    return None


def _clean_text(value: str, *, limit: int = 220) -> str:
    text = unescape(str(value or ""))
    text = re.sub(r"<[^>]+>", " ", text)
    text = re.sub(r"\s+", " ", text).strip(" -|,.;")
    if len(text) <= limit:
        return text
    return text[: limit - 3].rstrip(" ,.;:-") + "..."


def _clean_source_from_url(url: str) -> str:
    match = re.search(r"https?://(?:www\.)?([^/]+)", str(url or ""), re.IGNORECASE)
    return str(match.group(1) or "").strip() if match else ""


def _format_report(items: list[tuple[dict, dict]], *, dutch: bool, include_links: bool) -> str:
    today_text = datetime.now().strftime("%d-%m-%Y")
    opener = _pick_variant(
        [
            "Hier is een korte nieuwsgolf op jouw golflengte.",
            "Ik heb een kleine verse interesse-scan voor je gemaakt.",
            "Kleine spontane interest-brief, recht uit het web.",
        ] if dutch else [
            "Here is a short interest-matched news pulse.",
            "I pulled together a small fresh scan tuned to your interests.",
            "A quick spontaneous interest brief, straight from the web.",
        ],
        *(profile["id"] for profile, _ in items),
    )

    lines = []
    lines.append("**Interessebrief**" if dutch else "**Interest Brief**")
    lines.append(f"Datum: {today_text}")
    lines.append("")
    lines.append(opener)
    lines.append("")

    for profile, result in items:
        label = profile["label_nl"] if dutch else profile["label_en"]
        why = profile["why_nl"] if dutch else profile["why_en"]
        title = _clean_text(result.get("title") or "", limit=140)
        snippet = _clean_text(result.get("snippet") or "", limit=180)
        source = str(result.get("source") or "").strip()
        date_value = _clean_text(str(result.get("date") or "").strip(), limit=48)
        lines.append(f"- **{label}:** {title}")
        if dutch:
            detail = f"  Kort: {snippet or 'dit sprong er nu het meest uit.'}"
            angle = f"  Waarom dit past: {why}."
        else:
            detail = f"  Short take: {snippet or 'this was the clearest current signal.'}"
            angle = f"  Why it fits: {why}."
        lines.append(detail)
        lines.append(angle)
        if source or date_value:
            meta_bits = [bit for bit in [source, date_value] if bit]
            lines.append(f"  Bron: {' | '.join(meta_bits)}")
        if include_links and result.get("url"):
            lines.append(f"  Link: {result['url']}")
        lines.append("")

    outro = _pick_variant(
        [
            "Geen mail, geen agenda, alleen een compacte web-update op basis van je interesses.",
            "Dit is bewust los van mail en agenda gehouden: puur profiel plus web.",
            "Kort, actueel en zonder inbox-ruis of agenda-gedoe.",
        ] if dutch else [
            "No email, no calendar, just a compact web update based on your interests.",
            "This was intentionally kept separate from mail and calendar: profile plus web only.",
            "Short, current, and free of inbox or calendar noise.",
        ],
        len(items),
    )
    lines.append(outro)
    return "\n".join(lines).strip()


def _pick_variant(options: list[str], *keys: object) -> str:
    if not options:
        return ""
    total = 0
    for key in keys:
        total += sum(ord(ch) for ch in str(key or ""))
    return options[total % len(options)]