use async_trait::async_trait;

pub use canister_impl::ICManagementClient;
use ex3_canister_client::error::CanisterError;

use crate::types::bitcoin::{
    GetBalanceRequest, GetCurrentFeePercentilesRequest, GetUtxosRequest, GetUtxosResponse,
    MillisatoshiPerByte, Satoshi, SendTransactionRequest,
};
use crate::types::ecdsa::{
    EcdsaPublicKeyArgument, EcdsaPublicKeyResponse, SignWithEcdsaArgument, SignWithEcdsaResponse,
};
use crate::types::{
    CanisterIdRecord, CanisterStatusResponse, CreateCanisterArgument, InstallCodeArgument,
    UpdateSettingsArgument,
};

mod canister_impl;
pub mod types;

pub type CanisterResult<T> = Result<T, CanisterError>;

#[async_trait]
pub trait ICManagement: Send + Sync {
    ///! Bitcoin management canister interface

    /// See [IC method `bitcoin_get_balance`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_balance)
    #[cfg(feature = "bitcoin")]
    async fn bitcoin_get_balance(&self, arg: GetBalanceRequest) -> CanisterResult<Satoshi>;

    /// See [IC method `bitcoin_get_utxos`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_utxos)
    #[cfg(feature = "bitcoin")]
    async fn bitcoin_get_utxos(&self, arg: GetUtxosRequest) -> CanisterResult<GetUtxosResponse>;

    /// See [IC method `bitcoin_send_transaction`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_send_transaction)
    #[cfg(feature = "bitcoin")]
    async fn bitcoin_send_transaction(&self, arg: SendTransactionRequest) -> CanisterResult<()>;

    /// See [IC method `bitcoin_get_current_fee_percentiles`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-bitcoin_get_current_fee_percentiles)
    #[cfg(feature = "bitcoin")]
    async fn bitcoin_get_current_fee_percentiles(
        &self,
        arg: GetCurrentFeePercentilesRequest,
    ) -> CanisterResult<Vec<MillisatoshiPerByte>>;

    ///! ECDSA management canister interface

    /// Return a SEC1 encoded ECDSA public key for the given canister using the given derivation path.
    ///
    /// See [IC method `ecdsa_public_key`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-ecdsa_public_key).
    #[cfg(feature = "ecdsa")]
    async fn ecdsa_public_key(
        &self,
        arg: EcdsaPublicKeyArgument,
    ) -> CanisterResult<EcdsaPublicKeyResponse>;

    /// Return a new ECDSA signature of the given message_hash that can be separately verified against a derived ECDSA public key.
    ///
    /// See [IC method `sign_with_ecdsa`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-sign_with_ecdsa).
    #[cfg(feature = "ecdsa")]
    async fn sign_with_ecdsa(
        &self,
        arg: SignWithEcdsaArgument,
    ) -> CanisterResult<SignWithEcdsaResponse>;

    ///! Management canister main interface
    ///
    /// Register a new canister and get its canister id.
    ///
    /// Note: This call charges [CREATE_CANISTER_CYCLES] from the caller canister.
    ///
    /// See [IC method `create_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-create_canister).
    #[cfg(feature = "main")]
    async fn create_canister(
        &self,
        arg: CreateCanisterArgument,
    ) -> CanisterResult<CanisterIdRecord>;

    /// [create_canister] and specify extra cycles to the new canister.
    ///
    /// Note: This call charges [CREATE_CANISTER_CYCLES] and the specified extra cycles from the caller canister.
    ///
    /// See [IC method `create_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-create_canister).
    #[cfg(feature = "main")]
    async fn create_canister_with_extra_cycles(
        &self,
        arg: CreateCanisterArgument,
        cycles: u128,
    ) -> CanisterResult<CanisterIdRecord>;

    /// Update the settings of a canister.
    ///
    /// See [IC method `update_settings`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-update_settings).
    #[cfg(feature = "main")]
    async fn update_settings(&self, arg: UpdateSettingsArgument) -> CanisterResult<()>;

    /// Install code into a canister.
    ///
    /// See [IC method `install_code`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-install_code).
    #[cfg(feature = "main")]
    async fn install_code(&self, arg: InstallCodeArgument) -> CanisterResult<()>;

    /// Remove a canister's code and state, making the canister empty again.
    ///
    /// See [IC method `uninstall_code`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-uninstall_code)
    #[cfg(feature = "main")]
    async fn uninstall_code(&self, arg: CanisterIdRecord) -> CanisterResult<()>;

    /// Start a canister if the canister status was `stopped` or `stopping`.
    ///
    /// See [IC method `start_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-start_canister)
    #[cfg(feature = "main")]
    async fn start_canister(&self, arg: CanisterIdRecord) -> CanisterResult<()>;

    /// Stop a canister.
    ///
    /// See [IC method `stop_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-stop_canister)
    #[cfg(feature = "main")]
    async fn stop_canister(&self, arg: CanisterIdRecord) -> CanisterResult<()>;

    /// Get status information about the canister.
    ///
    /// See [IC method `canister_status`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-canister_status)
    #[cfg(feature = "main")]
    async fn canister_status(
        &self,
        arg: CanisterIdRecord,
    ) -> CanisterResult<CanisterStatusResponse>;

    /// Delete a canister from the IC.
    ///
    /// See [IC method `delete_canister`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-delete_canister)
    #[cfg(feature = "main")]
    async fn delete_canister(&self, arg: CanisterIdRecord) -> CanisterResult<()>;

    /// Deposit cycles into the specified canister.

    /// Note that, beyond the argument as specified in the interface description,
    /// there is a second parameter `cycles` which is the amount of cycles to be deposited.
    ///
    /// See [IC method `deposit_cycles`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-deposit_cycles)
    #[cfg(feature = "main")]
    async fn deposit_cycles(&self, arg: CanisterIdRecord, cycles: u128) -> CanisterResult<()>;

    /// Get 32 pseudo-random bytes.
    ///
    /// See [IC method `raw_rand`](https://internetcomputer.org/docs/current/references/ic-interface-spec/#ic-raw_rand)
    #[cfg(feature = "main")]
    async fn raw_rand(&self) -> CanisterResult<Vec<u8>>;
}
