use std::sync::Arc;

use async_trait::async_trait;
use candid::Principal;

use ex3_canister_client::canister_call_wrapper::{call, call_with_payment};

use crate::types::bitcoin::{
    GetBalanceRequest, GetCurrentFeePercentilesRequest, GetUtxosRequest, GetUtxosResponse,
    MillisatoshiPerByte, Satoshi, SendTransactionRequest, GET_BALANCE_COST_CYCLES,
    GET_CURRENT_FEE_PERCENTILES_CYCLES, GET_UTXOS_COST_CYCLES, SEND_TRANSACTION_BASE_CYCLES,
    SEND_TRANSACTION_PER_BYTE_CYCLES,
};
use crate::types::ecdsa::{
    EcdsaPublicKeyArgument, EcdsaPublicKeyResponse, SignWithEcdsaArgument, SignWithEcdsaResponse,
    ECDSA_SIGNATURE_FEE,
};
use crate::types::{
    CanisterIdRecord, CanisterStatusResponse, CreateCanisterArgument, InstallCodeArgument,
    UpdateSettingsArgument, CREATE_CANISTER_CYCLES,
};
use crate::{CanisterResult, ICManagement};

type CanisterId = Principal;

#[derive(Debug)]
pub struct ICManagementClient {
    pub canister_id: Arc<CanisterId>,
}

impl ICManagementClient {
    pub fn new(canister_id: CanisterId) -> Self {
        Self {
            canister_id: Arc::new(canister_id),
        }
    }
}

#[async_trait]
impl ICManagement for ICManagementClient {
    #[cfg(feature = "bitcoin")]
    async fn bitcoin_get_balance(&self, arg: GetBalanceRequest) -> CanisterResult<Satoshi> {
        call_with_payment(
            &self.canister_id,
            "bitcoin_get_balance",
            (arg,),
            GET_BALANCE_COST_CYCLES,
        )
        .await
    }

    #[cfg(feature = "bitcoin")]
    async fn bitcoin_get_utxos(&self, arg: GetUtxosRequest) -> CanisterResult<GetUtxosResponse> {
        call_with_payment(
            &self.canister_id,
            "bitcoin_get_utxos",
            (arg,),
            GET_UTXOS_COST_CYCLES,
        )
        .await
    }

    #[cfg(feature = "bitcoin")]
    async fn bitcoin_send_transaction(&self, arg: SendTransactionRequest) -> CanisterResult<()> {
        let cycles = SEND_TRANSACTION_BASE_CYCLES
            + (arg.transaction.len() as u128) * SEND_TRANSACTION_PER_BYTE_CYCLES;
        call_with_payment(
            &self.canister_id,
            "bitcoin_send_transaction",
            (arg,),
            cycles,
        )
        .await
    }

    #[cfg(feature = "bitcoin")]
    async fn bitcoin_get_current_fee_percentiles(
        &self,
        arg: GetCurrentFeePercentilesRequest,
    ) -> CanisterResult<Vec<MillisatoshiPerByte>> {
        call_with_payment(
            &self.canister_id,
            "bitcoin_get_current_fee_percentiles",
            (arg,),
            GET_CURRENT_FEE_PERCENTILES_CYCLES,
        )
        .await
    }

    #[cfg(feature = "ecdsa")]
    async fn ecdsa_public_key(
        &self,
        arg: EcdsaPublicKeyArgument,
    ) -> CanisterResult<EcdsaPublicKeyResponse> {
        call(&self.canister_id, "ecdsa_public_key", (arg,)).await
    }

    #[cfg(feature = "ecdsa")]
    async fn sign_with_ecdsa(
        &self,
        arg: SignWithEcdsaArgument,
    ) -> CanisterResult<SignWithEcdsaResponse> {
        call_with_payment(
            &self.canister_id,
            "sign_with_ecdsa",
            (arg,),
            ECDSA_SIGNATURE_FEE,
        )
        .await
    }

    #[cfg(feature = "main")]
    async fn create_canister(
        &self,
        arg: CreateCanisterArgument,
    ) -> CanisterResult<CanisterIdRecord> {
        call_with_payment(
            &self.canister_id,
            "create_canister",
            (arg,),
            CREATE_CANISTER_CYCLES,
        )
        .await
    }

    #[cfg(feature = "main")]
    async fn create_canister_with_extra_cycles(
        &self,
        arg: CreateCanisterArgument,
        cycles: u128,
    ) -> CanisterResult<CanisterIdRecord> {
        call_with_payment(
            &self.canister_id,
            "create_canister",
            (arg,),
            CREATE_CANISTER_CYCLES.checked_add(cycles).unwrap(),
        )
        .await
    }

    #[cfg(feature = "main")]
    async fn update_settings(&self, arg: UpdateSettingsArgument) -> CanisterResult<()> {
        call(&self.canister_id, "update_settings", (arg,)).await
    }

    #[cfg(feature = "main")]
    async fn install_code(&self, arg: InstallCodeArgument) -> CanisterResult<()> {
        call(&self.canister_id, "install_code", (arg,)).await
    }

    #[cfg(feature = "main")]
    async fn uninstall_code(&self, arg: CanisterIdRecord) -> CanisterResult<()> {
        call(&self.canister_id, "uninstall_code", (arg,)).await
    }

    #[cfg(feature = "main")]
    async fn start_canister(&self, arg: CanisterIdRecord) -> CanisterResult<()> {
        call(&self.canister_id, "start_canister", (arg,)).await
    }

    #[cfg(feature = "main")]
    async fn stop_canister(&self, arg: CanisterIdRecord) -> CanisterResult<()> {
        call(&self.canister_id, "stop_canister", (arg,)).await
    }

    #[cfg(feature = "main")]
    async fn canister_status(
        &self,
        arg: CanisterIdRecord,
    ) -> CanisterResult<CanisterStatusResponse> {
        call(&self.canister_id, "canister_status", (arg,)).await
    }

    #[cfg(feature = "main")]
    async fn delete_canister(&self, arg: CanisterIdRecord) -> CanisterResult<()> {
        call(&self.canister_id, "delete_canister", (arg,)).await
    }

    #[cfg(feature = "main")]
    async fn deposit_cycles(&self, arg: CanisterIdRecord, cycles: u128) -> CanisterResult<()> {
        call_with_payment(&self.canister_id, "deposit_cycles", (arg,), cycles).await
    }

    #[cfg(feature = "main")]
    async fn raw_rand(&self) -> CanisterResult<Vec<u8>> {
        call(&self.canister_id, "raw_rand", ()).await
    }
}
