"""MCP tool registration: workflow tools (hand-registered) + read-only tools."""

from __future__ import annotations

import logging
from typing import Any

from fastmcp import FastMCP

from spherepay_mcp.application.business_rep import BusinessRepService
from spherepay_mcp.application.customer_onboarding import CustomerOnboardingService
from spherepay_mcp.application.funding_instrument import FundingInstrumentService
from spherepay_mcp.application.offloader_wallet import OffloaderWalletService
from spherepay_mcp.application.transfer_workflow import TransferWorkflowService
from spherepay_mcp.application.virtual_account import VirtualAccountService
from spherepay_mcp.domain.context import get_correlation_id, new_correlation_id
from spherepay_mcp.domain.validators import (
    validate_amount,
    validate_bank_account_id,
    validate_cctp_chain,
    validate_currency,
    validate_customer_id,
    validate_customer_type,
    validate_dest_currency,
    validate_event_id,
    validate_instrument_type,
    validate_network,
    validate_offloader_wallet_id,
    validate_payout_id,
    validate_source_currency,
    validate_transfer_id,
    validate_virtual_account_id,
    validate_wallet_id,
    validate_webhook_id,
)
from spherepay_mcp.domain.value_objects import ApiError, ErrorCategory
from spherepay_mcp.ports.spherepay_gateway import SpherePayGateway

logger = logging.getLogger(__name__)


def _error_response(e: ApiError) -> dict[str, Any]:
    return {
        "error": True,
        "category": e.category.value,
        "message": e.message,
        "status_code": e.status_code,
        "details": e.details,
        "correlation_id": e.correlation_id,
    }


def _unexpected_error(e: Exception) -> dict[str, Any]:
    logger.exception("Unexpected error in tool execution")
    return {
        "error": True,
        "category": ErrorCategory.UNKNOWN.value,
        "message": f"Unexpected error: {type(e).__name__}: {e}",
        "status_code": 500,
        "details": None,
        "correlation_id": get_correlation_id() or None,
    }


