//! Transaction signing.

use crate::order_id::compute_order_item_id_at_index;
use crate::sdk_compat::serialize_for_sdk_signing;
use crate::types::*;
use crate::{Error, Keypair, NonceManager, Result};
use ed25519_dalek::Signer as DalekSigner;
use rayon::prelude::*;
use serde_json::json;

/// Threshold for switching to parallel signing.
const PARALLEL_THRESHOLD: usize = 10;

/// High-performance signer.
pub struct Signer {
    keypair: Keypair,
    nonce_manager: Option<NonceManager>,
    serializer: Vec<u8>,
    compute_order_id: bool,
    compute_batch_order_ids: bool,
}

impl Signer {
    /// Create a signer.
    pub fn new(keypair: Keypair) -> Self {
        Self {
            keypair,
            nonce_manager: None,
            serializer: Vec::with_capacity(512),
            compute_order_id: true,
            compute_batch_order_ids: false,
        }
    }

    /// Create a signer with nonce management.
    pub fn with_nonce_manager(keypair: Keypair, nonce_manager: NonceManager) -> Self {
        Self {
            keypair,
            nonce_manager: Some(nonce_manager),
            serializer: Vec::with_capacity(512),
            compute_order_id: true,
            compute_batch_order_ids: false,
        }
    }

    /// Disable optional pre-computed order ID generation.
    pub fn without_order_id(mut self) -> Self {
        self.compute_order_id = false;
        self
    }

    /// Enable optional pre-computed order ID generation.
    pub fn with_order_id(mut self) -> Self {
        self.compute_order_id = true;
        self
    }

    /// Check whether order ID generation is enabled.
    pub fn computes_order_id(&self) -> bool {
        self.compute_order_id
    }

    /// Set whether single-order ID generation is enabled.
    pub fn set_order_id(&mut self, enabled: bool) {
        self.compute_order_id = enabled;
    }

    /// Enable optional pre-computed batch order IDs for multi-order transactions.
    ///
    /// When enabled, `sign_group()` and grouped legacy batch methods include `order_ids`.
    /// Default is disabled to avoid extra hashing work in hot paths.
    pub fn with_batch_order_ids(mut self) -> Self {
        self.compute_batch_order_ids = true;
        self
    }

    /// Disable optional pre-computed batch order IDs.
    pub fn without_batch_order_ids(mut self) -> Self {
        self.compute_batch_order_ids = false;
        self
    }

    /// Check whether batch order ID generation is enabled.
    pub fn computes_batch_order_ids(&self) -> bool {
        self.compute_batch_order_ids
    }

    /// Set whether batch order ID generation is enabled.
    pub fn set_batch_order_ids(&mut self, enabled: bool) {
        self.compute_batch_order_ids = enabled;
    }

    /// Get signer pubkey.
    pub fn pubkey(&self) -> Pubkey {
        self.keypair.pubkey()
    }

    /// Sign raw bytes and return base58 signature.
    pub fn sign_bytes(&self, message: &[u8]) -> String {
        let signature = self.keypair.signing_key().sign(message);
        bs58::encode(signature.to_bytes()).into_string()
    }

    fn next_nonce(&self) -> u64 {
        self.nonce_manager
            .as_ref()
            .map(|m| m.next())
            .unwrap_or_else(crate::nonce::current_timestamp_millis)
    }

    /// Low-level signing entrypoint.
    pub fn sign_action(
        &mut self,
        action: &Action,
        nonce: u64,
        account: &Pubkey,
    ) -> Result<SignedTransaction> {
        let signer_pubkey = self.keypair.pubkey();

        serialize_for_sdk_signing(action, nonce, account, &mut self.serializer)?;

        let order_id = if self.compute_order_id {
            self.compute_action_order_id(action, nonce, account)
        } else {
            None
        };
        let order_ids = if self.compute_batch_order_ids {
            self.compute_action_order_ids(action, nonce, account)
        } else {
            None
        };

        let signature = self.sign_bytes(&self.serializer);
        let actions = self.action_to_json(action)?;

        Ok(SignedTransaction {
            actions,
            nonce,
            account: account.to_base58(),
            signer: signer_pubkey.to_base58(),
            signature,
            order_id,
            order_ids,
        })
    }

    /// Sign using signer pubkey as account.
    pub fn sign_action_self(&mut self, action: &Action, nonce: u64) -> Result<SignedTransaction> {
        let account = self.keypair.pubkey();
        self.sign_action(action, nonce, &account)
    }

