// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! High-level SDK for interacting with the Rialo blockchain.
//!
//! This module provides a simplified, high-level interface for common blockchain
//! operations such as querying accounts, checking balances, requesting airdrops,
//! and transferring tokens. It's designed for use in examples, benchmarks, and
//! simple applications.

use std::str::FromStr;

#[cfg(not(target_arch = "wasm32"))]
use crate::rpc::{request_airdrop_with_confirmation, wait_for_confirmation};
use crate::{
    error::{Result, RialoError},
    generated::{rpc_client::RpcClient, types::AccountInfo},
    keyring::Keyring,
    rpc::types::{Pubkey, Signature},
    ClientContext,
};

/// Configuration for creating a Rialo SDK client.
///
/// This struct holds the necessary configuration for connecting to a Rialo node
/// and managing a keyring for signing transactions.
#[derive(Clone)]
pub struct RialoConfig {
    /// The RPC URL to connect to
    pub rpc_url: String,
    /// The keyring to use for signing transactions
    pub keyring: Keyring,
}

impl RialoConfig {
    /// Creates a new RialoConfig with the specified RPC URL and keyring.
    ///
    /// # Arguments
    ///
    /// * `rpc_url` - The URL of the Rialo RPC endpoint
    /// * `keyring` - The keyring to use for signing transactions
    ///
    /// # Example
    ///
    /// ```no_run
    /// use rialo_cdk::sdk::RialoConfig;
    /// use rialo_cdk::keyring::Keyring;
    /// use ed25519_dalek::SigningKey;
    ///
    /// let keypair = SigningKey::generate(&mut rand::thread_rng());
    /// let keyring = Keyring::new("test".to_string(), keypair, None, None);
    /// let config = RialoConfig::new("http://127.0.0.1:4104".to_string(), keyring);
    /// ```
    pub fn new(rpc_url: String, keyring: Keyring) -> Self {
        Self { rpc_url, keyring }
    }

    /// Creates a new RialoConfig with the specified RPC URL and wallet.
    ///
    /// # Deprecated
    ///
    /// Use `new()` with a `Keyring` instead.
    #[deprecated(since = "0.2.0", note = "Use new() with a Keyring instead")]
    pub fn with_wallet(rpc_url: String, wallet: Keyring) -> Self {
        Self::new(rpc_url, wallet)
    }
}

/// The main Rialo SDK client for interacting with the Rialo blockchain.
///
/// This struct provides a high-level interface for common blockchain operations.
/// It wraps a `ClientContext` for RPC communication and a `Keyring` for signing.
///
/// # Example
///
/// ```no_run
/// use rialo_cdk::sdk::{Rialo, RialoConfig};
/// use rialo_cdk::keyring::Keyring;
/// use ed25519_dalek::SigningKey;
///
/// #[tokio::main]
/// async fn main() -> rialo_cdk::Result<()> {
///     let keypair = SigningKey::generate(&mut rand::thread_rng());
///     let keyring = Keyring::new("test".to_string(), keypair, None, None);
///     let config = RialoConfig::new("http://127.0.0.1:4104".to_string(), keyring);
///     
///     let client = Rialo::new(config);
///     let balance = client.get_account_balance(None).await?;
///     println!("Balance: {} kelvin", balance);
///     
///     Ok(())
/// }
/// ```
pub struct Rialo {
    client_context: ClientContext,
    keyring: Keyring,
}

impl Rialo {
    /// Creates a new Rialo SDK client with the provided configuration.
    ///
    /// # Arguments
    ///
    /// * `config` - The SDK configuration containing RPC URL and keyring
    ///
    /// # Example
    ///
    /// ```no_run
    /// use rialo_cdk::sdk::{Rialo, RialoConfig};
    /// use rialo_cdk::keyring::Keyring;
    /// use ed25519_dalek::SigningKey;
    ///
    /// let keypair = SigningKey::generate(&mut rand::thread_rng());
    /// let keyring = Keyring::new("test".to_string(), keypair, None, None);
    /// let config = RialoConfig::new("http://127.0.0.1:4104".to_string(), keyring);
    /// let client = Rialo::new(config);
    /// ```
    pub fn new(config: RialoConfig) -> Self {
        let client_context = ClientContext::new(config.rpc_url);
        Self {
            client_context,
            keyring: config.keyring,
        }
    }

    /// Returns the public key of the active keyring keypair.
    ///
    /// # Returns
    ///
    /// The public key of the keyring's active keypair
    pub fn pubkey(&self) -> Pubkey {
        self.keyring.pubkey()
    }

    /// Returns a reference to the underlying keyring.
    ///
    /// # Returns
    ///
    /// A reference to the keyring
    pub fn keyring(&self) -> &Keyring {
        &self.keyring
    }

