"""Natural Payments MCP server using FastMCP.

Provides tools for AI agents to interact with Natural's payment APIs.
No AI/LLM dependencies - agents fill structured parameters directly.
"""

from __future__ import annotations

import time
from typing import Any, Literal

from fastmcp import FastMCP

from naturalpay._logging import get_logger, log_tool_call
from naturalpay.client import NaturalClient

logger = get_logger(__name__)


def create_server(api_key: str | None = None, server_url: str | None = None) -> FastMCP:
    """Create a Natural Payments MCP server.

    Args:
        api_key: API key (defaults to NATURAL_API_KEY env var)
        server_url: Server URL (defaults to NATURAL_SERVER_URL env var)

    Returns:
        Configured FastMCP server instance
    """
    logger.info("Creating Natural Payments MCP server")
    mcp = FastMCP("Natural Payments")

    # Lazy client initialization
    _client: NaturalClient | None = None

    def get_client() -> NaturalClient:
        nonlocal _client
        if _client is None:
            _client = NaturalClient(api_key=api_key, base_url=server_url)
        return _client

    # =========================================================================
    # PAYMENT TOOLS
    # =========================================================================

    @mcp.tool()
    async def create_payment(
        amount: float,
        agent_id: str,
        memo: str,
        customer_party_id: str,
        recipient_email: str | None = None,
        recipient_phone: str | None = None,
        recipient_party_id: str | None = None,
    ) -> dict:
        """Send a payment to a recipient.

        Must provide exactly one of: recipient_email, recipient_phone, or recipient_party_id.

        Args:
            amount: Payment amount (e.g., 50.00)
            agent_id: Agent ID making the payment (agt_xxx)
            memo: Payment description/memo (required)
            customer_party_id: Customer party ID on whose behalf (pty_xxx)
            recipient_email: Recipient's email address
            recipient_phone: Recipient's phone number
            recipient_party_id: Recipient's party ID (pty_xxx)

        Returns:
            Payment result with transfer_id, status, amount, etc.
        """
        recipient_count = sum(
            [
                recipient_email is not None,
                recipient_phone is not None,
                recipient_party_id is not None,
            ]
        )
        if recipient_count != 1:
            raise ValueError(
                "Must provide exactly one of: recipient_email, recipient_phone, or recipient_party_id"
            )

        start_time = time.time()
        try:
            client = get_client()
            result = await client.payments.create(
                recipient_email=recipient_email,
                recipient_phone=recipient_phone,
                recipient_party_id=recipient_party_id,
                amount=amount,
                memo=memo,
                agent_id=agent_id,
                customer_party_id=customer_party_id,
            )
            duration_ms = (time.time() - start_time) * 1000
            log_tool_call(logger, "create_payment", success=True, duration_ms=duration_ms)
            return result.model_dump(exclude_none=True)
        except Exception as e:
            duration_ms = (time.time() - start_time) * 1000
            log_tool_call(logger, "create_payment", success=False, duration_ms=duration_ms, error=e)
            raise

    @mcp.tool()
    async def get_payment_status(transfer_id: str) -> dict:
        """Get the status of a payment by transfer ID.

        Args:
            transfer_id: The transfer ID returned from create_payment

        Returns:
            Payment details including status, amount, recipient, timestamps
        """
        start_time = time.time()
        try:
            client = get_client()
            result = await client.payments.retrieve(transfer_id)
            duration_ms = (time.time() - start_time) * 1000
            log_tool_call(logger, "get_payment_status", success=True, duration_ms=duration_ms)
            return result.model_dump(exclude_none=True)
        except Exception as e:
            duration_ms = (time.time() - start_time) * 1000
            log_tool_call(
                logger, "get_payment_status", success=False, duration_ms=duration_ms, error=e
            )
            raise

    @mcp.tool()
    async def cancel_payment(transfer_id: str) -> dict:
        """Cancel a pending payment.

        Only pending payments can be cancelled. Completed or processing
        payments cannot be reversed.

        Args:
            transfer_id: The transfer ID to cancel

        Returns:
            Cancellation result with status and message
        """
        start_time = time.time()
        try:
            client = get_client()
            result = await client.payments.cancel(transfer_id)
            duration_ms = (time.time() - start_time) * 1000
            log_tool_call(logger, "cancel_payment", success=True, duration_ms=duration_ms)
            return result.model_dump()
        except Exception as e:
            duration_ms = (time.time() - start_time) * 1000
            log_tool_call(logger, "cancel_payment", success=False, duration_ms=duration_ms, error=e)
            raise

    # =========================================================================
    # WALLET TOOLS
    # =========================================================================

    @mcp.tool()
    async def get_account_balance() -> dict:
        """Get the current wallet balance.

        Returns:
            Wallet balance with available amount and breakdown by account type
        """
        start_time = time.time()
        try:
            client = get_client()
            result = await client.account.balance()
            duration_ms = (time.time() - start_time) * 1000
            log_tool_call(logger, "get_account_balance", success=True, duration_ms=duration_ms)
            return {
                "wallet_id": result.wallet_id,
                "available_usd": result.available_usd,
                "balances": [
                    {
                        "asset_code": bal.asset_code,
                        "available": bal.available.amount_dollars,
                        "breakdown": {
                            "operating_funded": bal.breakdown.operating_funded.amount_dollars,
                            "operating_advanced": bal.breakdown.operating_advanced.amount_dollars,
                            "escrow_funded_settled": bal.breakdown.escrow_funded_settled.amount_dollars,
                            "escrow_advanced": bal.breakdown.escrow_advanced.amount_dollars,
                            "holds_outbound": bal.breakdown.holds_outbound.amount_dollars,
                        },
                    }
                    for bal in result.balances
                ],
            }
        except Exception as e:
            duration_ms = (time.time() - start_time) * 1000
            log_tool_call(
                logger, "get_account_balance", success=False, duration_ms=duration_ms, error=e
            )
            raise

    @mcp.tool()
    async def list_transactions(
        limit: int = 10,
        agent_id: str | None = None,
        customer_party_id: str | None = None,
        customer_filter: str | None = None,
    ) -> list[dict[str, Any]]:
        """List recent transactions.

        For full functionality, provide agent_id and customer_party_id
        to establish agent context for accessing delegated transactions.

        Args:
            limit: Maximum number of transactions (default: 10, max: 100)
            agent_id: Agent ID for agent-context authentication
            customer_party_id: Customer party ID when acting on behalf of customer
            customer_filter: Filter by customer agent_id (or '_self' for partner only)

        Returns:
            List of transactions with details
        """
        start_time = time.time()
        try:
            client = get_client()
            results = await client.transactions.list(
                limit=limit,
                customer_filter=customer_filter,
                agent_id=agent_id,
                customer_party_id=customer_party_id,
            )
            duration_ms = (time.time() - start_time) * 1000
            log_tool_call(logger, "list_transactions", success=True, duration_ms=duration_ms)
            return [tx.model_dump(exclude_none=True) for tx in results]
        except Exception as e:
            duration_ms = (time.time() - start_time) * 1000
            log_tool_call(
                logger, "list_transactions", success=False, duration_ms=duration_ms, error=e
            )
            raise

    # =========================================================================
    # AGENT TOOLS
    # =========================================================================

    @mcp.tool()
    async def list_agents(
        status: str | None = None,
        limit: int = 50,
    ) -> dict:
        """List agents for the partner.

        Args:
            status: Filter by status (ACTIVE or REVOKED)
            limit: Maximum number of agents (default: 50, max: 100)

        Returns:
            List of agents with their details
        """
        start_time = time.time()
        try:
            client = get_client()
            result = await client.agents.list(status=status, limit=limit)
            duration_ms = (time.time() - start_time) * 1000
            log_tool_call(logger, "list_agents", success=True, duration_ms=duration_ms)
            return result.model_dump(exclude_none=True)
        except Exception as e:
            duration_ms = (time.time() - start_time) * 1000
            log_tool_call(logger, "list_agents", success=False, duration_ms=duration_ms, error=e)
            raise

    return mcp