    /// Sign a single order item.
    pub fn sign(&mut self, item: OrderItem, nonce: Option<u64>) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::Order { orders: vec![item] };
        self.sign_action_self(&action, nonce)
    }

    /// Sign multiple independent items in parallel.
    pub fn sign_all(
        &self,
        items: Vec<OrderItem>,
        base_nonce: Option<u64>,
    ) -> Result<Vec<SignedTransaction>> {
        if items.is_empty() {
            return Ok(vec![]);
        }

        let base = base_nonce.unwrap_or_else(crate::nonce::current_timestamp_millis);
        if items.len() < PARALLEL_THRESHOLD {
            items
                .into_iter()
                .enumerate()
                .map(|(i, item)| self.sign_single_item(item, base + i as u64))
                .collect()
        } else {
            items
                .into_par_iter()
                .enumerate()
                .map(|(i, item)| self.sign_single_item(item, base + i as u64))
                .collect()
        }
    }

    /// Sign multiple items atomically as one transaction.
    pub fn sign_group(
        &mut self,
        items: Vec<OrderItem>,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        if items.is_empty() {
            return Err(Error::EmptyOrders);
        }
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::Order { orders: items };
        self.sign_action_self(&action, nonce)
    }

    fn sign_single_item(&self, item: OrderItem, nonce: u64) -> Result<SignedTransaction> {
        let account = self.keypair.pubkey();
        let signer_pubkey = self.keypair.pubkey();
        let order_id = if self.compute_order_id {
            let mut scratch = Vec::with_capacity(96);
            compute_order_item_id_at_index(&item, 0, nonce, &account, &mut scratch)
                .map(|id| id.to_base58())
        } else {
            None
        };

        let action = Action::Order { orders: vec![item] };
        let mut serializer = Vec::with_capacity(512);
        serialize_for_sdk_signing(&action, nonce, &account, &mut serializer)?;

        let signature = self.sign_bytes(&serializer);
        let actions = self.action_to_json(&action)?;

        Ok(SignedTransaction {
            actions,
            nonce,
            account: account.to_base58(),
            signer: signer_pubkey.to_base58(),
            signature,
            order_id,
            order_ids: None,
        })
    }

    /// Sign a faucet action.
    pub fn sign_faucet(&mut self, nonce: Option<u64>) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::Faucet(Faucet::new(self.keypair.pubkey()));
        self.sign_action_self(&action, nonce)
    }

    /// Sign agent wallet creation/deletion.
    pub fn sign_agent_wallet(
        &mut self,
        agent: Pubkey,
        delete: bool,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::AgentWalletCreation(AgentWallet { agent, delete });
        self.sign_action_self(&action, nonce)
    }

    /// Sign user settings update.
    pub fn sign_user_settings(
        &mut self,
        settings: UserSettings,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::UpdateUserSettings(settings);
        self.sign_action_self(&action, nonce)
    }

    /// Sign one or more oracle price updates (`px` actions).
    pub fn sign_oracle_prices(
        &mut self,
        oracles: Vec<OraclePrice>,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        if oracles.is_empty() {
            return Err(Error::InvalidOrder(
                "oracle prices array cannot be empty".to_string(),
            ));
        }
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::Oracle { oracles };
        self.sign_action_self(&action, nonce)
    }

    /// Sign a batch Pyth oracle update (`o` action).
    pub fn sign_pyth_oracle(
        &mut self,
        oracles: Vec<PythOraclePrice>,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        if oracles.is_empty() {
            return Err(Error::InvalidOrder(
                "pyth oracle array cannot be empty".to_string(),
            ));
        }
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::PythOracle { oracles };
        self.sign_action_self(&action, nonce)
    }

    /// Sign whitelist faucet access update (admin).
    pub fn sign_whitelist_faucet(
        &mut self,
        target: Pubkey,
        whitelist: bool,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::WhitelistFaucet(WhitelistFaucet { target, whitelist });
        self.sign_action_self(&action, nonce)
    }

    /// Sign a sub-account creation (optionally with initial margin transfer).
    pub fn sign_create_sub_account(
        &mut self,
        sub_account: CreateSubAccount,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::CreateSubAccount(sub_account);
        self.sign_action_self(&action, nonce)
    }

    /// Sign a sub-account removal.
    pub fn sign_remove_sub_account(
        &mut self,
        to_remove: Pubkey,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::RemoveSubAccount(RemoveSubAccount { to_remove });
        self.sign_action_self(&action, nonce)
    }

    /// Sign a sub-account rename.
    pub fn sign_rename_sub_account(
        &mut self,
        rename: RenameSubAccount,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::RenameSubAccount(rename);
        self.sign_action_self(&action, nonce)
    }

    /// Sign a margin transfer between accounts.
    pub fn sign_transfer(
        &mut self,
        transfer: Transfer,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::Transfer(transfer);
        self.sign_action_self(&action, nonce)
    }

    /// Sign a portfolio withdraw.
    pub fn sign_withdraw(
        &mut self,
        withdraw: Withdraw,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::Withdraw(withdraw);
        self.sign_action_self(&action, nonce)
    }

    /// Sign a withdraw-lock recovery.
    pub fn sign_withdraw_lock_recover(
        &mut self,
        recover: WithdrawLockRecover,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::WithdrawLockRecover(recover);
        self.sign_action_self(&action, nonce)
    }

    /// Sign a multisig creation.
    pub fn sign_create_multisig(
        &mut self,
        create_multisig: CreateMultisig,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::CreateMultisig(create_multisig);
        self.sign_action_self(&action, nonce)
    }

    /// Sign a multisig proposal.
    pub fn sign_multisig_propose(
        &mut self,
        propose: MultisigPropose,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::MultisigPropose(propose);
        self.sign_action_self(&action, nonce)
    }

    /// Sign a multisig approval.
    pub fn sign_multisig_approve(
        &mut self,
        approve: MultisigApprove,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::MultisigApprove(approve);
        self.sign_action_self(&action, nonce)
    }

    /// Sign a multisig rejection.
    pub fn sign_multisig_reject(
        &mut self,
        reject: MultisigReject,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::MultisigReject(reject);
        self.sign_action_self(&action, nonce)
    }

    /// Sign a multisig cancellation.
    pub fn sign_multisig_cancel(
        &mut self,
        cancel: MultisigCancel,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::MultisigCancel(cancel);
        self.sign_action_self(&action, nonce)
    }

    /// Sign a multisig execution.
    pub fn sign_multisig_execute(
        &mut self,
        execute: MultisigExecute,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::MultisigExecute(execute);
        self.sign_action_self(&action, nonce)
    }

    /// Sign a multisig policy update.
    pub fn sign_update_multisig_policy(
        &mut self,
        update: UpdateMultisigPolicy,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        let nonce = nonce.unwrap_or_else(|| self.next_nonce());
        let action = Action::UpdateMultisigPolicy(update);
        self.sign_action_self(&action, nonce)
    }

    /// Deprecated compatibility alias.
    #[deprecated(
        since = "0.2.0",
        note = "Use sign(), sign_all(), or sign_group() instead"
    )]
    pub fn sign_order(
        &mut self,
        orders: Vec<OrderItem>,
        nonce: Option<u64>,
    ) -> Result<SignedTransaction> {
        self.sign_group(orders, nonce)
    }

    /// Deprecated compatibility alias.
    #[deprecated(since = "0.2.0", note = "Use sign_all() for simple batches")]
    pub fn sign_orders_batch(
        &self,
        order_batches: Vec<Vec<OrderItem>>,
        base_nonce: Option<u64>,
    ) -> Result<Vec<SignedTransaction>> {
        if order_batches.is_empty() {
            return Ok(vec![]);
        }

        let base = base_nonce.unwrap_or_else(crate::nonce::current_timestamp_millis);
        if order_batches.len() < PARALLEL_THRESHOLD {
            order_batches
                .into_iter()
                .enumerate()
                .map(|(i, orders)| self.sign_single_order_batch(orders, base + i as u64))
                .collect()
        } else {
            order_batches
                .into_par_iter()
                .enumerate()
                .map(|(i, orders)| self.sign_single_order_batch(orders, base + i as u64))
                .collect()
        }
    }

    fn sign_single_order_batch(
        &self,
        orders: Vec<OrderItem>,
        nonce: u64,
    ) -> Result<SignedTransaction> {
        if orders.is_empty() {
            return Err(Error::EmptyOrders);
        }

        let account = self.keypair.pubkey();
        let signer_pubkey = self.keypair.pubkey();
        let order_id = if self.compute_order_id && orders.len() == 1 {
            let mut scratch = Vec::with_capacity(96);
            compute_order_item_id_at_index(&orders[0], 0, nonce, &account, &mut scratch)
                .map(|id| id.to_base58())
        } else {
            None
        };
        let order_ids = if self.compute_batch_order_ids && orders.len() > 1 {
            let mut scratch = Vec::with_capacity(96);
            let mut ids = Vec::with_capacity(orders.len());
            for (idx, item) in orders.iter().enumerate() {
                if let Some(id) =
                    compute_order_item_id_at_index(item, idx as u32, nonce, &account, &mut scratch)
                {
                    ids.push(id.to_base58());
                }
            }
            if ids.is_empty() {
                None
            } else {
                Some(ids)
            }
        } else {
            None
        };

        let action = Action::Order { orders };
        let mut serializer = Vec::with_capacity(512);
        serialize_for_sdk_signing(&action, nonce, &account, &mut serializer)?;
        let signature = self.sign_bytes(&serializer);
        let actions = self.action_to_json(&action)?;

        Ok(SignedTransaction {
            actions,
            nonce,
            account: account.to_base58(),
            signer: signer_pubkey.to_base58(),
            signature,
            order_id,
            order_ids,
        })
    }

    fn compute_action_order_id(
        &self,
        action: &Action,
        nonce: u64,
        account: &Pubkey,
    ) -> Option<String> {
        match action {
            Action::Order { orders } if orders.len() == 1 => {
                let mut scratch = Vec::with_capacity(96);
                compute_order_item_id_at_index(&orders[0], 0, nonce, account, &mut scratch)
                    .map(|id| id.to_base58())
            }
            _ => None,
        }
    }

    fn compute_action_order_ids(
        &self,
        action: &Action,
        nonce: u64,
        account: &Pubkey,
    ) -> Option<Vec<String>> {
        match action {
            Action::Order { orders } if orders.len() > 1 => {
                let mut scratch = Vec::with_capacity(96);
                let mut ids = Vec::with_capacity(orders.len());
                for (idx, item) in orders.iter().enumerate() {
                    if let Some(id) = compute_order_item_id_at_index(
                        item,
                        idx as u32,
                        nonce,
                        account,
                        &mut scratch,
                    ) {
                        ids.push(id.to_base58());
                    }
                }
                if ids.is_empty() {
                    None
                } else {
                    Some(ids)
                }
            }
            _ => None,
        }
    }

    fn action_to_json(&self, action: &Action) -> Result<Vec<serde_json::Value>> {
        match action {
            Action::Order { orders } => orders
                .iter()
                .map(|item| self.order_item_to_json(item))
                .collect(),
            Action::Faucet(faucet) => {
                let mut faucet_obj = json!({ "u": faucet.user.to_base58() });
                if let Some(amount) = faucet.amount {
                    faucet_obj["amount"] = json!(amount);
                }
                Ok(vec![json!({ "faucet": faucet_obj })])
            }
            Action::AgentWalletCreation(agent) => Ok(vec![json!({
                "agentWalletCreation": {
                    "a": agent.agent.to_base58(),
                    "d": agent.delete
                }
            })]),
            Action::UpdateUserSettings(settings) => {
                let mut ordered = settings.max_leverage.clone();
                ordered.sort_unstable_by(|left, right| left.0.cmp(&right.0));
                let mut leverage = serde_json::Map::with_capacity(ordered.len());
                for (symbol, lev) in ordered {
                    leverage.insert(symbol, json!(lev));
                }
                Ok(vec![json!({ "updateUserSettings": { "m": leverage } })])
            }
            Action::Oracle { oracles } => Ok(oracles
                .iter()
                .map(|o| {
                    json!({
                        "px": {
                            "t": o.timestamp,
                            "c": o.asset,
                            "px": o.price
                        }
                    })
                })
                .collect()),
            Action::PythOracle { oracles } => {
                let entries: Vec<_> = oracles
                    .iter()
                    .map(|o| {
                        json!({
                            "t": o.timestamp,
                            "fi": o.feed_index,
                            "px": o.price,
                            "e": o.exponent
                        })
                    })
                    .collect();
                Ok(vec![json!({ "o": { "oracles": entries } })])
            }
            Action::WhitelistFaucet(action) => Ok(vec![json!({
                "whitelistFaucet": {
                    "target": action.target.to_base58(),
                    "whitelist": action.whitelist
                }
            })]),
            Action::CreateSubAccount(action) => {
                let mut obj = json!({ "name": action.name });
                if let Some(amount) = action.margin_amount {
                    obj["marginAmount"] = json!(amount);
                }
                Ok(vec![json!({ "createSubAccount": obj })])
            }
            Action::RemoveSubAccount(action) => Ok(vec![json!({
                "removeSubAccount": {
                    "toRemove": action.to_remove.to_base58()
                }
            })]),
            Action::RenameSubAccount(action) => Ok(vec![json!({
                "renameSubAccount": {
                    "account": action.account.to_base58(),
                    "name": action.name
                }
            })]),
            Action::Transfer(transfer) => Ok(vec![json!({
                "transfer": {
                    "k": match transfer.kind {
                        TransferKind::Internal => "internal",
                        TransferKind::External => "external",
                    },
                    "from": transfer.from.to_base58(),
                    "to": transfer.to.to_base58(),
                    "marginAmount": transfer.margin_amount,
                }
            })]),
            Action::Withdraw(withdraw) => Ok(vec![json!({
                "withdraw": {
                    "u": withdraw.user.to_base58(),
                    "v": withdraw.vault.to_base58(),
                    "rta": withdraw.recipient_token_account.to_base58(),
                    "a": withdraw.amount,
                    "b": withdraw.blockhash.to_base58(),
                }
            })]),
            Action::WithdrawLockRecover(recover) => Ok(vec![json!({
                "withdrawLockRecover": {
                    "u": recover.user.to_base58(),
                    "h": recover.hash.to_base58(),
                }
            })]),
            Action::CreateMultisig(action) => Ok(vec![json!({
                "createMultisig": {
                    "signers": action.signers.iter().map(Pubkey::to_base58).collect::<Vec<_>>(),
                    "threshold": action.threshold,
                    "timeLockSecs": action.time_lock_secs,
                    "proposalLifetimeSecs": action.proposal_lifetime_secs,
                }
            })]),
            Action::MultisigPropose(action) => {
                let mut inner_actions = Vec::new();
                for inner in &action.actions {
                    inner_actions.extend(self.action_to_json(inner)?);
                }
                Ok(vec![json!({
                    "msp": {
                        "m": action.multisig.to_base58(),
                        "a": inner_actions,
                    }
                })])
            }
            Action::MultisigApprove(action) => Ok(vec![json!({
                "msa": {
                    "m": action.multisig.to_base58(),
                    "p": action.proposal_id,
                }
            })]),
            Action::MultisigReject(action) => Ok(vec![json!({
                "msr": {
                    "m": action.multisig.to_base58(),
                    "p": action.proposal_id,
                }
            })]),
            Action::MultisigCancel(action) => Ok(vec![json!({
                "msc": {
                    "m": action.multisig.to_base58(),
                    "p": action.proposal_id,
                }
            })]),
            Action::MultisigExecute(action) => Ok(vec![json!({
                "mse": {
                    "m": action.multisig.to_base58(),
                    "p": action.proposal_id,
                }
            })]),
            Action::UpdateMultisigPolicy(action) => Ok(vec![json!({
                "msu": {
                    "m": action.multisig.to_base58(),
                    "signers": action.signers.iter().map(Pubkey::to_base58).collect::<Vec<_>>(),
                    "threshold": action.threshold,
                    "timeLockSecs": action.time_lock_secs,
                    "proposalLifetimeSecs": action.proposal_lifetime_secs,
                }
            })]),
        }
    }

    fn order_item_to_json(&self, item: &OrderItem) -> Result<serde_json::Value> {
        match item {
            OrderItem::Order(order) => match &order.order_type {
                OrderType::Limit { tif } => {
                    let tif_str = match tif {
                        TimeInForce::Gtc => "GTC",
                        TimeInForce::Ioc => "IOC",
                        TimeInForce::Alo => "ALO",
                    };
                    Ok(json!({
                        "l": {
                            "c": order.symbol,
                            "b": order.is_buy,
                            "px": order.price,
                            "sz": order.size,
                            "tif": tif_str,
                            "r": order.reduce_only,
                            "i": order.iso
                        }
                    }))
                }
                OrderType::Trigger {
                    is_market,
                    trigger_px: _,
                } => {
                    if !is_market {
                        return Err(Error::InvalidOrder(
                            "trigger orders are not supported by BULK API; use market".to_string(),
                        ));
                    }
                    Ok(json!({
                        "m": {
                            "c": order.symbol,
                            "b": order.is_buy,
                            "sz": order.size,
                            "r": order.reduce_only,
                            "i": order.iso
                        }
                    }))
                }
            },
            OrderItem::Modify(modify) => Ok(json!({
                "mod": {
                    "oid": modify.order_id.to_base58(),
                    "c": modify.symbol,
                    "sz": modify.amount
                }
            })),
            OrderItem::Cancel(cancel) => Ok(json!({
                "cx": {
                    "c": cancel.symbol,
                    "oid": cancel.order_id.to_base58()
                }
            })),
            OrderItem::CancelAll(cancel_all) => Ok(json!({
                "cxa": {
                    "c": cancel_all.symbols
                }
            })),
            OrderItem::Stop(stop) => Ok(json!({
                "st": {
                    "c": stop.symbol,
                    "d": stop.is_buy,
                    "sz": stop.size,
                    "tr": stop.trigger_price,
                    "lim": stop.limit_price,
                    "i": stop.iso
                }
            })),
            OrderItem::TakeProfit(tp) => Ok(json!({
                "tp": {
                    "c": tp.symbol,
                    "d": tp.is_buy,
                    "sz": tp.size,
                    "tr": tp.trigger_price,
                    "lim": tp.limit_price,
                    "i": tp.iso
                }
            })),
            OrderItem::RangeOco(rng) => Ok(json!({
                "rng": {
                    "c": rng.symbol,
                    "d": rng.is_buy,
                    "sz": rng.size,
                    "pmin": rng.collar_min,
                    "pmax": rng.collar_max,
                    "lmin": rng.limit_min,
                    "lmax": rng.limit_max,
                    "i": rng.iso
                }
            })),
            OrderItem::TriggerBasket(trig) => {
                let nested: Result<Vec<_>> = trig
                    .actions
                    .iter()
                    .map(|a| self.order_item_to_json(a))
                    .collect();
                Ok(json!({
                    "trig": {
                        "c": trig.symbol,
                        "d": trig.is_buy,
                        "tr": trig.trigger_price,
                        "actions": nested?,
                        "i": trig.iso
                    }
                }))
            }
            OrderItem::OnFill(of) => {
                let actions: Result<Vec<_>> = of
                    .actions
                    .iter()
                    .map(|a| self.order_item_to_json(a))
                    .collect();
                Ok(json!({
                    "of": {
                        "p": of.p,
                        "actions": actions?
                    }
                }))
            }
            OrderItem::TrailingStop(trl) => Ok(json!({
                "trl": {
                    "c": trl.symbol,
                    "b": trl.is_buy,
                    "sz": trl.size,
                    "trb": trl.trail_bps,
                    "stb": trl.step_bps,
                    "lim": trl.limit_price,
                    "i": trl.iso
                }
            })),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_sign_single() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair);
        let order = Order::limit("BTC-USD", true, 100000.0, 0.1, TimeInForce::Gtc);
        let signed = signer.sign(order.into(), Some(1234567890)).unwrap();

        assert_eq!(signed.nonce, 1234567890);
        assert_eq!(signed.actions.len(), 1);
        assert!(signed.actions[0].get("l").is_some());
        assert!(!signed.signature.is_empty());
    }

    #[test]
    fn test_sign_modify_uses_compact_sdk_keys() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair);
        let modify = Modify::new(Hash::random(), "BTC-USD", 0.25);
        let signed = signer
            .sign(OrderItem::Modify(modify), Some(1234567890))
            .unwrap();

        let mod_obj = signed.actions[0].get("mod").unwrap();
        assert!(mod_obj.get("c").is_some());
        assert!(mod_obj.get("sz").is_some());
        assert!(mod_obj.get("symbol").is_none());
        assert!(mod_obj.get("amount").is_none());
    }

    #[test]
    fn test_sign_all_parallel_nonce_increment() {
        let keypair = Keypair::generate();
        let signer = Signer::new(keypair);
        let orders: Vec<OrderItem> = (0..20)
            .map(|i| {
                Order::limit(
                    "BTC-USD",
                    i % 2 == 0,
                    100000.0 + i as f64,
                    0.1,
                    TimeInForce::Gtc,
                )
                .into()
            })
            .collect();

        let signed = signer.sign_all(orders, Some(1000000)).unwrap();
        assert_eq!(signed.len(), 20);
        for (i, tx) in signed.iter().enumerate() {
            assert_eq!(tx.nonce, 1000000 + i as u64);
            assert!(tx.order_id.is_some());
            assert!(tx.order_ids.is_none());
        }
    }

    #[test]
    fn test_sign_group_atomic() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair);
        let bracket: Vec<OrderItem> = vec![
            Order::limit("BTC-USD", true, 100000.0, 0.1, TimeInForce::Gtc).into(),
            Order::limit("BTC-USD", false, 99000.0, 0.1, TimeInForce::Gtc).into(),
            Order::limit("BTC-USD", false, 110000.0, 0.1, TimeInForce::Gtc).into(),
        ];

        let signed = signer.sign_group(bracket, Some(1234567890)).unwrap();
        assert_eq!(signed.actions.len(), 3);
        assert_eq!(signed.nonce, 1234567890);
        assert!(signed.order_ids.is_none());
    }

    #[test]
    fn test_sign_faucet() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair);
        let signed = signer.sign_faucet(Some(1234567890)).unwrap();
        assert_eq!(signed.actions.len(), 1);
        assert!(signed.actions[0].get("faucet").is_some());
    }

    #[test]
    fn test_sign_agent_wallet() {
        let keypair = Keypair::generate();
        let agent_keypair = Keypair::generate();
        let mut signer = Signer::new(keypair);
        let signed = signer
            .sign_agent_wallet(agent_keypair.pubkey(), false, Some(1234567890))
            .unwrap();
        assert!(signed.actions[0].get("agentWalletCreation").is_some());
    }

    #[test]
    fn test_sign_group_empty_error() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair);
        let result = signer.sign_group(vec![], Some(1234567890));
        assert!(matches!(result, Err(Error::EmptyOrders)));
    }

    #[test]
    fn test_sign_oracle_prices() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair);
        let signed = signer
            .sign_oracle_prices(
                vec![OraclePrice {
                    timestamp: 1704067200000000000,
                    asset: "BTC".to_string(),
                    price: 102500.0,
                }],
                Some(1234567890),
            )
            .unwrap();
        assert_eq!(signed.actions.len(), 1);
        assert!(signed.actions[0].get("px").is_some());
    }

    #[test]
    fn test_sign_pyth_oracle() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair);
        let signed = signer
            .sign_pyth_oracle(
                vec![PythOraclePrice {
                    timestamp: 1704067200000000000,
                    feed_index: 1,
                    price: 10250000000000,
                    exponent: -8,
                }],
                Some(1234567890),
            )
            .unwrap();
        assert_eq!(signed.actions.len(), 1);
        assert!(signed.actions[0].get("o").is_some());
    }

    #[test]
    fn test_sign_user_settings_uses_sdk_map_shape() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair);
        let signed = signer
            .sign_user_settings(
                UserSettings::new(vec![("BTC".to_string(), 5.0), ("ETH".to_string(), 3.0)]),
                Some(1234567890),
            )
            .unwrap();
        let obj = signed.actions[0].get("updateUserSettings").unwrap();
        let leverage = obj.get("m").and_then(|v| v.as_object()).unwrap();
        assert_eq!(leverage.get("BTC").and_then(|v| v.as_f64()), Some(5.0));
        assert_eq!(leverage.get("ETH").and_then(|v| v.as_f64()), Some(3.0));
    }

    #[test]
    fn test_sign_user_settings_is_stable_across_input_order() {
        let keypair = Keypair::generate();
        let settings_a =
            UserSettings::new(vec![("BTC".to_string(), 5.0), ("ETH".to_string(), 3.0)]);
        let settings_b =
            UserSettings::new(vec![("ETH".to_string(), 3.0), ("BTC".to_string(), 5.0)]);

        let mut signer_a = Signer::new(keypair.clone());
        let mut signer_b = Signer::new(keypair);

        let signed_a = signer_a
            .sign_user_settings(settings_a, Some(1234567890))
            .unwrap();
        let signed_b = signer_b
            .sign_user_settings(settings_b, Some(1234567890))
            .unwrap();

        assert_eq!(signed_a.signature, signed_b.signature);
        assert_eq!(signed_a.actions, signed_b.actions);
    }

    #[test]
    fn test_sign_create_sub_account() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair);
        let signed = signer
            .sign_create_sub_account(CreateSubAccount::new("desk-1"), Some(1234567890))
            .unwrap();
        assert_eq!(signed.actions.len(), 1);
        let obj = signed.actions[0].get("createSubAccount").unwrap();
        assert_eq!(obj.get("name").and_then(|v| v.as_str()), Some("desk-1"));
        assert!(obj.get("marginSymbol").is_none());
        assert!(obj.get("marginAmount").is_none());
    }

    #[test]
    fn test_sign_create_sub_account_with_margin() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair);
        let signed = signer
            .sign_create_sub_account(
                CreateSubAccount::with_margin("desk-1", 1000.0),
                Some(1234567890),
            )
            .unwrap();
        let obj = signed.actions[0].get("createSubAccount").unwrap();
        assert!(obj.get("marginSymbol").is_none());
        assert_eq!(
            obj.get("marginAmount").and_then(|v| v.as_f64()),
            Some(1000.0)
        );
    }

    #[test]
    fn test_sign_remove_sub_account() {
        let keypair = Keypair::generate();
        let target = Keypair::generate().pubkey();
        let mut signer = Signer::new(keypair);
        let signed = signer
            .sign_remove_sub_account(target, Some(1234567890))
            .unwrap();
        let obj = signed.actions[0].get("removeSubAccount").unwrap();
        assert_eq!(
            obj.get("toRemove").and_then(|v| v.as_str()),
            Some(target.to_base58().as_str())
        );
    }

    #[test]
    fn test_sign_rename_sub_account() {
        let keypair = Keypair::generate();
        let target = Keypair::generate().pubkey();
        let mut signer = Signer::new(keypair);
        let signed = signer
            .sign_rename_sub_account(RenameSubAccount::new(target, "desk-2"), Some(1234567890))
            .unwrap();
        let obj = signed.actions[0].get("renameSubAccount").unwrap();
        assert_eq!(
            obj.get("account").and_then(|v| v.as_str()),
            Some(target.to_base58().as_str())
        );
        assert_eq!(obj.get("name").and_then(|v| v.as_str()), Some("desk-2"));
    }

    #[test]
    fn test_sign_transfer_internal() {
        let keypair = Keypair::generate();
        let from = keypair.pubkey();
        let to = Keypair::generate().pubkey();
        let mut signer = Signer::new(keypair);
        let signed = signer
            .sign_transfer(Transfer::internal(from, to, 10.0), Some(1234567890))
            .unwrap();
        let obj = signed.actions[0].get("transfer").unwrap();
        assert_eq!(obj.get("k").and_then(|v| v.as_str()), Some("internal"));
        assert!(obj.get("marginSymbol").is_none());
        assert_eq!(obj.get("marginAmount").and_then(|v| v.as_f64()), Some(10.0));
    }

    #[test]
    fn test_sign_whitelist_faucet() {
        let keypair = Keypair::generate();
        let target = Keypair::generate().pubkey();
        let mut signer = Signer::new(keypair);
        let signed = signer
            .sign_whitelist_faucet(target, true, Some(1234567890))
            .unwrap();
        assert_eq!(signed.actions.len(), 1);
        assert!(signed.actions[0].get("whitelistFaucet").is_some());
    }

    #[test]
    fn test_order_id_computed_by_default() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair);
        let order = Order::limit("BTC-USD", true, 100000.0, 0.1, TimeInForce::Gtc);
        let signed = signer.sign(order.into(), Some(1234567890)).unwrap();
        assert!(signed.order_id.is_some());
    }

    #[test]
    fn test_order_id_disabled() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair).without_order_id();
        let order = Order::limit("BTC-USD", true, 100000.0, 0.1, TimeInForce::Gtc);
        let signed = signer.sign(order.into(), Some(1234567890)).unwrap();
        assert!(signed.order_id.is_none());
    }

    #[test]
    fn test_batch_order_ids_optional_enabled() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair).with_batch_order_ids();
        assert!(signer.computes_batch_order_ids());

        let bracket: Vec<OrderItem> = vec![
            Order::limit("BTC-USD", true, 100000.0, 0.1, TimeInForce::Gtc).into(),
            Order::limit("BTC-USD", false, 99000.0, 0.1, TimeInForce::Gtc).into(),
            Order::limit("BTC-USD", false, 110000.0, 0.1, TimeInForce::Gtc).into(),
        ];

        let signed = signer.sign_group(bracket, Some(1234567890)).unwrap();
        let ids = signed.order_ids.expect("order_ids should be present");
        assert_eq!(ids.len(), 3);
        assert!(signed.order_id.is_none());
    }

    #[test]
    #[allow(deprecated)]
    fn test_legacy_methods_still_work() {
        let keypair = Keypair::generate();
        let mut signer = Signer::new(keypair.clone());
        let order = Order::limit("BTC-USD", true, 100000.0, 0.1, TimeInForce::Gtc);
        let one = signer
            .sign_order(vec![order.clone().into()], Some(100))
            .unwrap();
        assert_eq!(one.actions.len(), 1);

        let signer = Signer::new(keypair);
        let batches = vec![vec![order.into()]];
        let many = signer.sign_orders_batch(batches, Some(200)).unwrap();
        assert_eq!(many.len(), 1);
    }

    #[test]
    #[allow(deprecated)]
    fn test_legacy_batch_order_ids_enabled() {
        let keypair = Keypair::generate();
        let signer = Signer::new(keypair).with_batch_order_ids();
        let batches = vec![vec![
            Order::limit("BTC-USD", true, 100000.0, 0.1, TimeInForce::Gtc).into(),
            Order::limit("BTC-USD", false, 110000.0, 0.1, TimeInForce::Gtc).into(),
        ]];

        let signed = signer.sign_orders_batch(batches, Some(300)).unwrap();
        assert_eq!(signed.len(), 1);
        assert_eq!(signed[0].order_ids.as_ref().map(Vec::len), Some(2));
    }
}