def register_workflow_tools(
    mcp: FastMCP,
    customer_svc: CustomerOnboardingService,
    funding_svc: FundingInstrumentService,
    transfer_svc: TransferWorkflowService,
    business_rep_svc: BusinessRepService,
    virtual_account_svc: VirtualAccountService,
    offloader_wallet_svc: OffloaderWalletService,
) -> None:
    """Register the 9 curated workflow tools on the MCP server."""

    @mcp.tool(
        name="onboard_customer",
        description=(
            "Create a new SpherePay customer and generate verification links. "
            "Supports 'individual' (requires address, optionally first/last name, DOB) "
            "and 'business' (requires business_information, registered/operating addresses) types. "
            "Returns the created customer with TOS and KYC verification links."
        ),
    )
    async def onboard_customer(
        customer_type: str,
        email: str,
        phone: str,
        address: dict[str, Any] | None = None,
        first_name: str | None = None,
        last_name: str | None = None,
        date_of_birth: str | None = None,
        business_information: dict[str, Any] | None = None,
        registered_address: dict[str, Any] | None = None,
        operating_address: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            customer_type = validate_customer_type(customer_type)
            return await customer_svc.onboard_customer(
                customer_type=customer_type,
                email=email,
                phone=phone,
                address=address or {},
                first_name=first_name,
                last_name=last_name,
                date_of_birth=date_of_birth,
                business_information=business_information,
                registered_address=registered_address,
                operating_address=operating_address,
            )
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="verify_customer",
        description=(
            "Run customer verification flow. "
            "Step 1: Call with phone_number to send OTP. "
            "Step 2: Call with otp_code to verify, then generates face verification link and submits. "
            "Returns progress with next steps."
        ),
    )
    async def verify_customer(
        customer_id: str,
        phone_number: str | None = None,
        otp_code: str | None = None,
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            customer_id = validate_customer_id(customer_id)
            return await customer_svc.verify_customer(
                customer_id=customer_id,
                phone_number=phone_number,
                otp_code=otp_code,
            )
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="setup_funding",
        description=(
            "Create a funding instrument (bank account or crypto wallet) for a customer. "
            "For bank_account: requires bank_name, account_name, currency ('usd' or 'eur'), "
            "account_details (accountNumber/routingNumber/accountType for USD; iban for EUR), "
            "and account_owner (accountHolderName, address). Optional: networks (list of strings, e.g. ['ach', 'wire']). "
            "For wallet: requires address (blockchain address) and network "
            "(ethereum, polygon, sol, base, arbitrum, tron, avalanche, optimism)."
        ),
    )
    async def setup_funding(
        customer_id: str,
        instrument_type: str,
        bank_name: str | None = None,
        account_name: str | None = None,
        currency: str | None = None,
        account_details: dict[str, Any] | None = None,
        account_owner: dict[str, Any] | None = None,
        networks: list[str] | None = None,
        address: str | None = None,
        network: str | None = None,
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            customer_id = validate_customer_id(customer_id)
            instrument_type = validate_instrument_type(instrument_type)
            if currency:
                currency = validate_currency(currency)
            if network:
                network = validate_network(network)
            return await funding_svc.setup_funding(
                customer_id=customer_id,
                instrument_type=instrument_type,
                bank_name=bank_name,
                account_name=account_name,
                currency=currency,
                account_details=account_details,
                account_owner=account_owner,
                networks=networks,
                address=address,
                network=network,
            )
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="execute_transfer",
        description=(
            "Execute a money transfer between funding instruments with automatic idempotency. "
            "Requires source_id and destination_id (bank account or wallet IDs), amount, "
            "source_currency, destination_currency, source_network, and destination_network. "
            "The idempotency key is auto-generated from parameters to prevent duplicate transfers. "
            "Supported networks: ach, wire, sepa (fiat); ethereum, polygon, sol, base, arbitrum, tron, avalanche, optimism (crypto)."
        ),
    )
    async def execute_transfer(
        source_id: str,
        destination_id: str,
        amount: str,
        source_currency: str,
        destination_currency: str,
        source_network: str,
        destination_network: str,
        customer_id: str | None = None,
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            amount = validate_amount(amount)
            source_currency = validate_currency(source_currency)
            destination_currency = validate_currency(destination_currency)
            source_network = validate_network(source_network)
            destination_network = validate_network(destination_network)
            if customer_id:
                customer_id = validate_customer_id(customer_id)
            return await transfer_svc.execute_transfer(
                source_id=source_id,
                destination_id=destination_id,
                amount=amount,
                source_currency=source_currency,
                destination_currency=destination_currency,
                source_network=source_network,
                destination_network=destination_network,
                customer_id=customer_id,
            )
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="onboard_business_rep",
        description=(
            "Create and verify a business representative for a business customer. "
            "Creates the representative, generates a face verification link, and submits verification. "
            "Requires customer_id (must be a business customer), first_name, last_name, email, "
            "phone, date_of_birth, and address. Optional: roles, ownership_percentage, title."
        ),
    )
    async def onboard_business_rep(
        customer_id: str,
        first_name: str,
        last_name: str,
        email: str,
        phone: str,
        date_of_birth: str,
        address: dict[str, Any],
        roles: list[str] | None = None,
        ownership_percentage: float | None = None,
        title: str | None = None,
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            customer_id = validate_customer_id(customer_id)
            return await business_rep_svc.onboard_representative(
                customer_id=customer_id,
                first_name=first_name,
                last_name=last_name,
                email=email,
                phone=phone,
                date_of_birth=date_of_birth,
                address=address,
                roles=roles,
                ownership_percentage=ownership_percentage,
                title=title,
            )
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="setup_virtual_account",
        description=(
            "Create a virtual account for automatic fiat-to-stablecoin conversion. "
            "Provides bank deposit instructions that auto-convert deposits to stablecoins. "
            "source_currency: 'usd' or 'eur'. "
            "destination_currency: 'usdc', 'usdt', or 'eurc'. "
            "network: blockchain network for destination (e.g., 'sol', 'ethereum', 'polygon'). "
            "wallet_address: destination wallet address. "
            "Optional integrator_bps_fee: basis points fee (default '0')."
        ),
    )
    async def setup_virtual_account(
        customer_id: str,
        source_currency: str,
        destination_currency: str,
        network: str,
        wallet_address: str,
        integrator_bps_fee: str = "0",
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            customer_id = validate_customer_id(customer_id)
            source_currency = validate_source_currency(source_currency)
            destination_currency = validate_dest_currency(destination_currency)
            network = validate_network(network)
            return await virtual_account_svc.setup_virtual_account(
                customer_id=customer_id,
                source_currency=source_currency,
                destination_currency=destination_currency,
                network=network,
                wallet_address=wallet_address,
                integrator_bps_fee=integrator_bps_fee,
            )
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="setup_offloader_wallet",
        description=(
            "Create an offloader wallet for automatic stablecoin-to-fiat off-ramp. "
            "Requires customer_id, currency (stablecoin: 'usdc', 'usdt', 'eurc'), "
            "network (blockchain network), and wallet_address."
        ),
    )
    async def setup_offloader_wallet(
        customer_id: str,
        currency: str,
        network: str,
        wallet_address: str,
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            customer_id = validate_customer_id(customer_id)
            currency = validate_currency(currency)
            network = validate_network(network)
            return await offloader_wallet_svc.setup_offloader_wallet(
                customer_id=customer_id,
                currency=currency,
                network=network,
                wallet_address=wallet_address,
            )
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="create_webhook",
        description=(
            "Create a webhook to receive event notifications. "
            "Provide a name, url (HTTPS endpoint), and events to subscribe to. "
            "Use ['*'] to subscribe to all events. "
            "Example events: customer.create, transfer.update, bankAccount.create. "
            "Returns a secret for webhook signature verification - save it."
        ),
    )
    async def create_webhook(
        name: str,
        url: str,
        events: list[str],
        description: str | None = None,
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            payload: dict[str, Any] = {"name": name, "url": url, "events": events}
            if description:
                payload["description"] = description
            result = await gateway.create_webhook(payload)
            return {
                "webhook": result.get("data", {}).get("webhook", result.get("data", result)),
                "important": "Save the 'secret' field - it's needed to verify webhook signatures",
                "next_steps": [
                    "Implement signature verification using the webhook secret",
                    "Use get_webhook to check delivery status",
                    "Use get_event to inspect individual events",
                ],
            }
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="submit_cctp_offramp",
        description=(
            "Submit a CCTP (Cross-Chain Transfer Protocol) burn transaction for off-ramp redemption. "
            "source_chain: 'sei' or 'noble'. "
            "source_transaction_hash: the burn transaction hash from the source chain. "
            "transfer_id: the payout/transfer ID (format: payout_...)."
        ),
    )
    async def submit_cctp_offramp(
        source_chain: str,
        source_transaction_hash: str,
        transfer_id: str,
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            source_chain = validate_cctp_chain(source_chain)
            transfer_id = validate_payout_id(transfer_id)
            payload = {
                "sourceChain": source_chain,
                "sourceTransactionHash": source_transaction_hash,
                "transferId": transfer_id,
            }
            result = await gateway.submit_cctp_offramp(payload)
            return {
                "redemption": result.get("data", result),
                "status_lifecycle": "PENDING -> BURNED -> ATTESTED -> MINTED (or FAILED)",
                "next_steps": [
                    "Monitor redemption status - it will progress through BURNED -> ATTESTED -> MINTED",
                    "Use get_transfer with the original transfer ID to check transfer status",
                ],
            }
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)


