"""Input validation functions for the tool boundary.

Validates and normalizes LLM inputs before they reach the service layer.
Uses existing domain value objects to ensure consistency.
See ADR-0013 for rationale.
"""

from __future__ import annotations

from decimal import Decimal, InvalidOperation

from spherepay_mcp.domain.value_objects import (
    ApiError,
    BankAccountId,
    BusinessRepId,
    CctpChain,
    Currency,
    CustomerId,
    ErrorCategory,
    EventId,
    Network,
    OffloaderWalletId,
    PayoutId,
    TransferId,
    VirtualAccountId,
    WalletId,
    WebhookId,
)

_CURRENCY_VALUES = {c.value for c in Currency}
_NETWORK_VALUES = {n.value for n in Network}
_CCTP_CHAIN_VALUES = {c.value for c in CctpChain}
_FIAT_CURRENCIES = {"usd", "eur"}
_STABLECOIN_CURRENCIES = {"usdc", "usdt", "eurc"}

# Common aliases LLMs use that we can normalize
_NETWORK_ALIASES: dict[str, str] = {
    "solana": "sol",
    "eth": "ethereum",
    "poly": "polygon",
    "arb": "arbitrum",
    "avax": "avalanche",
    "opt": "optimism",
}


def validate_customer_id(raw: str) -> str:
    """Validate customer ID format. Returns the ID unchanged if valid."""
    try:
        CustomerId(raw)
        return raw
    except ValueError:
        raise ApiError(
            category=ErrorCategory.VALIDATION,
            message=(
                f"Invalid customer_id format. Expected: 'customer_' followed by 32 hex characters "
                f"(e.g., 'customer_aaaabbbbccccddddeeeeffffaaaabbbb'). Got: '{raw}'"
            ),
            status_code=400,
        )


def validate_bank_account_id(raw: str) -> str:
    """Validate bank account ID format."""
    try:
        BankAccountId(raw)
        return raw
    except ValueError:
        raise ApiError(
            category=ErrorCategory.VALIDATION,
            message=(
                f"Invalid bank_account_id format. Expected: 'bankAccount_' followed by 32 hex characters "
                f"(e.g., 'bankAccount_aaaabbbbccccddddeeeeffffaaaabbbb'). Got: '{raw}'"
            ),
            status_code=400,
        )


def validate_wallet_id(raw: str) -> str:
    """Validate wallet ID format."""
    try:
        WalletId(raw)
        return raw
    except ValueError:
        raise ApiError(
            category=ErrorCategory.VALIDATION,
            message=(
                f"Invalid wallet_id format. Expected: 'wallet_' followed by 32 hex characters "
                f"(e.g., 'wallet_aaaabbbbccccddddeeeeffffaaaabbbb'). Got: '{raw}'"
            ),
            status_code=400,
        )


def validate_transfer_id(raw: str) -> str:
    """Validate transfer ID format."""
    try:
        TransferId(raw)
        return raw
    except ValueError:
        raise ApiError(
            category=ErrorCategory.VALIDATION,
            message=(
                f"Invalid transfer_id format. Expected: 'transfer_' followed by 32 hex characters "
                f"(e.g., 'transfer_aaaabbbbccccddddeeeeffffaaaabbbb'). Got: '{raw}'"
            ),
            status_code=400,
        )


def validate_business_rep_id(raw: str) -> str:
    """Validate business representative ID format."""
    try:
        BusinessRepId(raw)
        return raw
    except ValueError:
        raise ApiError(
            category=ErrorCategory.VALIDATION,
            message=(
                f"Invalid business_rep_id format. Expected: 'businessRep_' followed by 32 hex characters "
                f"(e.g., 'businessRep_aaaabbbbccccddddeeeeffffaaaabbbb'). Got: '{raw}'"
            ),
            status_code=400,
        )


def validate_amount(raw: str | int | float) -> str:
    """Coerce and validate amount to string format. Accepts str, int, or float."""
    try:
        d = Decimal(str(raw))
    except (InvalidOperation, ValueError, TypeError):
        raise ApiError(
            category=ErrorCategory.VALIDATION,
            message=f"Invalid amount: '{raw}'. Must be a positive number (e.g., '100.00', 100, 99.5).",
            status_code=400,
        )
    if d <= 0:
        raise ApiError(
            category=ErrorCategory.VALIDATION,
            message=f"Amount must be positive. Got: '{raw}'.",
            status_code=400,
        )
    return str(d)


def validate_currency(raw: str) -> str:
    """Normalize and validate currency code (case-insensitive)."""
    normalized = raw.strip().lower()
    if normalized in _CURRENCY_VALUES:
        return normalized
    raise ApiError(
        category=ErrorCategory.VALIDATION,
        message=(f"Invalid currency: '{raw}'. Supported currencies: {', '.join(sorted(_CURRENCY_VALUES))}."),
        status_code=400,
    )


