"""
Agent tool definitions for the Email Assistant.

Each tool is a plain function that wraps ``GmailClient`` methods and returns
JSON-serialisable dicts.  The ``TOOL_SCHEMAS`` list provides OpenAI-compatible
function-calling descriptors so the LLM knows how to invoke them.
"""

from __future__ import annotations

import json
from collections.abc import Callable
from typing import Any, cast

from email_assistant.gmail_client import GmailClient

# ---------------------------------------------------------------------------
# Singleton client (created once, reused across calls)
# ---------------------------------------------------------------------------
_gmail: GmailClient | None = None


def _client() -> GmailClient:
    """Lazy initialisation of the shared Gmail client."""
    global _gmail  # noqa: PLW0603
    if _gmail is None:
        _gmail = GmailClient()
    return _gmail


# ═══════════════════════════════════════════════════════════════════════════
# Tool implementations
# ═══════════════════════════════════════════════════════════════════════════

def search_emails(query: str, max_results: int = 10) -> list[dict[str, Any]]:
    """Search Gmail using Gmail search syntax.

    Args:
        query: Gmail search query string (e.g. ``from:amazon newer_than:7d``).
        max_results: Maximum number of results to return.

    Returns:
        List of email metadata dicts.
    """
    return _client().search_messages(query, max_results=max_results)


def get_email(message_id: str) -> dict[str, Any] | None:
    """Fetch the full content of a single email.

    Args:
        message_id: Gmail message ID.

    Returns:
        Email dict with metadata and body, or ``None``.
    """
    return _client().get_message_full(message_id)


def get_thread(thread_id: str) -> list[dict[str, Any]]:
    """Fetch all messages in a conversation thread.

    Args:
        thread_id: Gmail thread ID.

    Returns:
        Chronological list of email dicts with bodies.
    """
    return _client().get_thread(thread_id)


def send_email(
    to: str,
    subject: str,
    body: str,
    cc: str = "",
    bcc: str = "",
) -> dict[str, Any]:
    """Send an email through the authenticated Gmail account."""
    return _client().send_message(
        to=to,
        subject=subject,
        body=body,
        cc=cc,
        bcc=bcc,
    )


# ═══════════════════════════════════════════════════════════════════════════
# OpenAI function-calling schemas
# ═══════════════════════════════════════════════════════════════════════════

TOOL_SCHEMAS: list[dict[str, Any]] = [
    {
        "type": "function",
        "function": {
            "name": "search_emails",
            "description": (
                "Search the user's Gmail inbox using Gmail search syntax. "
                "Translate natural language queries into Gmail search operators. "
                "Common operators: from:, to:, subject:, newer_than:Nd, "
                "older_than:Nd, is:unread, has:attachment, label:, "
                "in:inbox, category:. Example: 'from:amazon newer_than:7d'."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": (
                            "Gmail search query. Use Gmail search syntax."
                        ),
                    },
                    "max_results": {
                        "type": "integer",
                        "description": (
                            "Maximum number of emails to return. "
                            "Defaults to 10."
                        ),
                    },
                },
                "required": ["query"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_email",
            "description": (
                "Fetch the full content of a single email by its message ID. "
                "Use this when the user wants to read a specific email or "
                "you need the full body for summarisation."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "message_id": {
                        "type": "string",
                        "description": "The Gmail message ID to retrieve.",
                    },
                },
                "required": ["message_id"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_thread",
            "description": (
                "Fetch all messages in an email conversation thread. "
                "Use this when the user wants a summary of an entire "
                "email conversation or thread."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "thread_id": {
                        "type": "string",
                        "description": "The Gmail thread ID to retrieve.",
                    },
                },
                "required": ["thread_id"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "send_email",
            "description": (
                "Send an email from the authenticated Gmail account. "
                "Use this only when the user has clearly asked to send an email "
                "and the recipient, subject, and body are fully specified."
            ),
            "parameters": {
                "type": "object",
                "properties": {
                    "to": {
                        "type": "string",
                        "description": "Recipient email address or comma-separated list.",
                    },
                    "subject": {
                        "type": "string",
                        "description": "Email subject line.",
                    },
                    "body": {
                        "type": "string",
                        "description": "Plain-text email body.",
                    },
                    "cc": {
                        "type": "string",
                        "description": "Optional CC recipient list as a comma-separated string.",
                    },
                    "bcc": {
                        "type": "string",
                        "description": "Optional BCC recipient list as a comma-separated string.",
                    },
                },
                "required": ["to", "subject", "body"],
            },
        },
    },
]

# ---------------------------------------------------------------------------
# Dispatcher — maps function name → callable
# ---------------------------------------------------------------------------
TOOL_DISPATCH: dict[str, Callable[..., Any]] = {
    "search_emails": search_emails,
    "get_email": get_email,
    "get_thread": get_thread,
    "send_email": send_email,
}


def execute_tool(name: str, arguments: object) -> str:
    """Execute a tool by name and return a JSON string result.

    Args:
        name: Tool function name.
        arguments: JSON string or dict of keyword arguments.

    Returns:
        JSON-encoded result string for injection into the LLM conversation.
    """
    if isinstance(arguments, str):
        arguments = json.loads(arguments)
    if not isinstance(arguments, dict):
        return json.dumps(
            {"error": "Tool arguments must be a JSON object with string keys."}
        )

    validated_arguments = cast(dict[str, Any], arguments)

    func = TOOL_DISPATCH.get(name)
    if func is None:
        return json.dumps({"error": f"Unknown tool: {name}"})

    try:
        result = func(**validated_arguments)
        return json.dumps(result, default=str, ensure_ascii=False)
    except Exception as exc:  # noqa: BLE001
        return json.dumps({"error": str(exc)})
