"""EOA transaction builder for direct Escrow contract interaction.

These functions build unsigned transaction dicts that an externally owned
account (EOA) can sign and broadcast directly, bypassing the Safe/relay flow.

Before calling ``createTrade``, the EOA must approve the Escrow contract
to spend USDC via :func:`build_approve_usdc_tx`.
"""

from __future__ import annotations

from typing import Any, cast

from eth_account.signers.local import LocalAccount
from web3 import Web3
from web3.types import TxParams

from talarion._constants import (
    ERC20_APPROVE_ABI,
    ESCROW_ABI,
    ESCROW_ADDRESS,
    MAX_UINT256,
    POLYGON_USDC_ADDRESS,
    ZERO_ADDRESS,
)
from talarion.models import SubmitResult


def build_create_trade_tx(
    result: SubmitResult,
    *,
    escrow_address: str = ESCROW_ADDRESS,
    chain_id: int = 137,
    gas: int = 300_000,
) -> dict[str, Any]:
    """Build an unsigned ``createTrade`` transaction for an EOA.

    The ``funder`` parameter is set to ``address(0)`` so that ``msg.sender``
    pays the margin directly. The EOA must be either the buyer or seller
    in the submit result, and must have approved the Escrow contract for USDC.

    Args:
        result: The :class:`~talarion.models.SubmitResult` from
            :meth:`~talarion.client.TalarionClient.submit_order`.
        escrow_address: Checksummed address of the Escrow contract.
        chain_id: Chain ID (default 137 for Polygon).
        gas: Gas limit.

    Returns:
        An unsigned transaction dict. Set ``from``, ``nonce``, and gas price
        fields before signing.
    """
    w3 = Web3()

    escrow = w3.eth.contract(
        address=Web3.to_checksum_address(escrow_address),
        abi=ESCROW_ABI,
    )

    trade_args = {
        "instrumentId": bytes.fromhex(result.instrument_bytes_hex.removeprefix("0x")),
        "size": result.size_int,
        "price": result.price_int,
        "buyer": Web3.to_checksum_address(result.buyer_checksum),
        "seller": Web3.to_checksum_address(result.seller_checksum),
        "salt": bytes.fromhex(result.salt_hex.removeprefix("0x")),
    }

    funder = Web3.to_checksum_address(ZERO_ADDRESS)

    tx: dict[str, Any] = dict(
        escrow.functions.createTrade(trade_args, funder).build_transaction(
            cast(TxParams, {
                    "chainId": chain_id,
                    "gas": gas,
                    "gasPrice": 0,
                    "nonce": 0,
                },
            )
        )
    )
    tx.pop("from", None)
    return tx


def build_cancel_trade_tx(
    trade_id: str,
    *,
    escrow_address: str = ESCROW_ADDRESS,
    chain_id: int = 137,
    gas: int = 200_000,
) -> dict[str, Any]:
    """Build an unsigned ``cancelPendingTrade`` transaction for an EOA.

    Args:
        trade_id: The bytes32 trade ID (hex string, with or without 0x prefix).
        escrow_address: Checksummed address of the Escrow contract.
        chain_id: Chain ID.
        gas: Gas limit.

    Returns:
        An unsigned transaction dict. Set ``from``, ``nonce``, and gas price
        fields before signing.
    """
    w3 = Web3()

    escrow = w3.eth.contract(
        address=Web3.to_checksum_address(escrow_address),
        abi=ESCROW_ABI,
    )

    trade_id_bytes = bytes.fromhex(trade_id.removeprefix("0x"))
    funder = Web3.to_checksum_address(ZERO_ADDRESS)

    tx: dict[str, Any] = dict(
        escrow.functions.cancelPendingTrade(trade_id_bytes, funder).build_transaction(
            cast(TxParams, {
                    "chainId": chain_id,
                    "gas": gas,
                    "gasPrice": 0,
                    "nonce": 0,
                },
            )
        )
    )
    tx.pop("from", None)
    return tx


def build_approve_usdc_tx(
    *,
    usdc_address: str = POLYGON_USDC_ADDRESS,
    escrow_address: str = ESCROW_ADDRESS,
    amount: int = MAX_UINT256,
    chain_id: int = 137,
    gas: int = 100_000,
) -> dict[str, Any]:
    """Build an unsigned USDC ``approve`` transaction.

    The EOA must approve the Escrow contract to transfer USDC before
    calling ``createTrade``. All parameters default to Polygon mainnet values.

    Args:
        usdc_address: USDC token contract address (default: Polygon canonical USDC).
        escrow_address: Escrow contract address (spender).
        amount: Approval amount in raw units (default: max uint256).
        chain_id: Chain ID.
        gas: Gas limit.

    Returns:
        An unsigned transaction dict.
    """
    w3 = Web3()

    usdc = w3.eth.contract(
        address=Web3.to_checksum_address(usdc_address),
        abi=ERC20_APPROVE_ABI,
    )

    tx: dict[str, Any] = dict(
        usdc.functions.approve(
            Web3.to_checksum_address(escrow_address), amount
        ).build_transaction(
            cast(TxParams, {
                    "chainId": chain_id,
                    "gas": gas,
                    "gasPrice": 0,
                    "nonce": 0,
                },
            )
        )
    )
    tx.pop("from", None)
    return tx


def sign_and_send(
    tx: dict[str, Any],
    private_key: str,
    rpc_url: str,
    *,
    nonce: int | None = None,
    gas_price: int | None = None,
    max_fee_per_gas: int | None = None,
    max_priority_fee_per_gas: int | None = None,
) -> str:
    """Sign a transaction and broadcast it. Returns the transaction hash.

    This is a convenience function. For production use, manage your own
    Web3 provider and nonce tracking.

    Args:
        tx: Transaction dict from one of the ``build_*`` functions.
        private_key: Hex-encoded private key of the EOA.
        rpc_url: JSON-RPC endpoint URL.
        nonce: Explicit nonce. If ``None``, fetched from the chain.
        gas_price: Legacy gas price in wei.
        max_fee_per_gas: EIP-1559 max fee per gas in wei.
        max_priority_fee_per_gas: EIP-1559 priority fee in wei.

    Returns:
        Transaction hash as a hex string.
    """
    w3 = Web3(Web3.HTTPProvider(rpc_url))

    account: LocalAccount = w3.eth.account.from_key(private_key)
    tx["from"] = account.address

    if nonce is None:
        tx["nonce"] = w3.eth.get_transaction_count(account.address)
    else:
        tx["nonce"] = nonce

    if max_fee_per_gas is not None:
        tx.pop("gasPrice", None)
        tx["maxFeePerGas"] = max_fee_per_gas
        tx["maxPriorityFeePerGas"] = max_priority_fee_per_gas or w3.eth.max_priority_fee
    elif gas_price is not None:
        tx["gasPrice"] = gas_price
    else:
        # EIP-1559 defaults — use try/except because some Polygon RPC nodes
        # don't support eth_maxPriorityFeePerGas and raise on access.
        tx.pop("gasPrice", None)
        try:
            priority_fee = w3.eth.max_priority_fee
        except Exception:
            priority_fee = 180_000_000_000  # 180 gwei fallback
        base_fee = w3.eth.gas_price
        tx["maxPriorityFeePerGas"] = priority_fee
        tx["maxFeePerGas"] = base_fee + priority_fee * 2

    signed = account.sign_transaction(tx)
    tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
    return tx_hash.hex()
