# 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

"""
Mastodon skill tools — uses the Mastodon REST API via httpx (no extra dep).

Requires MASTODON_INSTANCE_URL and MASTODON_ACCESS_TOKEN environment variables.
Works with any Mastodon-compatible server (Mastodon, GoToSocial, Pleroma, etc.).
"""

import logging
import os
import re

from familiar.skills._api_utils import api_get, api_post

logger = logging.getLogger(__name__)

REQUEST_TIMEOUT = 30


def _get_config() -> dict:
    return {
        "url": os.environ.get("MASTODON_INSTANCE_URL", "").rstrip("/"),
        "token": os.environ.get("MASTODON_ACCESS_TOKEN", ""),
    }


def _configured() -> bool:
    cfg = _get_config()
    return bool(cfg["url"] and cfg["token"])


def _headers() -> dict:
    return {"Authorization": f"Bearer {_get_config()['token']}"}


def _base_url() -> str:
    return _get_config()["url"]


# ── Tool handlers ────────────────────────────────────────────────


def mastodon_post(data: dict) -> str:
    """Post a status to Mastodon."""
    if not _configured():
        return "Mastodon not configured. Run /connect mastodon to set up."

    text = data.get("text", "").strip()
    if not text:
        return "Please provide 'text' for the post."

    visibility = data.get("visibility", "public")
    if visibility not in ("public", "unlisted", "private", "direct"):
        visibility = "public"

    spoiler = data.get("spoiler_text", "")

    body = {"status": text, "visibility": visibility}
    if spoiler:
        body["spoiler_text"] = spoiler

    try:
        result = api_post(
            f"{_base_url()}/api/v1/statuses",
            json=body,
            headers=_headers(),
            timeout=REQUEST_TIMEOUT,
            service_name="Mastodon",
            connect_cmd="mastodon",
        )
        url = result.get("url", result.get("uri", ""))
        return f"Posted to Mastodon: {url}"
    except Exception as e:
        logger.error("Mastodon post error: %s", e)
        return f"Failed to post: {e}"


def mastodon_timeline(data: dict) -> str:
    """Get the home timeline."""
    if not _configured():
        return "Mastodon not configured. Run /connect mastodon to set up."

    limit = min(int(data.get("limit", 20)), 40)

    try:
        result = api_get(
            f"{_base_url()}/api/v1/timelines/home",
            headers=_headers(),
            params={"limit": str(limit)},
            timeout=REQUEST_TIMEOUT,
            service_name="Mastodon",
            connect_cmd="mastodon",
        )
        if not result:
            return "Timeline is empty."

        lines = ["Home Timeline:"]
        for status in result[:limit]:
            acct = status.get("account", {}).get("acct", "?")
            content = re.sub(r"<[^>]+>", "", status.get("content", ""))
            content = content[:120] + "..." if len(content) > 120 else content
            ts = status.get("created_at", "")[:10]
            lines.append(f"  @{acct} ({ts}): {content}")
        return "\n".join(lines)
    except Exception as e:
        logger.error("Mastodon timeline error: %s", e)
        return f"Failed to fetch timeline: {e}"


def mastodon_mentions(data: dict) -> str:
    """Get recent mentions/notifications."""
    if not _configured():
        return "Mastodon not configured. Run /connect mastodon to set up."

    limit = min(int(data.get("limit", 20)), 40)

    try:
        result = api_get(
            f"{_base_url()}/api/v1/notifications",
            headers=_headers(),
            params={"limit": str(limit), "types[]": "mention"},
            timeout=REQUEST_TIMEOUT,
            service_name="Mastodon",
            connect_cmd="mastodon",
        )
        if not result:
            return "No recent mentions."

        lines = ["Recent Mentions:"]
        for notif in result[:limit]:
            acct = notif.get("account", {}).get("acct", "?")
            status = notif.get("status", {})
            content = re.sub(r"<[^>]+>", "", status.get("content", ""))
            content = content[:120] + "..." if len(content) > 120 else content
            ts = notif.get("created_at", "")[:10]
            lines.append(f"  @{acct} ({ts}): {content}")
        return "\n".join(lines)
    except Exception as e:
        logger.error("Mastodon mentions error: %s", e)
        return f"Failed to fetch mentions: {e}"


