# 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

"""
Web API Skill - Argus, the All-Seeing

Argus Panoptes was the hundred-eyed giant who could see everything at once.
He watches all the endpoints of the web and brings back what you seek.

Make HTTP requests to external services.
"""

import json
import logging
import urllib.error
import urllib.parse
import urllib.request

from familiar.core.secrets_detection import mask_secrets
from familiar.core.tools import require_fields

logger = logging.getLogger(__name__)


def fetch_url(data: dict) -> str:
    """Fetch content from a URL."""
    if err := require_fields(data, "url"):
        return err
    url = data.get("url", "")
    timeout = data.get("timeout", 30)
    logger.info("webapi: GET %s", mask_secrets(url))

    try:
        req = urllib.request.Request(url)
        req.add_header("User-Agent", "PiAgent/1.0")

        with urllib.request.urlopen(req, timeout=timeout) as response:
            content = response.read().decode("utf-8", errors="replace")
            logger.info("webapi: GET %s -> %s", mask_secrets(url), response.status)

            # Truncate if too long
            if len(content) > 10000:
                content = content[:10000] + "\n... (truncated)"

            return f"[{response.status}]\n{content}"

    except urllib.error.HTTPError as e:
        return f"HTTP Error {e.code}: {e.reason}"
    except urllib.error.URLError as e:
        return f"URL Error: {e.reason}"
    except Exception as e:
        return f"Error: {str(e)}. Check endpoint URL and API key configuration."


def fetch_json_api(data: dict) -> str:
    """Fetch and parse JSON from an API."""
    if err := require_fields(data, "url"):
        return err
    url = data.get("url", "")
    headers = data.get("headers", {})
    timeout = data.get("timeout", 30)
    logger.info("webapi: GET %s", mask_secrets(url))

    try:
        req = urllib.request.Request(url)
        req.add_header("User-Agent", "PiAgent/1.0")
        req.add_header("Accept", "application/json")

        for key, value in headers.items():
            req.add_header(key, value)

        with urllib.request.urlopen(req, timeout=timeout) as response:
            content = response.read().decode("utf-8")
            logger.info("webapi: GET %s -> %s", mask_secrets(url), response.status)

            # Parse JSON
            try:
                parsed = json.loads(content)
                # Pretty print
                formatted = json.dumps(parsed, indent=2)

                if len(formatted) > 5000:
                    formatted = formatted[:5000] + "\n... (truncated)"

                return formatted
            except json.JSONDecodeError:
                return f"Response is not valid JSON:\n{content[:1000]}"

    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", errors="replace")[:500]
        return f"HTTP Error {e.code}: {e.reason}\n{body}"
    except Exception as e:
        return f"Error: {str(e)}. Check endpoint URL and API key configuration."


def post_webhook(data: dict) -> str:
    """Send data to a webhook."""
    if err := require_fields(data, "url"):
        return err
    url = data.get("url", "")
    payload = data.get("payload", {})
    content_type = data.get("content_type", "application/json")
    logger.info("webapi: POST %s", mask_secrets(url))

    try:
        if content_type == "application/json":
            body = json.dumps(payload).encode("utf-8")
        else:
            body = urllib.parse.urlencode(payload).encode("utf-8")

        req = urllib.request.Request(url, data=body, method="POST")
        req.add_header("User-Agent", "PiAgent/1.0")
        req.add_header("Content-Type", content_type)

        with urllib.request.urlopen(req, timeout=30) as response:
            content = response.read().decode("utf-8", errors="replace")
            logger.info("webapi: POST %s -> %s", mask_secrets(url), response.status)
            return f"[{response.status}] {content[:500]}"

    except urllib.error.HTTPError as e:
        body = e.read().decode("utf-8", errors="replace")[:500]
        return f"HTTP Error {e.code}: {e.reason}\n{body}"
    except Exception as e:
        return f"Error: {str(e)}. Check endpoint URL and API key configuration."


def get_weather(data: dict) -> str:
    """Get weather using wttr.in (no API key needed)."""
    location = data.get("location", "")
    format_str = data.get("format", "%l:+%c+%t+%h+%w")

    try:
        # wttr.in provides free weather data
        url = f"https://wttr.in/{urllib.parse.quote(location)}?format={urllib.parse.quote(format_str)}"

        req = urllib.request.Request(url)
        req.add_header("User-Agent", "curl")  # wttr.in responds better to curl

        with urllib.request.urlopen(req, timeout=10) as response:
            return response.read().decode("utf-8").strip()

    except Exception as e:
        return f"Could not fetch weather: {e}"


TOOLS = [
    {
        "name": "fetch_url",
        "description": (
            "Fetch raw content (HTML, text, etc.) from a URL via HTTP GET. "
            "Use this to retrieve web pages or plain-text endpoints. "
            "Response is truncated at 10 000 characters to avoid overloading context. "
            "Returns the HTTP status code followed by the body content."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "url": {"type": "string", "description": "URL to fetch"},
                "timeout": {"type": "integer", "description": "Timeout in seconds", "default": 30},
            },
            "required": ["url"],
        },
        "handler": fetch_url,
        "category": "webapi",
    },
    {
        "name": "fetch_json_api",
        "description": (
            "Call a JSON API endpoint via HTTP GET and return the pretty-printed parsed response. "
            "Use this for REST APIs that return JSON data. "
            "Supports custom headers for authentication (e.g., Authorization Bearer tokens). "
            "Response is truncated at 5000 characters; returns an error if the response is not valid JSON."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "url": {"type": "string", "description": "API URL"},
                "headers": {
                    "type": "object",
                    "description": "Optional headers (e.g., Authorization)",
                    "default": {},
                },
            },
            "required": ["url"],
        },
        "handler": fetch_json_api,
        "category": "webapi",
    },
    {
        "name": "post_webhook",
        "description": (
            "Send a POST request with a JSON or form-encoded payload to a webhook URL. "
            "Use this to trigger external automations, notifications, or integrations. "
            "Supports application/json and application/x-www-form-urlencoded content types. "
            "Returns the HTTP status code and first 500 characters of the response body."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "url": {"type": "string", "description": "Webhook URL"},
                "payload": {"type": "object", "description": "Data to send", "default": {}},
                "content_type": {
                    "type": "string",
                    "description": "Content type",
                    "enum": ["application/json", "application/x-www-form-urlencoded"],
                    "default": "application/json",
                },
            },
            "required": ["url"],
        },
        "handler": post_webhook,
        "category": "webapi",
    },
    {
        "name": "get_weather",
        "description": (
            "Get current weather conditions for a city or location using the free wttr.in service. "
            "Use this when the user asks about weather — no API key is required. "
            "Accepts any city name or location string (e.g., 'Seattle', 'London'). "
            "Returns a compact summary with temperature, humidity, and wind conditions."
        ),
        "input_schema": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City name or location (e.g., 'Seattle', 'London')",
                    "default": "",
                }
            },
        },
        "handler": get_weather,
        "category": "webapi",
    },
]