    /// Returns a reference to the underlying wallet.
    ///
    /// # Deprecated
    ///
    /// Use `keyring()` instead.
    #[deprecated(since = "0.2.0", note = "Use keyring() instead")]
    pub fn wallet(&self) -> &Keyring {
        &self.keyring
    }

    /// Returns a reference to the underlying client context.
    ///
    /// # Returns
    ///
    /// A reference to the ClientContext
    pub fn client_context(&self) -> &ClientContext {
        &self.client_context
    }

    /// Retrieves the balance for the specified account address.
    ///
    /// If no address is provided, returns the balance for the wallet's active account.
    ///
    /// # Arguments
    ///
    /// * `address` - Optional public key to check. If None, uses the keyring's active keypair.
    ///
    /// # Returns
    ///
    /// The account balance in RLO (as a floating point number)
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use rialo_cdk::sdk::{Rialo, RialoConfig};
    /// # use rialo_cdk::keyring::Keyring;
    /// # use ed25519_dalek::SigningKey;
    /// # #[tokio::main]
    /// # async fn main() -> rialo_cdk::Result<()> {
    /// # let keypair = SigningKey::generate(&mut rand::thread_rng());
    /// # let keyring = Keyring::new("test".to_string(), keypair, None, None);
    /// # let config = RialoConfig::new("http://127.0.0.1:4104".to_string(), keyring);
    /// # let client = Rialo::new(config);
    /// let balance = client.get_account_balance(None).await?;
    /// println!("Balance: {} RLO", balance);
    /// # Ok(())
    /// # }
    /// ```
    pub async fn get_account_balance(&self, address: Option<Pubkey>) -> Result<f64> {
        let addr = address.unwrap_or_else(|| self.pubkey());
        let kelvin = self.client_context.get_balance(&addr).await?;
        Ok(kelvin_to_rlo(kelvin))
    }

    /// Retrieves detailed account information for the specified address.
    ///
    /// If no address is provided, returns information for the keyring's active keypair.
    ///
    /// # Arguments
    ///
    /// * `address` - Optional public key to query. If None, uses the keyring's active keypair.
    ///
    /// # Returns
    ///
    /// Detailed account information including balance, owner, and data
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use rialo_cdk::sdk::{Rialo, RialoConfig};
    /// # use rialo_cdk::keyring::Keyring;
    /// # use ed25519_dalek::SigningKey;
    /// # #[tokio::main]
    /// # async fn main() -> rialo_cdk::Result<()> {
    /// # let keypair = SigningKey::generate(&mut rand::thread_rng());
    /// # let keyring = Keyring::new("test".to_string(), keypair, None, None);
    /// # let config = RialoConfig::new("http://127.0.0.1:4104".to_string(), keyring);
    /// # let client = Rialo::new(config);
    /// let account_info = client.get_account(None).await?;
    /// println!("Owner: {}", account_info.owner);
    /// # Ok(())
    /// # }
    /// ```
    pub async fn get_account(&self, address: Option<Pubkey>) -> Result<AccountInfo> {
        let addr = address.unwrap_or_else(|| self.pubkey());
        self.client_context.get_account_info(&addr).await
    }

    /// Requests an airdrop of RLO tokens to the specified address.
    ///
    /// This is typically used in development and testing environments to fund accounts.
    /// The function waits for the airdrop transaction to be confirmed.
    ///
    /// # Arguments
    ///
    /// * `amount` - The amount of RLO to airdrop
    /// * `address` - Optional public key to receive the airdrop. If None, uses the keyring's active keypair.
    ///
    /// # Returns
    ///
    /// The transaction signature of the airdrop operation
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use rialo_cdk::sdk::{Rialo, RialoConfig};
    /// # use rialo_cdk::keyring::Keyring;
    /// # use ed25519_dalek::SigningKey;
    /// # #[tokio::main]
    /// # async fn main() -> rialo_cdk::Result<()> {
    /// # let keypair = SigningKey::generate(&mut rand::thread_rng());
    /// # let keyring = Keyring::new("test".to_string(), keypair, None, None);
    /// # let config = RialoConfig::new("http://127.0.0.1:4104".to_string(), keyring);
    /// # let client = Rialo::new(config);
    /// let signature = client.airdrop(1.0, None).await?;
    /// println!("Airdrop signature: {}", signature);
    /// # Ok(())
    /// # }
    /// ```
    #[cfg(not(target_arch = "wasm32"))]
    pub async fn airdrop(&self, amount: f64, address: Option<Pubkey>) -> Result<Signature> {
        let addr = address.unwrap_or_else(|| self.pubkey());
        let kelvin = rlo_to_kelvin(amount);

        // Use the airdrop with confirmation utility
        request_airdrop_with_confirmation(&self.client_context, &addr, kelvin, None).await
    }