def mastodon_search(data: dict) -> str:
    """Search for accounts, statuses, or hashtags."""
    if not _configured():
        return "Mastodon not configured. Run /connect mastodon to set up."

    query = data.get("query", "").strip()
    if not query:
        return "Please provide a 'query' to search for."

    search_type = data.get("type", "")
    params = {"q": query, "limit": "10"}
    if search_type in ("accounts", "statuses", "hashtags"):
        params["type"] = search_type

    try:
        result = api_get(
            f"{_base_url()}/api/v2/search",
            headers=_headers(),
            params=params,
            timeout=REQUEST_TIMEOUT,
            service_name="Mastodon",
            connect_cmd="mastodon",
        )
        lines = [f"Search results for '{query}':"]

        accounts = result.get("accounts", [])
        if accounts:
            lines.append("\nAccounts:")
            for a in accounts[:5]:
                lines.append(f"  @{a.get('acct', '?')} — {a.get('display_name', '')}")

        statuses = result.get("statuses", [])
        if statuses:
            lines.append("\nStatuses:")
            for s in statuses[:5]:
                content = re.sub(r"<[^>]+>", "", s.get("content", ""))
                content = content[:100] + "..." if len(content) > 100 else content
                lines.append(f"  @{s.get('account', {}).get('acct', '?')}: {content}")

        hashtags = result.get("hashtags", [])
        if hashtags:
            lines.append("\nHashtags:")
            for h in hashtags[:5]:
                lines.append(f"  #{h.get('name', '?')}")

        if len(lines) == 1:
            return f"No results found for '{query}'."
        return "\n".join(lines)
    except Exception as e:
        logger.error("Mastodon search error: %s", e)
        return f"Search failed: {e}"


def mastodon_profile(data: dict) -> str:
    """Get a user's profile information."""
    if not _configured():
        return "Mastodon not configured. Run /connect mastodon to set up."

    account_id = data.get("account_id", "").strip()

    if account_id and not account_id.isdigit():
        return "Invalid account_id: must be numeric"

    if not account_id:
        # Get own profile
        try:
            result = api_get(
                f"{_base_url()}/api/v1/accounts/verify_credentials",
                headers=_headers(),
                timeout=REQUEST_TIMEOUT,
                service_name="Mastodon",
                connect_cmd="mastodon",
            )
        except Exception as e:
            return f"Failed to fetch profile: {e}"
    else:
        try:
            result = api_get(
                f"{_base_url()}/api/v1/accounts/{account_id}",
                headers=_headers(),
                timeout=REQUEST_TIMEOUT,
                service_name="Mastodon",
                connect_cmd="mastodon",
            )
        except Exception as e:
            return f"Failed to fetch profile: {e}"

    note = re.sub(r"<[^>]+>", "", result.get("note", ""))
    return (
        f"Profile: @{result.get('acct', '?')}\n"
        f"  Name: {result.get('display_name', '')}\n"
        f"  Bio: {note[:200]}\n"
        f"  Followers: {result.get('followers_count', 0)}\n"
        f"  Following: {result.get('following_count', 0)}\n"
        f"  Posts: {result.get('statuses_count', 0)}\n"
        f"  URL: {result.get('url', '')}"
    )


# ── Tool definitions ─────────────────────────────────────────────

TOOLS = [
    {
        "name": "mastodon_post",
        "description": "Post a status to Mastodon / GoToSocial. Supports public, unlisted, private, and direct visibility.",
        "input_schema": {
            "type": "object",
            "properties": {
                "text": {"type": "string", "description": "The status text to post"},
                "visibility": {
                    "type": "string",
                    "description": "Post visibility: public, unlisted, private, or direct",
                    "enum": ["public", "unlisted", "private", "direct"],
                },
                "spoiler_text": {
                    "type": "string",
                    "description": "Content warning text (optional)",
                },
            },
            "required": ["text"],
        },
        "handler": mastodon_post,
        "category": "social",
    },
    {
        "name": "mastodon_timeline",
        "description": "Get the home timeline from Mastodon. Returns recent posts from followed accounts.",
        "input_schema": {
            "type": "object",
            "properties": {
                "limit": {
                    "type": "integer",
                    "description": "Number of posts to fetch (max 40)",
                },
            },
        },
        "handler": mastodon_timeline,
        "category": "social",
    },
    {
        "name": "mastodon_mentions",
        "description": "Get recent mentions and notifications from Mastodon.",
        "input_schema": {
            "type": "object",
            "properties": {
                "limit": {
                    "type": "integer",
                    "description": "Number of notifications to fetch (max 40)",
                },
            },
        },
        "handler": mastodon_mentions,
        "category": "social",
    },
    {
        "name": "mastodon_search",
        "description": "Search Mastodon for accounts, statuses, or hashtags.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string", "description": "Search query"},
                "type": {
                    "type": "string",
                    "description": "Filter by type: accounts, statuses, or hashtags",
                    "enum": ["accounts", "statuses", "hashtags"],
                },
            },
            "required": ["query"],
        },
        "handler": mastodon_search,
        "category": "social",
    },
    {
        "name": "mastodon_profile",
        "description": "Get a Mastodon user profile. Leave account_id empty to get your own profile.",
        "input_schema": {
            "type": "object",
            "properties": {
                "account_id": {
                    "type": "string",
                    "description": "Account ID to look up (empty for own profile)",
                },
            },
        },
        "handler": mastodon_profile,
        "category": "social",
    },
]
