use crate::neo_clients::JsonRpcProvider;
use crate::neo_x::evm::provider::NeoXProvider;
use crate::neo_x::evm::transaction::NeoXTransaction;
use alloy::primitives::Address as AlloyAddress;
use alloy::primitives::U256 as AlloyU256;
use alloy::providers::{Provider, ProviderBuilder};
use alloy::rpc::types::eth::{TransactionReceipt, TransactionRequest};
use alloy::signers::local::{LocalSignerError, PrivateKeySigner};
use alloy::signers::Signer;
use std::str::FromStr;

/// Neo X EVM Wallet for managing accounts and signing transactions on Neo X.
/// This wraps an alloy `PrivateKeySigner` to provide seamless integration.
///
/// (alloy is the maintained successor to ethers-rs, which was retired.)
#[derive(Debug, Clone)]
pub struct NeoXWallet {
	inner: PrivateKeySigner,
}

impl NeoXWallet {
	/// Creates a new Neo X wallet from a hex-encoded private key string
	pub fn from_private_key(hex_key: &str) -> Result<Self, LocalSignerError> {
		let wallet = PrivateKeySigner::from_str(hex_key)?;
		Ok(Self { inner: wallet })
	}

	/// Creates a new Neo X wallet from randomly generated bytes
	pub fn create_random() -> Self {
		let wallet = PrivateKeySigner::random();
		Self { inner: wallet }
	}

	/// Gets the EVM address associated with this wallet
	pub fn address(&self) -> AlloyAddress {
		self.inner.address()
	}

	/// Returns the underlying alloy signer
	pub fn inner_wallet(&self) -> &PrivateKeySigner {
		&self.inner
	}
}

/// A wrapper to easily send EVM transactions on Neo X using a NeoXWallet and NeoXProvider.
pub struct NeoXClient<'a, P: JsonRpcProvider> {
	pub wallet: NeoXWallet,
	pub provider: NeoXProvider<'a, P>,
}

impl<'a, P: JsonRpcProvider + 'static> NeoXClient<'a, P> {
	/// Creates a new Neo X EVM client
	pub fn new(wallet: NeoXWallet, provider: NeoXProvider<'a, P>) -> Self {
		Self { wallet, provider }
	}

	/// Gets the balance of the wallet's address on Neo X
	pub async fn get_balance(&self) -> Result<AlloyU256, String> {
		let evm = self.provider.evm_provider().ok_or("No EVM provider configured")?;
		let address = self.wallet.address();
		evm.get_balance(address).await.map_err(|e| e.to_string())
	}

	/// Sends a `NeoXTransaction` using the configured wallet and provider
	pub async fn send_transaction(
		&self,
		tx: NeoXTransaction,
	) -> Result<TransactionReceipt, String> {
		self.send_transaction_request(tx.into_alloy_request()).await
	}

	/// Sends an Alloy transaction request using the configured wallet and provider.
	///
	/// Use this method when the request needs EVM values wider than the legacy
	/// [`NeoXTransaction`] representation supports.
	pub async fn send_transaction_request(
		&self,
		request: TransactionRequest,
	) -> Result<TransactionReceipt, String> {
		// Ensure an EVM provider is configured (validates the RPC URL early).
		let _evm = self.provider.evm_provider().ok_or("No EVM provider configured")?;

		let chain_id = self.provider.chain_id().await.map_err(|e| e.to_string())?;
		let signer = self.wallet.inner.clone().with_chain_id(Some(chain_id));

		// alloy composes a signing provider by attaching the wallet to a fresh
		// HTTP connection derived from the provider's RPC URL. This is the
		// stateless send pattern recommended by alloy.
		let url = self
			.provider
			.rpc_url()
			.parse()
			.map_err(|e: url::ParseError| format!("Invalid Neo X RPC URL: {e}"))?;
		let client = ProviderBuilder::new().wallet(signer).connect_http(url);

		let pending_tx = client.send_transaction(request).await.map_err(|e| e.to_string())?;

		let receipt = pending_tx.get_receipt().await.map_err(|e| e.to_string())?;

		Ok(receipt)
	}
}