    /// Calculates the minimum balance required for rent exemption.
    ///
    /// # Arguments
    ///
    /// * `data_size` - The size of the account data in bytes. If None, uses 0.
    ///
    /// # Returns
    ///
    /// The minimum balance in kelvin required for rent exemption
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use rialo_cdk::sdk::{Rialo, RialoConfig};
    /// # use rialo_cdk::keyring::Keyring;
    /// # use ed25519_dalek::SigningKey;
    /// # #[tokio::main]
    /// # async fn main() -> rialo_cdk::Result<()> {
    /// # let keypair = SigningKey::generate(&mut rand::thread_rng());
    /// # let keyring = Keyring::new("test".to_string(), keypair, None, None);
    /// # let config = RialoConfig::new("http://127.0.0.1:4104".to_string(), keyring);
    /// # let client = Rialo::new(config);
    /// let rent = client.get_rent_exemption(Some(1024)).await?;
    /// println!("Rent exemption for 1KB: {} kelvin", rent);
    /// # Ok(())
    /// # }
    /// ```
    pub async fn get_rent_exemption(&self, data_size: Option<usize>) -> Result<u64> {
        let size = data_size.unwrap_or(0) as u64;
        self.client_context
            .get_minimum_balance_for_rent_exemption(size)
            .await
    }

    /// Transfers RLO from the keyring to a recipient.
    ///
    /// This is a simplified transfer method that uses the keyring's active keypair
    /// as the sender and fee payer.
    ///
    /// # Arguments
    ///
    /// * `recipient` - The public key of the account to receive the RLO
    /// * `amount` - The amount of RLO to transfer
    ///
    /// # Returns
    ///
    /// The transaction signature
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use rialo_cdk::sdk::{Rialo, RialoConfig};
    /// # use rialo_cdk::keyring::Keyring;
    /// # use rialo_cdk::rpc::types::Pubkey;
    /// # use ed25519_dalek::SigningKey;
    /// # #[tokio::main]
    /// # async fn main() -> rialo_cdk::Result<()> {
    /// # let keypair = SigningKey::generate(&mut rand::thread_rng());
    /// # let keyring = Keyring::new("test".to_string(), keypair, None, None);
    /// # let config = RialoConfig::new("http://127.0.0.1:4104".to_string(), keyring);
    /// # let client = Rialo::new(config);
    /// # let recipient = Pubkey::new_unique();
    /// let signature = client.transfer(recipient, 0.1).await?;
    /// println!("Transfer signature: {}", signature);
    /// # Ok(())
    /// # }
    /// ```
    #[cfg(feature = "bincode")]
    pub async fn transfer(&self, recipient: Pubkey, amount: f64) -> Result<Signature> {
        use crate::transaction::TransactionBuilder;

        let kelvin = rlo_to_kelvin(amount);
        let sender = self.pubkey();

        // Get current time for valid_from
        let valid_from = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("Time went backwards")
            .as_millis() as i64;

        // Get config hash prefix for replay protection
        let config_hash_prefix = self.client_context.get_config_hash_prefix().await?;

        // Build and sign transaction
        let signed_tx = TransactionBuilder::new(sender, valid_from, config_hash_prefix)
            .add_transfer_instruction(&sender, &recipient, kelvin)
            .sign_with_keypair(&self.keyring, 0)?;

        // Send transaction
        let signature = self
            .client_context
            .send_transaction(&signed_tx, None)
            .await?;

        // Wait for confirmation
        #[cfg(not(target_arch = "wasm32"))]
        wait_for_confirmation(&self.client_context, &signature, None).await?;

        Ok(signature)
    }

    /// Retrieves detailed information about a transaction by its signature.
    ///
    /// # Arguments
    ///
    /// * `signature` - The transaction signature string
    ///
    /// # Returns
    ///
    /// The transaction details as a JSON Value
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use rialo_cdk::sdk::{Rialo, RialoConfig};
    /// # use rialo_cdk::keyring::Keyring;
    /// # use ed25519_dalek::SigningKey;
    /// # #[tokio::main]
    /// # async fn main() -> rialo_cdk::Result<()> {
    /// # let keypair = SigningKey::generate(&mut rand::thread_rng());
    /// # let keyring = Keyring::new("test".to_string(), keypair, None, None);
    /// # let config = RialoConfig::new("http://127.0.0.1:4104".to_string(), keyring);
    /// # let client = Rialo::new(config);
    /// let tx_info = client.get_transaction("5VfTKP...").await?;
    /// println!("Transaction: {}", serde_json::to_string_pretty(&tx_info).unwrap());
    /// # Ok(())
    /// # }
    /// ```
    pub async fn get_transaction(&self, signature: &str) -> Result<serde_json::Value> {
        // Parse signature string
        let sig = Signature::from_str(signature)
            .map_err(|e| RialoError::InvalidInput(format!("Invalid signature: {}", e)))?;

        // Get transaction
        let tx_response = self.client_context.get_transaction(&sig).await?;

        // Convert to JSON for compatibility with existing code
        Ok(serde_json::to_value(tx_response)?)
    }