def register_read_tools(mcp: FastMCP, gateway: SpherePayGateway) -> None:
    """Register the 15 atomic read-only tools directly on the MCP server."""

    @mcp.tool(
        name="get_customer",
        description="Retrieve a SpherePay customer by ID. Returns customer details including verification status.",
    )
    async def get_customer(customer_id: str) -> dict[str, Any]:
        new_correlation_id()
        try:
            customer_id = validate_customer_id(customer_id)
            return await gateway.get_customer(customer_id)
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="list_customers",
        description="List SpherePay customers with pagination. Returns paginated customer list.",
    )
    async def list_customers(page: int = 1, limit: int = 10) -> dict[str, Any]:
        new_correlation_id()
        try:
            return await gateway.list_customers({"page": page, "limit": limit})
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="get_transfer",
        description="Retrieve a SpherePay transfer by ID. Returns transfer details including status.",
    )
    async def get_transfer(transfer_id: str) -> dict[str, Any]:
        new_correlation_id()
        try:
            transfer_id = validate_transfer_id(transfer_id)
            return await gateway.get_transfer(transfer_id)
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="list_transfers",
        description="List SpherePay transfers with pagination and optional filters.",
    )
    async def list_transfers(
        page: int = 1,
        limit: int = 10,
        customer: str | None = None,
        status: str | None = None,
        transfer_type: str | None = None,
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            params: dict[str, Any] = {"page": page, "limit": limit}
            if customer:
                params["customer"] = customer
            if status:
                params["status"] = status
            if transfer_type:
                params["type"] = transfer_type
            return await gateway.list_transfers(params)
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="get_bank_account",
        description="Retrieve a SpherePay bank account by ID. Returns account details including status.",
    )
    async def get_bank_account(bank_account_id: str) -> dict[str, Any]:
        new_correlation_id()
        try:
            bank_account_id = validate_bank_account_id(bank_account_id)
            return await gateway.get_bank_account(bank_account_id)
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="list_bank_accounts",
        description="List SpherePay bank accounts with pagination. Optionally filter by customer_id.",
    )
    async def list_bank_accounts(
        page: int = 1,
        limit: int = 10,
        customer_id: str | None = None,
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            params: dict[str, Any] = {"page": page, "limit": limit}
            if customer_id:
                params["customerId"] = customer_id
            return await gateway.list_bank_accounts(params)
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="get_wallet",
        description="Retrieve a SpherePay wallet by ID. Returns wallet details including address and network.",
    )
    async def get_wallet(wallet_id: str) -> dict[str, Any]:
        new_correlation_id()
        try:
            wallet_id = validate_wallet_id(wallet_id)
            return await gateway.get_wallet(wallet_id)
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="list_wallets",
        description="List SpherePay wallets with pagination. Optionally filter by customer_id.",
    )
    async def list_wallets(
        page: int = 1,
        limit: int = 10,
        customer_id: str | None = None,
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            params: dict[str, Any] = {"page": page, "limit": limit}
            if customer_id:
                params["customerId"] = customer_id
            return await gateway.list_wallets(params)
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    # --- Virtual Account reads ---

    @mcp.tool(
        name="get_virtual_account",
        description="Retrieve a SpherePay virtual account by ID. Returns account details including deposit instructions.",
    )
    async def get_virtual_account(virtual_account_id: str) -> dict[str, Any]:
        new_correlation_id()
        try:
            virtual_account_id = validate_virtual_account_id(virtual_account_id)
            return await gateway.get_virtual_account(virtual_account_id)
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="list_virtual_accounts",
        description="List SpherePay virtual accounts with pagination.",
    )
    async def list_virtual_accounts(page: int = 1, limit: int = 10) -> dict[str, Any]:
        new_correlation_id()
        try:
            return await gateway.list_virtual_accounts({"page": page, "limit": limit})
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="list_virtual_account_transfers",
        description="List transfers for a specific virtual account. Shows deposit activity and conversion history.",
    )
    async def list_virtual_account_transfers(
        virtual_account_id: str,
        page: int = 1,
        limit: int = 10,
    ) -> dict[str, Any]:
        new_correlation_id()
        try:
            virtual_account_id = validate_virtual_account_id(virtual_account_id)
            return await gateway.list_virtual_account_transfers(virtual_account_id, {"page": page, "limit": limit})
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    # --- Offloader Wallet reads ---

    @mcp.tool(
        name="get_offloader_wallet",
        description="Retrieve a SpherePay offloader wallet by ID. Returns wallet details and off-ramp configuration.",
    )
    async def get_offloader_wallet(offloader_wallet_id: str) -> dict[str, Any]:
        new_correlation_id()
        try:
            offloader_wallet_id = validate_offloader_wallet_id(offloader_wallet_id)
            return await gateway.get_offloader_wallet(offloader_wallet_id)
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="list_offloader_wallets",
        description="List SpherePay offloader wallets with pagination.",
    )
    async def list_offloader_wallets(page: int = 1, limit: int = 10) -> dict[str, Any]:
        new_correlation_id()
        try:
            return await gateway.list_offloader_wallets({"page": page, "limit": limit})
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    # --- Webhook & Event reads ---

    @mcp.tool(
        name="get_webhook",
        description="Retrieve a SpherePay webhook by ID. Returns webhook details including delivery status and subscribed events.",
    )
    async def get_webhook(webhook_id: str) -> dict[str, Any]:
        new_correlation_id()
        try:
            webhook_id = validate_webhook_id(webhook_id)
            return await gateway.get_webhook(webhook_id)
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)

    @mcp.tool(
        name="get_event",
        description="Retrieve a SpherePay event by ID. Returns event details including type, payload, and webhook delivery records.",
    )
    async def get_event(event_id: str) -> dict[str, Any]:
        new_correlation_id()
        try:
            event_id = validate_event_id(event_id)
            return await gateway.get_event(event_id)
        except ApiError as e:
            return _error_response(e)
        except Exception as e:
            return _unexpected_error(e)
