from typing import Any, Dict, List, Optional
import os
import ssl
import httpx
from websockets.asyncio.client import connect
from eth_utils import to_checksum_address
from tristero.config import get_config
from tristero.data import get_gas_addr

try:
    import certifi  # type: ignore
except Exception:  # pragma: no cover
    certifi = None


class APIException(Exception):
    pass


class QuoteException(Exception):
    pass


def handle_resp(resp: httpx.Response):
    try:
        resp.raise_for_status()
        data = resp.json()
        return data
    except Exception as e:
        try:
            data = resp.json()
        except Exception as json_e:
            if resp.text:
                raise APIException(resp.text)
            else:
                raise e
        raise APIException(data)


async def t_get(client: httpx.AsyncClient, url: str):
    resp = await client.get(url, headers=get_config().headers, timeout=20)
    return handle_resp(resp)


async def t_post(client: httpx.AsyncClient, url: str, body: Optional[dict[str, Any]]):
    resp = await client.post(url, headers=get_config().headers, json=body, timeout=20)
    return handle_resp(resp)


def or_native(chain_id: str, address: str):
    return get_gas_addr(chain_id) if address == "native" else address

def _env_truthy(name: str) -> bool:
    v = os.getenv(name, "").strip().lower()
    return v in {"1", "true", "yes", "y", "on"}


def _httpx_verify_setting():
    if _env_truthy("TRISTERO_INSECURE_SSL"):
        return False
    if certifi is not None:
        return certifi.where()
    return True


def _ws_ssl_context_for_url(url: str):
    if not url.lower().startswith("wss://"):
        return None
    if _env_truthy("TRISTERO_INSECURE_SSL"):
        return ssl._create_unverified_context()
    if certifi is not None:
        return ssl.create_default_context(cafile=certifi.where())
    return ssl.create_default_context()


# Replace the default client with one that uses certifi (fixes CERTIFICATE_VERIFY_FAILED on some macOS setups)
c = httpx.AsyncClient(timeout=20, verify=_httpx_verify_setting())


async def get_quote(
    from_wallet: str,
    to_wallet: str,
    from_chain_id: str,
    from_address: str,
    to_chain_id: str,
    to_address: str,
    amount: int,
):
    from_token_address = or_native(from_chain_id, from_address)
    to_token_address = or_native(to_chain_id, to_address)

    data = {
        "src_chain_id": from_chain_id,
        "src_token_address": from_token_address,
        "src_token_quantity": str(int(amount)),
        "src_wallet_address": from_wallet,
        "dst_chain_id": to_chain_id,
        "dst_token_address": to_token_address,
        "dst_wallet_address": to_wallet,
    }
    resp = await c.post(
        get_config().quoter_url,
        json=data,
        headers=get_config().headers,
    )
    try:
        return handle_resp(resp)
    except Exception as e:
        raise QuoteException(e, data) from e


async def fill_order(signature: str, domain: dict[str, Any], message: dict[str, Any]):
    data = {"signature": signature, "domain": domain, "message": message}
    resp = await c.post(
        get_config().filler_url,
        json=data,
        headers=get_config().headers,
    )
    return handle_resp(resp)


async def fill_order_feather(src_chain: str, dst_chain: str, dst_address: str, amount: int, client_id: str = ''):
    data = {
        "client_id": client_id,
        "src_chain": src_chain,
        "dst_chain": dst_chain,
        "dst_address": dst_address,
        "amount": amount
    }
    resp = await c.post(
        get_config().filler_url,
        json=data,
        headers=get_config().headers,
    )
    return handle_resp(resp)


async def poll_updates(order_id: str):
    base = get_config().ws_url.rstrip("/")
    ws = await connect(
        f"{base}/{order_id}",
        ssl=_ws_ssl_context_for_url(base),
    )
    return ws


async def poll_updates_feather(order_id: str):
    base = get_config().ws_url.rstrip("/")
    ws = await connect(
        f"{base}/feather/{order_id}",
        ssl=_ws_ssl_context_for_url(base),
    )
    return ws


async def get_margin_quote(
    chain_id: str,
    wallet_address: str,
    quote_currency: str,
    base_currency: str,
    leverage_ratio: int,
    collateral_amount: str,
) -> Dict[str, Any]:
    """
    Get a quote for opening a margin position.
    
    Args:
        chain_id: Chain ID (e.g., "42161" for Arbitrum)
        wallet_address: User's wallet address
        quote_currency: Quote currency token address (e.g., USDC)
        base_currency: Base currency token address (e.g., WETH)
        leverage_ratio: Leverage ratio (e.g., 2 for 2x)
        collateral_amount: Collateral amount in raw units
    
    Returns:
        Quote response with order_data for signing
    """
    payload = {
        "chain_id": chain_id,
        "wallet_address": wallet_address,
        "quote_currency": quote_currency,
        "base_currency": base_currency,
        "leverage_ratio": leverage_ratio,
        "collateral_amount": collateral_amount,
    }
    resp = await c.post(
        f"{get_config().margin_quoter_url.rstrip('/')}/margin",
        json=payload,
        headers=get_config().headers,
    )
    data = handle_resp(resp)
    if not data.get("success"):
        raise QuoteException(data.get("message") or "margin quote failed", payload)
    return data


async def submit_margin_order(signed_order_payload: Dict[str, Any]) -> Dict[str, Any]:
    """
    Submit a signed margin order.
    
    Args:
        signed_order_payload: Signed order from sign_margin_order
    
    Returns:
        Submission response with order_id
    """
    resp = await c.post(
        f"{get_config().margin_filler_url.rstrip('/')}/",
        json=signed_order_payload,
        headers=get_config().headers,
    )
    return handle_resp(resp)


async def submit_close_position(signed_close_payload: Dict[str, Any]) -> Dict[str, Any]:
    """
    Submit a signed close position request.
    
    Args:
        signed_close_payload: Signed close request from sign_close_position
    
    Returns:
        Close response with order_id
    """
    resp = await c.post(
        f"{get_config().margin_filler_url.rstrip('/')}/close-margin-position",
        json=signed_close_payload,
        headers=get_config().headers,
    )
    return handle_resp(resp)


async def get_margin_positions(wallet_address: str) -> List[Dict[str, Any]]:
    """
    Get all margin positions for a wallet.
    
    Args:
        wallet_address: User's wallet address
    
    Returns:
        List of margin positions
    """
    wallet = to_checksum_address(wallet_address)
    resp = await c.get(
        f"{get_config().wallet_server_url}/{wallet}/margin-positions",
        headers=get_config().headers,
    )
    return handle_resp(resp)


async def get_margin_position(position_id: str) -> Dict[str, Any]:
    """
    Get a specific margin position by ID.
    
    Args:
        position_id: Position ID
    
    Returns:
        Margin position details
    """
    resp = await c.get(
        f"{get_config().wallet_server_url}/margin-positions/{position_id}",
        headers=get_config().headers,
    )
    return handle_resp(resp)


async def poll_margin_updates(order_id: str):
    """
    Open a WebSocket connection to poll margin order updates.
    
    Args:
        order_id: Margin order ID
    
    Returns:
        WebSocket connection
    """
    base = get_config().ws_url.rstrip("/")
    ssl_ctx = _ws_ssl_context_for_url(base)

    # Some deployments expose margin order updates on the same channel as normal orders.
    # Try the non-/margin path first (fixes local 403), then fall back to /margin.
    try:
        return await connect(f"{base}/{order_id}", ssl=ssl_ctx)
    except Exception:
        return await connect(f"{base}/margin/{order_id}", ssl=ssl_ctx)