def serve(
    transport: Literal["stdio", "sse", "streamable-http"] = "stdio",
    *,
    host: str = "127.0.0.1",
    port: int = 8080,
    api_key: str | None = None,
    server_url: str | None = None,
) -> None:
    """Run the Natural Payments MCP server.

    Args:
        transport: Transport type (stdio, sse, streamable-http)
        host: Host to bind to (for HTTP transports)
        port: Port to bind to (for HTTP transports)
        api_key: API key (defaults to NATURAL_API_KEY env var)
        server_url: Server URL (defaults to NATURAL_SERVER_URL env var)
    """
    logger.info(
        "Starting Natural Payments MCP server",
        extra={"transport": transport, "host": host, "port": port},
    )
    mcp = create_server(api_key=api_key, server_url=server_url)

    if transport == "stdio":
        logger.info("Running with stdio transport")
        mcp.run()
    elif transport == "sse":
        logger.info(f"Running with SSE transport on {host}:{port}")
        mcp.run(transport="sse", host=host, port=port)
    elif transport == "streamable-http":
        logger.info(f"Running with streamable-http transport on {host}:{port}")
        mcp.run(transport="streamable-http", host=host, port=port)
    else:
        logger.error(f"Unknown transport: {transport}")
        raise ValueError(f"Unknown transport: {transport}")
