use crate::{
    call_handler_ext::{
        TransactionConfig,
        maybe_return_coins,
    },
    utxo_manager::SharedUtxoManager,
    wallet_ext::{
        BuilderData,
        WalletExt,
    },
};
use fuel_core_client::client::FuelClient;
use fuel_core_types::{
    fuel_tx::{
        Create,
        Output,
        TxPointer,
        UniqueIdentifier,
        Witness,
    },
    fuel_types::canonical::Serialize,
};
use fuels::{
    accounts::ViewOnlyAccount,
    prelude::{
        Contract,
        Error as FuelsError,
        Result as FuelsResult,
        Wallet,
    },
    programs::contract::{
        DeployResponse,
        Regular,
    },
};
use std::future::Future;

pub(crate) trait ContractExt {
    fn almost_sync_deploy(
        self,
        account: &Wallet,
        builder_data: &BuilderData,
        utxo_manager: &SharedUtxoManager,
        tx_config: &Option<TransactionConfig>,
        submit_clients: &[FuelClient],
    ) -> impl Future<Output = FuelsResult<DeployResponse>>;
}

impl ContractExt for Contract<Regular> {
    /// Deploys a compiled contract to a running node.
    /// To deploy a contract, you need an account with enough assets to pay for deployment.
    /// This account will also receive the change.
    async fn almost_sync_deploy(
        self,
        account: &Wallet,
        builder_data: &BuilderData,
        utxo_manager: &SharedUtxoManager,
        tx_config: &Option<TransactionConfig>,
        submit_clients: &[FuelClient],
    ) -> FuelsResult<DeployResponse> {
        let consensus_parameters = &builder_data.consensus_parameters;
        let max_fee = builder_data.max_fee();
        let base_asset_id = *consensus_parameters.base_asset_id();
        let chain_id = consensus_parameters.chain_id();
        let secret_key = account.signer().secret_key();
        let owner = account.address();

        let contract_id = self.contract_id();
        let state_root = self.state_root();
        let salt = self.salt();
        let storage_slots = self.storage_slots();
        let code = self.code();
        let bytecode = Witness::from(code);

        let witness_limit = bytecode.size() + crate::wallet_ext::SIGNATURE_MARGIN;

        let mut builder = fuel_core_types::fuel_tx::TransactionBuilder::<Create>::create(
            bytecode,
            salt,
            storage_slots.to_vec(),
        );

        builder
            .with_chain_id(consensus_parameters.chain_id())
            .max_fee_limit(max_fee)
            .witness_limit(witness_limit as u64);

        let input_coins = {
            utxo_manager
                .lock()
                .await
                .guaranteed_extract_coins(owner, base_asset_id, max_fee as u128)
                .map_err(|e| FuelsError::Other(e.to_string()))
        }?;

        for coin in input_coins.iter() {
            builder.add_unsigned_coin_input(
                secret_key,
                coin.utxo_id,
                coin.amount,
                coin.asset_id,
                TxPointer::default(),
            );
        }

        builder.add_output(Output::Change {
            to: owner,
            amount: 0,
            asset_id: base_asset_id,
        });

        builder.add_output(Output::ContractCreated {
            contract_id,
            state_root,
        });

        let tx = builder.finalize_as_transaction();
        let tx_id = tx.id(&chain_id);

        let result = match account
            .send_transaction(chain_id, &tx, submit_clients)
            .await
        {
            Ok(result) => result,
            Err(e) => {
                let err_msg = e.to_string();
                if crate::wallet_ext::is_coin_invalid_error(&err_msg) {
                    tracing::warn!(
                        %tx_id,
                        "Deploy rejected due to invalid coins, \
                         not returning coins to UtxoManager: {err_msg}",
                    );
                } else {
                    tracing::warn!(
                        %tx_id,
                        "Deploy failed, returning coins to UtxoManager: {err_msg}",
                    );
                    let mut utxo_manager = utxo_manager.lock().await;
                    utxo_manager.load_from_coins_vec(input_coins);
                }
                return Err(FuelsError::Other(e.to_string()));
            }
        };

        // Transaction accepted — spawn background expiration safety net.
        maybe_return_coins(
            account,
            &tx,
            tx_id,
            tx_config.and_then(|c| c.expiration_height),
            utxo_manager,
        );

        let tx_id = Some(result.tx_id);

        {
            let mut utxo_manager = utxo_manager.lock().await;
            utxo_manager.load_from_coins_vec(result.known_coins);
            utxo_manager.load_from_coins_vec(result.dynamic_coins);
        }

        Ok(DeployResponse {
            tx_status: Some(result.tx_status.take_success_checked(None)?),
            tx_id,
            contract_id,
        })
    }
}