    /// Deploys a compiled program to the Rialo network.
    ///
    /// This function handles program deployment using the appropriate loader.
    ///
    /// # Arguments
    ///
    /// * `program_path` - Path to the compiled program binary
    ///
    /// # Returns
    ///
    /// The deployed program's public key
    ///
    /// # Example
    ///
    /// ```no_run
    /// # use rialo_cdk::sdk::{Rialo, RialoConfig};
    /// # use rialo_cdk::keyring::Keyring;
    /// # use ed25519_dalek::SigningKey;
    /// # #[tokio::main]
    /// # async fn main() -> rialo_cdk::Result<()> {
    /// # let keypair = SigningKey::generate(&mut rand::thread_rng());
    /// # let keyring = Keyring::new("test".to_string(), keypair, None, None);
    /// # let config = RialoConfig::new("http://127.0.0.1:4104".to_string(), keyring);
    /// # let client = Rialo::new(config);
    /// let program_id = client.deploy_program("/path/to/program.so").await?;
    /// println!("Program deployed: {}", program_id);
    /// # Ok(())
    /// # }
    /// ```
    #[cfg(feature = "bincode")]
    pub async fn deploy_program(&self, program_path: &str) -> Result<Pubkey> {
        use crate::program::{FileProgramDataSource, ProgramDeployment};

        let data_source = FileProgramDataSource::new(program_path);
        let mut deployment = ProgramDeployment::new(data_source);

        deployment.deploy(&self.client_context, &self.keyring).await
    }

    /// Sets a number value in a program (used for testing/examples).
    ///
    /// This is a simplified example method for invoking a test program.
    ///
    /// # Arguments
    ///
    /// * `_program_id` - The program's public key
    /// * `_number` - The number to set
    ///
    /// # Returns
    ///
    /// The transaction signature if successful, or None
    #[cfg(feature = "bincode")]
    pub async fn set_number(&self, _program_id: Pubkey, _number: u32) -> Result<Option<Signature>> {
        // This is a simplified stub - the full implementation would require
        // program-specific instruction building logic
        // For now, we'll return an error indicating this needs to be implemented
        // based on the specific program interface
        Err(RialoError::InvalidInput(
            "set_number requires program-specific implementation".to_string(),
        ))
    }
}

/// Helper function to create a simple Rialo client configuration.
///
/// # Arguments
///
/// * `rpc_url` - The URL of the Rialo RPC endpoint
/// * `wallet` - The wallet to use for signing transactions
///
/// # Returns
///
/// A configured `RialoConfig`
///
/// # Example
///
/// ```no_run
/// use rialo_cdk::sdk::create_client_config;
/// use rialo_cdk::keyring::Keyring;
/// use ed25519_dalek::SigningKey;
///
/// let keypair = SigningKey::generate(&mut rand::thread_rng());
/// let keyring = Keyring::new("test".to_string(), keypair, None, None);
/// let config = create_client_config("http://127.0.0.1:4104", keyring);
/// ```
pub fn create_client_config(rpc_url: &str, keyring: Keyring) -> RialoConfig {
    RialoConfig::new(rpc_url.to_string(), keyring)
}

// Helper functions for unit conversions

/// Converts RLO to kelvin (1 RLO = 1,000,000,000 kelvin)
fn rlo_to_kelvin(rlo: f64) -> u64 {
    (rlo * 1_000_000_000.0) as u64
}

/// Converts kelvin to RLO (1,000,000,000 kelvin = 1 RLO)
fn kelvin_to_rlo(kelvin: u64) -> f64 {
    kelvin as f64 / 1_000_000_000.0
}

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

    #[test]
    fn test_rlo_kelvin_conversion() {
        assert_eq!(rlo_to_kelvin(1.0), 1_000_000_000);
        assert_eq!(rlo_to_kelvin(0.5), 500_000_000);
        assert_eq!(rlo_to_kelvin(0.001), 1_000_000);

        assert_eq!(kelvin_to_rlo(1_000_000_000), 1.0);
        assert_eq!(kelvin_to_rlo(500_000_000), 0.5);
        assert_eq!(kelvin_to_rlo(1_000_000), 0.001);
    }

    #[test]
    fn test_config_creation() {
        let keypair = ed25519_dalek::SigningKey::generate(&mut rand::thread_rng());
        let keyring = Keyring::new("test".to_string(), keypair, None, None);
        let config = create_client_config("http://127.0.0.1:4104", keyring);
        assert_eq!(config.rpc_url, "http://127.0.0.1:4104");
    }
}
