uniffi_lipalightninglib/onchain/
swap.rsuse crate::amount::{AsSats, Sats, ToAmount};
use crate::errors::Result;
use crate::locker::Locker;
use crate::onchain::{get_onchain_resolving_fees, query_onchain_fee_rate};
use crate::support::Support;
use crate::util::unix_timestamp_to_system_time;
use crate::{
    Amount, CalculateLspFeeResponseV2, FailedSwapInfo, LspFee, OnchainResolvingFees,
    ResolveFailedSwapInfo, RuntimeErrorCode, SwapAddressInfo,
};
use breez_sdk_core::error::ReceiveOnchainError;
use breez_sdk_core::{
    BitcoinAddressData, Network, OpeningFeeParams, PrepareRefundRequest, ReceiveOnchainRequest,
    RefundRequest,
};
use perro::{ensure, permanent_failure, runtime_error, MapToError};
use std::sync::Arc;
pub struct Swap {
    support: Arc<Support>,
}
impl Swap {
    pub(crate) fn new(support: Arc<Support>) -> Self {
        Self { support }
    }
    pub fn create(&self) -> std::result::Result<SwapAddressInfo, ReceiveOnchainError> {
        let lsp_fee_params =
            self.get_lsp_fee_params()
                .map_err(|_e| ReceiveOnchainError::ServiceConnectivity {
                    err: "Could not retrieve lsp fee params".to_string(),
                })?;
        let swap_info = self
            .support
            .rt
            .handle()
            .block_on(self.support.sdk.receive_onchain(ReceiveOnchainRequest {
                opening_fee_params: Some(lsp_fee_params),
            }))?;
        let rate = self.support.get_exchange_rate();
        Ok(SwapAddressInfo {
            address: swap_info.bitcoin_address,
            min_deposit: (swap_info.min_allowed_deposit as u64)
                .as_sats()
                .to_amount_up(&rate),
            max_deposit: (swap_info.max_allowed_deposit as u64)
                .as_sats()
                .to_amount_down(&rate),
            swap_fee: 0_u64.as_sats().to_amount_up(&rate),
        })
    }
    pub fn determine_resolving_fees(
        &self,
        failed_swap_info: FailedSwapInfo,
    ) -> Result<Option<OnchainResolvingFees>> {
        let failed_swap_closure = failed_swap_info.clone();
        let prepare_onchain_tx = move |address: String| -> Result<(Sats, Sats, u32)> {
            let sweep_info = self.prepare_sweep(
                failed_swap_closure,
                BitcoinAddressData {
                    address,
                    network: Network::Bitcoin,
                    amount_sat: None,
                    label: None,
                    message: None,
                },
            )?;
            Ok((
                sweep_info.recovered_amount.sats.as_sats(),
                sweep_info.onchain_fee.sats.as_sats(),
                sweep_info.onchain_fee_rate,
            ))
        };
        get_onchain_resolving_fees(
            &self.support,
            self,
            failed_swap_info.amount.to_msats().as_msats(),
            prepare_onchain_tx,
        )
    }
    pub fn prepare_sweep(
        &self,
        failed_swap_info: FailedSwapInfo,
        destination: BitcoinAddressData,
    ) -> Result<SweepFailedSwapInfo> {
        let to_address = destination.address;
        let onchain_fee_rate = query_onchain_fee_rate(&self.support)?;
        let response = self
            .support
            .rt
            .handle()
            .block_on(self.support.sdk.prepare_refund(PrepareRefundRequest {
                swap_address: failed_swap_info.address.clone(),
                to_address: to_address.clone(),
                sat_per_vbyte: onchain_fee_rate,
            }))
            .map_to_runtime_error(
                RuntimeErrorCode::NodeUnavailable,
                "Failed to prepare a failed swap refund transaction",
            )?;
        let rate = self.support.get_exchange_rate();
        let onchain_fee = response.refund_tx_fee_sat.as_sats().to_amount_up(&rate);
        ensure!(
            failed_swap_info.amount.sats > onchain_fee.sats,
            permanent_failure("Swap amount is below fees")
        );
        let recovered_amount = (failed_swap_info.amount.sats - onchain_fee.sats)
            .as_sats()
            .to_amount_down(&rate);
        Ok(SweepFailedSwapInfo {
            swap_address: failed_swap_info.address,
            recovered_amount,
            onchain_fee,
            to_address,
            onchain_fee_rate,
        })
    }
    pub fn sweep(&self, sweep_failed_swap_info: SweepFailedSwapInfo) -> Result<String> {
        Ok(self
            .support
            .rt
            .handle()
            .block_on(self.support.sdk.refund(RefundRequest {
                swap_address: sweep_failed_swap_info.swap_address,
                to_address: sweep_failed_swap_info.to_address,
                sat_per_vbyte: sweep_failed_swap_info.onchain_fee_rate,
            }))
            .map_to_runtime_error(
                RuntimeErrorCode::NodeUnavailable,
                "Failed to create and broadcast failed swap refund transaction",
            )?
            .refund_tx_id)
    }
    pub fn swap(&self, failed_swap_info: FailedSwapInfo, sats_per_vbyte: u32) -> Result<String> {
        let swap_address_info = self.create().map_to_runtime_error(
            RuntimeErrorCode::NodeUnavailable,
            "Couldn't generate swap address",
        )?;
        let prepare_response = self
            .support
            .rt
            .handle()
            .block_on(self.support.sdk.prepare_refund(PrepareRefundRequest {
                swap_address: failed_swap_info.address.clone(),
                to_address: swap_address_info.address.clone(),
                sat_per_vbyte: sats_per_vbyte,
            }))
            .map_to_runtime_error(RuntimeErrorCode::NodeUnavailable, "Coudln't prepare refund")?;
        let send_amount_sats = failed_swap_info.amount.sats - prepare_response.refund_tx_fee_sat;
        ensure!(
            swap_address_info.min_deposit.sats <= send_amount_sats,
            runtime_error(
                RuntimeErrorCode::NodeUnavailable,
                "Failed swap amount isn't enough for creating new swap"
            )
        );
        ensure!(
            swap_address_info.max_deposit.sats >= send_amount_sats,
            runtime_error(
                RuntimeErrorCode::NodeUnavailable,
                "Failed swap amount is too big for creating new swap"
            )
        );
        let lsp_fees = self
            .support
            .calculate_lsp_fee_for_amount(send_amount_sats, self.get_lsp_fee_params()?)?
            .lsp_fee
            .sats;
        ensure!(
            lsp_fees < send_amount_sats,
            runtime_error(
                RuntimeErrorCode::NodeUnavailable,
                "A new channel is needed and the failed swap amount is not enough to pay for fees"
            )
        );
        let refund_response = self
            .support
            .rt
            .handle()
            .block_on(self.support.sdk.refund(RefundRequest {
                swap_address: failed_swap_info.address,
                to_address: swap_address_info.address,
                sat_per_vbyte: sats_per_vbyte,
            }))
            .map_to_runtime_error(
                RuntimeErrorCode::NodeUnavailable,
                "Couldn't broadcast swap refund transaction",
            )?;
        Ok(refund_response.refund_tx_id)
    }
    pub(crate) fn list_failed_unresolved(&self) -> Result<Vec<FailedSwapInfo>> {
        Ok(self
            .support
            .rt
            .handle()
            .block_on(self.support.sdk.list_refundables())
            .map_to_runtime_error(
                RuntimeErrorCode::NodeUnavailable,
                "Failed to list refundable failed swaps",
            )?
            .into_iter()
            .filter(|s| s.refund_tx_ids.is_empty())
            .map(|s| FailedSwapInfo {
                address: s.bitcoin_address,
                amount: s
                    .confirmed_sats
                    .as_sats()
                    .to_amount_down(&self.support.get_exchange_rate()),
                created_at: unix_timestamp_to_system_time(s.created_at as u64),
            })
            .collect())
    }
    pub fn calculate_lsp_fee_for_amount(
        &self,
        amount_sat: u64,
    ) -> Result<CalculateLspFeeResponseV2> {
        self.support
            .calculate_lsp_fee_for_amount(amount_sat, self.get_lsp_fee_params()?)
    }
    pub fn get_lsp_fee(&self) -> Result<LspFee> {
        let exchange_rate = self.support.get_exchange_rate();
        let lsp_fee = self.get_lsp_fee_params()?;
        Ok(LspFee {
            channel_minimum_fee: lsp_fee.min_msat.as_msats().to_amount_up(&exchange_rate),
            channel_fee_permyriad: lsp_fee.proportional as u64 / 100,
        })
    }
    pub(crate) fn get_lsp_fee_params(&self) -> Result<OpeningFeeParams> {
        self.support
            .task_manager
            .lock_unwrap()
            .get_longer_valid_lsp_fee()
    }
}
pub struct SweepFailedSwapInfo {
    pub swap_address: String,
    pub recovered_amount: Amount,
    pub onchain_fee: Amount,
    pub to_address: String,
    pub onchain_fee_rate: u32,
}
impl From<ResolveFailedSwapInfo> for SweepFailedSwapInfo {
    fn from(value: ResolveFailedSwapInfo) -> Self {
        Self {
            swap_address: value.swap_address,
            recovered_amount: value.recovered_amount,
            onchain_fee: value.onchain_fee,
            to_address: value.to_address,
            onchain_fee_rate: value.onchain_fee_rate,
        }
    }
}
impl From<SweepFailedSwapInfo> for ResolveFailedSwapInfo {
    fn from(value: SweepFailedSwapInfo) -> Self {
        Self {
            swap_address: value.swap_address,
            recovered_amount: value.recovered_amount,
            onchain_fee: value.onchain_fee,
            to_address: value.to_address,
            onchain_fee_rate: value.onchain_fee_rate,
        }
    }
}