use celestia_types::state::{AccAddress, Address};
use tendermint::crypto::default::ecdsa_secp256k1::SigningKey;
use tendermint::public_key::Secp256k1 as VerifyingKey;

pub use imp::*;

/// [`TestAccount`] stores celestia account credentials and information, for cases where we don't
/// mind jusk keeping the plaintext secret key in memory
#[derive(Debug, Clone)]
pub struct TestAccount {
    /// Bech32 `AccountId` of this account
    pub address: Address,
    /// public key
    #[allow(dead_code)]
    pub verifying_key: VerifyingKey,
    /// private key
    pub signing_key: SigningKey,
}

impl TestAccount {
    pub fn random() -> Self {
        let signing_key = SigningKey::random(&mut rand::rngs::OsRng);
        let verifying_key = *signing_key.verifying_key();

        Self {
            address: AccAddress::new(verifying_key.into()).into(),
            verifying_key,
            signing_key,
        }
    }

    pub fn from_pk(pk: &[u8]) -> Self {
        let signing_key = SigningKey::from_slice(pk).unwrap();
        let verifying_key = *signing_key.verifying_key();

        Self {
            address: AccAddress::new(verifying_key.into()).into(),
            verifying_key,
            signing_key,
        }
    }
}

pub fn load_account() -> TestAccount {
    let hex_key = include_str!("../../ci/credentials/node-0.plaintext-key").trim();

    TestAccount::from_pk(&hex::decode(hex_key).expect("valid hex representation"))
}

#[cfg(not(target_arch = "wasm32"))]
mod imp {
    use std::{future::Future, sync::OnceLock};

    use celestia_rpc::Client;
    use tokio::sync::{Mutex, MutexGuard};

    use super::*;
    use crate::GrpcClient;

    pub const CELESTIA_GRPC_URL: &str = "http://localhost:19090";
    pub const CELESTIA_RPC_URL: &str = "ws://localhost:26658";

    pub fn new_grpc_client() -> GrpcClient {
        GrpcClient::builder()
            .url(CELESTIA_GRPC_URL)
            .build()
            .unwrap()
    }

    pub async fn new_rpc_client() -> Client {
        Client::new(CELESTIA_RPC_URL, None).await.unwrap()
    }

    // we have to sequence the tests which submits transactions.
    // multiple independent tx clients don't work well in parallel
    // as they break each other's account.sequence
    pub async fn new_tx_client() -> (MutexGuard<'static, ()>, GrpcClient) {
        static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
        let lock = LOCK.get_or_init(|| Mutex::new(())).lock().await;

        let creds = load_account();
        let client = GrpcClient::builder()
            .url(CELESTIA_GRPC_URL)
            .signer_keypair(creds.signing_key)
            .build()
            .unwrap();

        (lock, client)
    }

    pub fn spawn<F>(future: F) -> tokio::task::JoinHandle<()>
    where
        F: Future<Output = ()> + Send + 'static,
    {
        tokio::spawn(future)
    }
}

#[cfg(target_arch = "wasm32")]
mod imp {
    use std::future::Future;

    use celestia_rpc::Client as RpcClient;
    use tokio::sync::oneshot;
    use wasm_bindgen_futures::spawn_local;

    use super::*;
    use crate::GrpcClient;

    const CELESTIA_GRPCWEB_PROXY_URL: &str = "http://localhost:18080";
    pub const CELESTIA_RPC_URL: &str = "http://localhost:26658";

    pub fn new_grpc_client() -> GrpcClient {
        GrpcClient::builder()
            .url(CELESTIA_GRPCWEB_PROXY_URL)
            .build()
            .unwrap()
    }

    pub async fn new_rpc_client() -> RpcClient {
        RpcClient::new(CELESTIA_RPC_URL, None).await.unwrap()
    }

    pub async fn new_tx_client() -> ((), GrpcClient) {
        let creds = load_account();
        let client = GrpcClient::builder()
            .url(CELESTIA_GRPCWEB_PROXY_URL)
            .signer_keypair(creds.signing_key)
            .build();

        ((), client.unwrap())
    }

    pub fn spawn<F>(future: F) -> oneshot::Receiver<()>
    where
        F: Future<Output = ()> + 'static,
    {
        let (tx, rx) = oneshot::channel();
        spawn_local(async move {
            future.await;
            let _ = tx.send(());
        });

        rx
    }
}