def validate_network(raw: str) -> str:
    """Normalize and validate network name (case-insensitive, with alias support)."""
    normalized = raw.strip().lower()
    # Check aliases first
    if normalized in _NETWORK_ALIASES:
        return _NETWORK_ALIASES[normalized]
    if normalized in _NETWORK_VALUES:
        return normalized
    raise ApiError(
        category=ErrorCategory.VALIDATION,
        message=(f"Invalid network: '{raw}'. Supported networks: {', '.join(sorted(_NETWORK_VALUES))}."),
        status_code=400,
    )


def validate_instrument_type(raw: str) -> str:
    """Validate funding instrument type."""
    normalized = raw.strip().lower()
    if normalized in ("bank_account", "wallet"):
        return normalized
    raise ApiError(
        category=ErrorCategory.VALIDATION,
        message=f"Invalid instrument_type: '{raw}'. Must be 'bank_account' or 'wallet'.",
        status_code=400,
    )


def validate_customer_type(raw: str) -> str:
    """Validate customer type."""
    normalized = raw.strip().lower()
    if normalized in ("individual", "business"):
        return normalized
    raise ApiError(
        category=ErrorCategory.VALIDATION,
        message=f"Invalid customer_type: '{raw}'. Must be 'individual' or 'business'.",
        status_code=400,
    )


def validate_virtual_account_id(raw: str) -> str:
    """Validate virtual account ID format."""
    try:
        VirtualAccountId(raw)
        return raw
    except ValueError:
        raise ApiError(
            category=ErrorCategory.VALIDATION,
            message=(
                f"Invalid virtual_account_id format. Expected: 'virtualAccount_' followed by 32 hex characters "
                f"(e.g., 'virtualAccount_aaaabbbbccccddddeeeeffffaaaabbbb'). Got: '{raw}'"
            ),
            status_code=400,
        )


def validate_offloader_wallet_id(raw: str) -> str:
    """Validate offloader wallet ID format."""
    try:
        OffloaderWalletId(raw)
        return raw
    except ValueError:
        raise ApiError(
            category=ErrorCategory.VALIDATION,
            message=(
                f"Invalid offloader_wallet_id format. Expected: 'offloaderWallet_' followed by 32 hex characters "
                f"(e.g., 'offloaderWallet_aaaabbbbccccddddeeeeffffaaaabbbb'). Got: '{raw}'"
            ),
            status_code=400,
        )


def validate_webhook_id(raw: str) -> str:
    """Validate webhook ID format."""
    try:
        WebhookId(raw)
        return raw
    except ValueError:
        raise ApiError(
            category=ErrorCategory.VALIDATION,
            message=(
                f"Invalid webhook_id format. Expected: 'webhook_' followed by 32 hex characters "
                f"(e.g., 'webhook_aaaabbbbccccddddeeeeffffaaaabbbb'). Got: '{raw}'"
            ),
            status_code=400,
        )


def validate_event_id(raw: str) -> str:
    """Validate event ID format."""
    try:
        EventId(raw)
        return raw
    except ValueError:
        raise ApiError(
            category=ErrorCategory.VALIDATION,
            message=(
                f"Invalid event_id format. Expected: 'event_' followed by 32 hex characters "
                f"(e.g., 'event_aaaabbbbccccddddeeeeffffaaaabbbb'). Got: '{raw}'"
            ),
            status_code=400,
        )


def validate_payout_id(raw: str) -> str:
    """Validate payout ID format (used by CCTP off-ramp)."""
    try:
        PayoutId(raw)
        return raw
    except ValueError:
        raise ApiError(
            category=ErrorCategory.VALIDATION,
            message=(
                f"Invalid transfer_id format for CCTP. Expected: 'payout_' followed by 32 hex characters "
                f"(e.g., 'payout_aaaabbbbccccddddeeeeffffaaaabbbb'). Got: '{raw}'"
            ),
            status_code=400,
        )


def validate_cctp_chain(raw: str) -> str:
    """Validate CCTP source chain."""
    normalized = raw.strip().lower()
    if normalized in _CCTP_CHAIN_VALUES:
        return normalized
    raise ApiError(
        category=ErrorCategory.VALIDATION,
        message=f"Invalid CCTP source chain: '{raw}'. Must be one of: {', '.join(sorted(_CCTP_CHAIN_VALUES))}.",
        status_code=400,
    )


def validate_source_currency(raw: str) -> str:
    """Validate virtual account source currency (fiat only)."""
    normalized = raw.strip().lower()
    if normalized in _FIAT_CURRENCIES:
        return normalized
    raise ApiError(
        category=ErrorCategory.VALIDATION,
        message=f"Invalid source currency: '{raw}'. Virtual accounts support: {', '.join(sorted(_FIAT_CURRENCIES))}.",
        status_code=400,
    )


def validate_dest_currency(raw: str) -> str:
    """Validate virtual account destination currency (stablecoins only)."""
    normalized = raw.strip().lower()
    if normalized in _STABLECOIN_CURRENCIES:
        return normalized
    raise ApiError(
        category=ErrorCategory.VALIDATION,
        message=f"Invalid destination currency: '{raw}'. Virtual accounts support: {', '.join(sorted(_STABLECOIN_CURRENCIES))}.",
        status_code=400,
    )
