//! EIP-712 order signing payloads.
//!
//! This module implements construction of EIP-712 signing digests for Tenbin
//! orders. The SDK handles all protocol-specific details (struct hashing, field
//! ordering, domain separation) - you just sign the resulting digest.
//!
//! ## Workflow
//!
//! 1. Obtain the `domain_separator` from [`crate::rest::client::RestClient::get_deployment`]
//! 2. Build your [`Order`](crate::types::Order) struct
//! 3. Call `signing_digest` to get the 32-byte hash
//! 4. Sign that hash using your [`PayloadSigner`](crate::signing::signer::PayloadSigner)
//! 5. Attach the signature to the order and submit
//!
//! ## Example
//!
//! ```ignore
//! use tenbin_sdk::signing::order::signing_digest;
//! use tenbin_sdk::types::{Order, OrderType};
//!
//! // 1. Get deployment info (contains domain_separator)
//! let deployment = client.rest.get_deployment().await?;
//!
//! // 2. Build order (without signature initially)
//! let mut order = Order {
//!     order_type: OrderType::Mint,
//!     // ... other fields ...
//!     signature: None,
//!     // ...
//! };
//!
//! // 3. Compute digest
//! let digest = signing_digest(&order, deployment.domain_separator);
//!
//! // 4. Sign with your signer
//! let signature = signer.sign_hash(&digest).await?;
//!
//! // 5. Attach and submit
//! order.signature = Some(signature);
//! let id = client.rest.submit_order(&order).await?;
//! ```

use alloy::primitives::{B256, FixedBytes, U256, b256, keccak256};
use alloy::sol_types::SolValue;

use crate::bindings::controller::IController;
use crate::types::{Order, OrderType};

const ORDER_TYPE_HASH: B256 =
    b256!("0xae5dedc94a8efc31fc4f6c152a75beefdcf468a567060934203dd9a9eb5b0055");

/// Compute the EIP-712 struct hash for an order.
fn struct_hash(order: &Order) -> B256 {
    let order_type_u256 = match order.order_type {
        OrderType::Mint => U256::from(0u8),
        OrderType::Redeem => U256::from(1u8),
    };

    let so = IController::Order::from(order);

    let encoded = (
        ORDER_TYPE_HASH,
        order_type_u256,
        so.nonce,
        so.expiry,
        so.payer,
        so.recipient,
        so.collateral_token,
        so.collateral_amount,
        so.asset_amount,
    )
        .abi_encode();

    keccak256(encoded)
}

/// Compute the EIP-712 signing digest for an order.
///
/// Returns the exact 32-byte hash that must be signed by the payer using
/// ECDSA over secp256k1. The digest incorporates the domain separator to
/// prevent cross-chain replay attacks.
///
/// # Arguments
/// * `order` - The order to sign (signature field is ignored)
/// * `domain_separator` - The EIP-712 domain separator from deployment info
///
/// # Returns
/// A 32-byte keccak256 hash ready for signing.
pub fn signing_digest(order: &Order, domain_separator: B256) -> B256 {
    let prefix = FixedBytes::<2>::from([0x19, 0x01]);
    let struct_hash = struct_hash(order);

    let packed = (prefix, domain_separator, struct_hash).abi_encode_packed();
    keccak256(packed)
}

#[cfg(test)]
mod tests {
    use crate::fixtures::valid_order;

    use super::*;

    #[test]
    fn signing_digest_is_deterministic_and_non_zero() {
        let order = valid_order();
        let domain = B256::from([0x55u8; 32]);

        let d1 = signing_digest(&order, domain);
        let d2 = signing_digest(&order, domain);

        assert_eq!(d1, d2);
        assert_ne!(d1, B256::ZERO);
    }

    #[test]
    fn mint_and_redeem_orders_have_distinct_digests() {
        let mut mint = valid_order();
        let mut redeem = valid_order();
        mint.order_type = OrderType::Mint;
        redeem.order_type = OrderType::Redeem;
        let domain = B256::from([0x77u8; 32]);

        let mint_digest = signing_digest(&mint, domain);
        let redeem_digest = signing_digest(&redeem, domain);

        assert_ne!(mint_digest, redeem_digest);
    }
}
