use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;

use chrono::Utc;
use futures::future::BoxFuture;
use futures::stream::{self, StreamExt};
use serde_json::json;
use tauri::{AppHandle, Runtime};
use tauri_plugin_store::StoreExt;
use thiserror::Error;
use tokio::sync::Mutex;

use crate::portfolio::engine::{replay_ledger, PortfolioEngineError};
use crate::portfolio::{
    CashConfirmation, PortfolioHolding, PortfolioLedger, PortfolioSnapshot, PortfolioStats,
    PortfolioTransaction, ReplaySnapshot, TradeConfirmation, TransactionKind, PORTFOLIO_LEDGER_KEY,
    PORTFOLIO_LEDGER_STORE_PATH,
};
use crate::terminal::security_lookup::{
    SecurityKind, SecurityLookup, SecurityLookupError, SecurityMatch,
};
use crate::terminal::{Holding, Portfolio};

const QUOTE_CONCURRENCY_LIMIT: usize = 4;
const TRANSACTION_ID_PREFIX: &str = "portfolio-tx-";

#[derive(Debug, Error, Clone, PartialEq)]
pub enum PortfolioStoreError {
    #[error("portfolio store unavailable: {0}")]
    StoreUnavailable(String),
    #[error("portfolio ledger is not valid JSON: {0}")]
    Deserialize(String),
    #[error("portfolio ledger could not be saved: {0}")]
    SaveFailed(String),
    #[error(transparent)]
    Engine(#[from] PortfolioEngineError),
}

#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum PortfolioValidationError {
    #[error("cash balance is insufficient for this trade")]
    TradeCash,
    #[error("cash balance is insufficient for this withdrawal")]
    WithdrawalCash,
    #[error("not enough shares are available to sell for {symbol}")]
    ShareShortfall { symbol: String },
}

#[derive(Debug, Error, Clone, PartialEq, Eq)]
pub enum PortfolioQuoteError {
    #[error("quote unavailable for {symbol}: {detail}")]
    Unavailable { symbol: String, detail: String },
}

#[derive(Debug, Error, Clone, PartialEq)]
pub enum PortfolioCommandError {
    #[error(transparent)]
    Store(#[from] PortfolioStoreError),
    #[error(transparent)]
    Validation(#[from] PortfolioValidationError),
    #[error(transparent)]
    Quote(#[from] PortfolioQuoteError),
}

pub trait PortfolioManagement: Send + Sync {
    fn portfolio<'a>(&'a self) -> BoxFuture<'a, Result<Portfolio, PortfolioCommandError>>;
    fn stats<'a>(&'a self) -> BoxFuture<'a, Result<PortfolioStats, PortfolioCommandError>>;
    fn history<'a>(
        &'a self,
        limit: usize,
    ) -> BoxFuture<'a, Result<Vec<PortfolioTransaction>, PortfolioCommandError>>;
    fn buy<'a>(
        &'a self,
        symbol: &'a str,
        quantity: f64,
        price_override: Option<f64>,
    ) -> BoxFuture<'a, Result<TradeConfirmation, PortfolioCommandError>>;
    fn sell<'a>(
        &'a self,
        symbol: &'a str,
        quantity: f64,
        price_override: Option<f64>,
    ) -> BoxFuture<'a, Result<TradeConfirmation, PortfolioCommandError>>;
    fn deposit_cash<'a>(
        &'a self,
        amount: f64,
    ) -> BoxFuture<'a, Result<CashConfirmation, PortfolioCommandError>>;
    fn withdraw_cash<'a>(
        &'a self,
        amount: f64,
    ) -> BoxFuture<'a, Result<CashConfirmation, PortfolioCommandError>>;
}

pub struct PortfolioService<R: Runtime> {
    app_handle: AppHandle<R>,
    security_lookup: Arc<dyn SecurityLookup>,
    write_lock: Mutex<()>,
    next_transaction_id: AtomicU64,
}

impl<R: Runtime> PortfolioService<R> {
    pub fn new(app_handle: &AppHandle<R>, security_lookup: Arc<dyn SecurityLookup>) -> Self {
        let next_transaction_id = load_ledger_from_handle(app_handle)
            .map(|ledger| next_transaction_sequence(&ledger))
            .unwrap_or(1);

        Self {
            app_handle: app_handle.clone(),
            security_lookup,
            write_lock: Mutex::new(()),
            next_transaction_id: AtomicU64::new(next_transaction_id),
        }
    }

    fn load_ledger(&self) -> Result<PortfolioLedger, PortfolioStoreError> {
        let store = self
            .app_handle
            .store(PORTFOLIO_LEDGER_STORE_PATH)
            .map_err(|error| PortfolioStoreError::StoreUnavailable(error.to_string()))?;

        match store.get(PORTFOLIO_LEDGER_KEY) {
            Some(value) => serde_json::from_value(value.clone())
                .map_err(|error| PortfolioStoreError::Deserialize(error.to_string())),
            None => Ok(PortfolioLedger::default()),
        }
    }

    fn save_ledger(&self, ledger: &PortfolioLedger) -> Result<(), PortfolioStoreError> {
        let store = self
            .app_handle
            .store(PORTFOLIO_LEDGER_STORE_PATH)
            .map_err(|error| PortfolioStoreError::StoreUnavailable(error.to_string()))?;

        store.set(PORTFOLIO_LEDGER_KEY.to_string(), json!(ledger));
        store
            .save()
            .map_err(|error| PortfolioStoreError::SaveFailed(error.to_string()))
    }

    fn replay(&self, ledger: &PortfolioLedger) -> Result<ReplaySnapshot, PortfolioStoreError> {
        replay_ledger(ledger).map_err(map_engine_error)
    }

    fn next_transaction_id(&self, ledger: &PortfolioLedger) -> String {
        let ledger_next = next_transaction_sequence(ledger);
        let next_id = reserve_transaction_sequence(&self.next_transaction_id, ledger_next);
        format!("{TRANSACTION_ID_PREFIX}{next_id}")
    }

    async fn resolve_trade_input(
        &self,
        symbol: &str,
        price_override: Option<f64>,
    ) -> Result<(String, String, f64), PortfolioCommandError> {
        let normalized_symbol = symbol.trim().to_ascii_uppercase();
        let security_match = SecurityMatch {
            symbol: normalized_symbol.clone(),
            name: None,
            exchange: None,
            kind: SecurityKind::Equity,
        };

        let company = self
            .security_lookup
            .load_company(&security_match)
            .await
            .map_err(|error| map_quote_error(&normalized_symbol, error))?;

        Ok((
            normalized_symbol,
            company.name,
            price_override.unwrap_or(company.price),
        ))
    }

    async fn candidate_trade_result(
        &self,
        symbol: &str,
        company_name: &str,
        quantity: f64,
        price: f64,
        kind: TransactionKind,
    ) -> Result<TradeConfirmation, PortfolioCommandError> {
        let _guard = self.write_lock.lock().await;
        let mut ledger = self.load_ledger()?;
        let before_snapshot = self.replay(&ledger)?;

        let transaction = PortfolioTransaction {
            id: self.next_transaction_id(&ledger),
            kind: kind.clone(),
            symbol: Some(symbol.to_string()),
            company_name: Some(company_name.to_string()),
            quantity: Some(quantity),
            price: Some(price),
            gross_amount: quantity * price,
            fee: 0.0,
            executed_at: Utc::now().to_rfc3339(),
            note: None,
        };
        ledger.transactions.push(transaction);

        let after_snapshot = self
            .replay(&ledger)
            .map_err(|error| map_trade_validation_error(error, symbol))?;

        self.save_ledger(&ledger)?;

        Ok(TradeConfirmation {
            symbol: symbol.to_string(),
            company_name: company_name.to_string(),
            quantity,
            price,
            gross_amount: quantity * price,
            cash_balance: after_snapshot.cash_balance,
            realized_gain: matches!(kind, TransactionKind::Sell)
                .then_some(after_snapshot.realized_gain - before_snapshot.realized_gain),
        })
    }

    async fn candidate_cash_result(
        &self,
        amount: f64,
        kind: TransactionKind,
    ) -> Result<CashConfirmation, PortfolioCommandError> {
        let _guard = self.write_lock.lock().await;
        let mut ledger = self.load_ledger()?;

        ledger.transactions.push(PortfolioTransaction {
            id: self.next_transaction_id(&ledger),
            kind: kind.clone(),
            symbol: None,
            company_name: None,
            quantity: None,
            price: None,
            gross_amount: amount,
            fee: 0.0,
            executed_at: Utc::now().to_rfc3339(),
            note: None,
        });

        let snapshot = self.replay(&ledger).map_err(map_cash_validation_error)?;

        self.save_ledger(&ledger)?;

        Ok(CashConfirmation {
            amount,
            cash_balance: snapshot.cash_balance,
            kind,
        })
    }

    async fn value_portfolio(&self) -> Result<PortfolioSnapshot, PortfolioCommandError> {
        let ledger = self.load_ledger()?;
        let replay_snapshot = self.replay(&ledger)?;

        let priced_holdings = stream::iter(replay_snapshot.positions.iter().cloned())
            .map(|position| async move {
                let security_match = SecurityMatch {
                    symbol: position.symbol.clone(),
                    name: Some(position.company_name.clone()),
                    exchange: None,
                    kind: SecurityKind::Equity,
                };

                let quote = self.security_lookup.load_company(&security_match).await;
                (position, quote)
            })
            .buffer_unordered(QUOTE_CONCURRENCY_LIMIT)
            .collect::<Vec<_>>()
            .await;

        let mut holdings = Vec::with_capacity(priced_holdings.len());
        let mut stale_pricing_symbols = Vec::new();
        let mut equities_market_value = 0.0;
        let mut unrealized_gain = 0.0;
        let mut day_change = 0.0;

        for (position, quote_result) in priced_holdings {
            let (current_price, day_change_value, stale_pricing) = match quote_result {
                Ok(company) => (company.price, company.change * position.quantity, false),
                Err(_) => {
                    let fallback_price = position.latest_trade_price.unwrap_or(0.0);
                    stale_pricing_symbols.push(position.symbol.clone());
                    (fallback_price, 0.0, true)
                }
            };

            let current_value = current_price * position.quantity;
            let holding_unrealized_gain = current_value - position.cost_basis;
            equities_market_value += current_value;
            unrealized_gain += holding_unrealized_gain;
            day_change += day_change_value;

            holdings.push(PortfolioHolding {
                symbol: position.symbol,
                name: position.company_name,
                quantity: position.quantity,
                cost_basis: position.cost_basis,
                current_price,
                current_value,
                unrealized_gain: holding_unrealized_gain,
                gain_loss_percent: percent_change(holding_unrealized_gain, position.cost_basis),
                latest_trade_at: position.latest_trade_at,
                stale_pricing,
                day_change: day_change_value,
            });
        }

        holdings.sort_by(|left, right| left.symbol.cmp(&right.symbol));

        let invested_cost_basis = holdings
            .iter()
            .map(|holding| holding.cost_basis)
            .sum::<f64>();
        let total_portfolio_value = equities_market_value + replay_snapshot.cash_balance;
        let baseline_value = total_portfolio_value - day_change;

        Ok(PortfolioSnapshot {
            cash_balance: replay_snapshot.cash_balance,
            day_change,
            day_change_percent: if baseline_value > 0.0 {
                (day_change / baseline_value) * 100.0
            } else {
                0.0
            },
            equities_market_value,
            holdings,
            invested_cost_basis,
            realized_gain: replay_snapshot.realized_gain,
            stale_pricing_symbols,
            total_portfolio_value,
            unrealized_gain,
        })
    }
}

impl<R: Runtime> PortfolioManagement for PortfolioService<R> {
    fn portfolio<'a>(&'a self) -> BoxFuture<'a, Result<Portfolio, PortfolioCommandError>> {
        Box::pin(async move {
            let snapshot = self.value_portfolio().await?;
            let holdings_count = snapshot.holdings.len();

            Ok(Portfolio {
                cash_balance: Some(snapshot.cash_balance),
                day_change: snapshot.day_change,
                day_change_percent: snapshot.day_change_percent,
                holdings: snapshot
                    .holdings
                    .into_iter()
                    .map(|holding| Holding {
                        avg_cost: if holding.quantity > 0.0 {
                            holding.cost_basis / holding.quantity
                        } else {
                            0.0
                        },
                        cost_basis: Some(holding.cost_basis),
                        current_price: holding.current_price,
                        current_value: holding.current_value,
                        gain_loss: holding.unrealized_gain,
                        gain_loss_percent: holding.gain_loss_percent,
                        latest_trade_at: holding.latest_trade_at,
                        name: holding.name,
                        quantity: holding.quantity,
                        symbol: holding.symbol,
                        unrealized_gain: Some(holding.unrealized_gain),
                    })
                    .collect(),
                holdings_count: Some(holdings_count),
                invested_cost_basis: Some(snapshot.invested_cost_basis),
                realized_gain: Some(snapshot.realized_gain),
                stale_pricing_symbols: Some(snapshot.stale_pricing_symbols),
                total_gain: snapshot.unrealized_gain,
                total_gain_percent: percent_change(
                    snapshot.unrealized_gain,
                    snapshot.invested_cost_basis,
                ),
                total_value: snapshot.total_portfolio_value,
                unrealized_gain: Some(snapshot.unrealized_gain),
            })
        })
    }

    fn stats<'a>(&'a self) -> BoxFuture<'a, Result<PortfolioStats, PortfolioCommandError>> {
        Box::pin(async move {
            let snapshot = self.value_portfolio().await?;

            Ok(PortfolioStats {
                cash_balance: snapshot.cash_balance,
                day_change: snapshot.day_change,
                equities_market_value: snapshot.equities_market_value,
                holdings_count: snapshot.holdings.len(),
                invested_cost_basis: snapshot.invested_cost_basis,
                realized_gain: snapshot.realized_gain,
                stale_pricing_symbols: snapshot.stale_pricing_symbols,
                total_portfolio_value: snapshot.total_portfolio_value,
                unrealized_gain: snapshot.unrealized_gain,
            })
        })
    }

    fn history<'a>(
        &'a self,
        limit: usize,
    ) -> BoxFuture<'a, Result<Vec<PortfolioTransaction>, PortfolioCommandError>> {
        Box::pin(async move {
            let ledger = self.load_ledger()?;
            let mut transactions = ledger.transactions;
            transactions.sort_by(|left, right| {
                right
                    .executed_at
                    .cmp(&left.executed_at)
                    .then_with(|| right.id.cmp(&left.id))
            });
            transactions.truncate(limit);
            Ok(transactions)
        })
    }

    fn buy<'a>(
        &'a self,
        symbol: &'a str,
        quantity: f64,
        price_override: Option<f64>,
    ) -> BoxFuture<'a, Result<TradeConfirmation, PortfolioCommandError>> {
        Box::pin(async move {
            let (symbol, company_name, price) =
                self.resolve_trade_input(symbol, price_override).await?;
            self.candidate_trade_result(
                &symbol,
                &company_name,
                quantity,
                price,
                TransactionKind::Buy,
            )
            .await
        })
    }

    fn sell<'a>(
        &'a self,
        symbol: &'a str,
        quantity: f64,
        price_override: Option<f64>,
    ) -> BoxFuture<'a, Result<TradeConfirmation, PortfolioCommandError>> {
        Box::pin(async move {
            let (symbol, company_name, price) =
                self.resolve_trade_input(symbol, price_override).await?;
            self.candidate_trade_result(
                &symbol,
                &company_name,
                quantity,
                price,
                TransactionKind::Sell,
            )
            .await
        })
    }

    fn deposit_cash<'a>(
        &'a self,
        amount: f64,
    ) -> BoxFuture<'a, Result<CashConfirmation, PortfolioCommandError>> {
        Box::pin(async move {
            self.candidate_cash_result(amount, TransactionKind::CashDeposit)
                .await
        })
    }

    fn withdraw_cash<'a>(
        &'a self,
        amount: f64,
    ) -> BoxFuture<'a, Result<CashConfirmation, PortfolioCommandError>> {
        Box::pin(async move {
            self.candidate_cash_result(amount, TransactionKind::CashWithdrawal)
                .await
        })
    }
}

fn load_ledger_from_handle<R: Runtime>(
    app_handle: &AppHandle<R>,
) -> Result<PortfolioLedger, PortfolioStoreError> {
    let store = app_handle
        .store(PORTFOLIO_LEDGER_STORE_PATH)
        .map_err(|error| PortfolioStoreError::StoreUnavailable(error.to_string()))?;

    match store.get(PORTFOLIO_LEDGER_KEY) {
        Some(value) => serde_json::from_value(value.clone())
            .map_err(|error| PortfolioStoreError::Deserialize(error.to_string())),
        None => Ok(PortfolioLedger::default()),
    }
}

fn parse_transaction_sequence(id: &str) -> Option<u64> {
    id.strip_prefix(TRANSACTION_ID_PREFIX)
        .and_then(|value| value.parse::<u64>().ok())
}

fn next_transaction_sequence(ledger: &PortfolioLedger) -> u64 {
    ledger
        .transactions
        .iter()
        .filter_map(|transaction| parse_transaction_sequence(&transaction.id))
        .max()
        .unwrap_or(0)
        + 1
}

fn reserve_transaction_sequence(counter: &AtomicU64, ledger_next: u64) -> u64 {
    loop {
        let current = counter.load(Ordering::Relaxed);
        let next_id = current.max(ledger_next);

        if counter
            .compare_exchange(current, next_id + 1, Ordering::Relaxed, Ordering::Relaxed)
            .is_ok()
        {
            return next_id;
        }
    }
}

fn map_engine_error(error: PortfolioEngineError) -> PortfolioStoreError {
    PortfolioStoreError::Engine(error)
}

fn map_trade_validation_error(error: PortfolioStoreError, symbol: &str) -> PortfolioCommandError {
    match error {
        PortfolioStoreError::Engine(PortfolioEngineError::NegativeCash { .. }) => {
            PortfolioCommandError::Validation(PortfolioValidationError::TradeCash)
        }
        PortfolioStoreError::Engine(PortfolioEngineError::InsufficientShares { .. }) => {
            PortfolioCommandError::Validation(PortfolioValidationError::ShareShortfall {
                symbol: symbol.to_string(),
            })
        }
        other => PortfolioCommandError::Store(other),
    }
}

fn map_cash_validation_error(error: PortfolioStoreError) -> PortfolioCommandError {
    match error {
        PortfolioStoreError::Engine(PortfolioEngineError::NegativeCash { .. }) => {
            PortfolioCommandError::Validation(PortfolioValidationError::WithdrawalCash)
        }
        other => PortfolioCommandError::Store(other),
    }
}

fn map_quote_error(symbol: &str, error: SecurityLookupError) -> PortfolioCommandError {
    let detail = match error {
        SecurityLookupError::DetailUnavailable { detail, .. }
        | SecurityLookupError::SearchUnavailable { detail, .. } => detail,
    };

    PortfolioCommandError::Quote(PortfolioQuoteError::Unavailable {
        symbol: symbol.to_string(),
        detail,
    })
}

fn percent_change(delta: f64, base: f64) -> f64 {
    if base > 0.0 {
        (delta / base) * 100.0
    } else {
        0.0
    }
}

#[cfg(test)]
mod tests {
    use std::env;
    use std::fs;
    use std::sync::Arc;
    use std::time::{SystemTime, UNIX_EPOCH};

    use futures::future::BoxFuture;
    use tauri::test::{mock_builder, mock_context, noop_assets, MockRuntime};
    use tauri_plugin_store::StoreExt;

    use super::{PortfolioCommandError, PortfolioManagement, PortfolioService};
    use crate::portfolio::{
        PortfolioLedger, PortfolioTransaction, PortfolioValidationError, TransactionKind,
        PORTFOLIO_LEDGER_KEY, PORTFOLIO_LEDGER_STORE_PATH,
    };
    use crate::terminal::security_lookup::{SecurityLookup, SecurityLookupError, SecurityMatch};
    use crate::terminal::Company;

    #[derive(Debug)]
    struct FakeSecurityLookup;

    impl SecurityLookup for FakeSecurityLookup {
        fn provider_name(&self) -> &'static str {
            "Google Finance"
        }

        fn search<'a>(
            &'a self,
            _query: &'a str,
        ) -> BoxFuture<'a, Result<Vec<SecurityMatch>, SecurityLookupError>> {
            Box::pin(async { Ok(Vec::new()) })
        }

        fn load_company<'a>(
            &'a self,
            security_match: &'a SecurityMatch,
        ) -> BoxFuture<'a, Result<Company, SecurityLookupError>> {
            Box::pin(async move {
                Ok(Company {
                    symbol: security_match.symbol.clone(),
                    name: format!("{} Corp", security_match.symbol),
                    price: 100.0,
                    change: 2.0,
                    change_percent: 2.0,
                    market_cap: 1_000_000.0,
                    volume: None,
                    volume_label: None,
                    pe: None,
                    eps: None,
                    high52_week: None,
                    low52_week: None,
                    profile: None,
                    price_chart: None,
                    price_chart_ranges: None,
                })
            })
        }
    }

    #[test]
    fn loads_default_empty_ledger_when_store_is_empty() {
        with_test_home("empty-ledger", || {
            let service = build_service();

            let history =
                futures::executor::block_on(service.history(10)).expect("history should load");

            assert!(history.is_empty());
        });
    }

    #[test]
    fn persists_appended_transactions() {
        with_test_home("persisted-ledger", || {
            let service = build_service();

            futures::executor::block_on(service.deposit_cash(1_000.0))
                .expect("deposit should succeed");

            let history =
                futures::executor::block_on(service.history(10)).expect("history should load");

            assert_eq!(history.len(), 1);
            assert_eq!(history[0].kind, TransactionKind::CashDeposit);
        });
    }

    #[test]
    fn rejects_buy_when_cash_is_insufficient() {
        with_test_home("buy-insufficient-cash", || {
            let service = build_service();

            let error = futures::executor::block_on(service.buy("AAPL", 10.0, Some(100.0)))
                .expect_err("buy should fail");

            assert!(matches!(
                error,
                PortfolioCommandError::Validation(PortfolioValidationError::TradeCash)
            ));
        });
    }

    #[test]
    fn rejects_sell_when_shares_are_insufficient() {
        with_test_home("sell-insufficient-shares", || {
            let service = build_service();

            futures::executor::block_on(service.deposit_cash(1_000.0))
                .expect("deposit should succeed");

            let error = futures::executor::block_on(service.sell("AAPL", 1.0, Some(100.0)))
                .expect_err("sell should fail");

            assert!(matches!(
                error,
                PortfolioCommandError::Validation(PortfolioValidationError::ShareShortfall { .. })
            ));
        });
    }

    #[test]
    fn rejects_withdrawal_when_cash_is_insufficient() {
        with_test_home("withdrawal-insufficient-cash", || {
            let service = build_service();

            let error = futures::executor::block_on(service.withdraw_cash(100.0))
                .expect_err("withdrawal should fail");

            assert!(matches!(
                error,
                PortfolioCommandError::Validation(PortfolioValidationError::WithdrawalCash)
            ));
        });
    }

    fn build_service() -> PortfolioService<MockRuntime> {
        let app = mock_builder()
            .plugin(tauri_plugin_store::Builder::new().build())
            .build(mock_context(noop_assets()))
            .expect("test app should build");

        PortfolioService::new(app.handle(), Arc::new(FakeSecurityLookup))
    }

    #[allow(dead_code)]
    fn seed_ledger(service: &PortfolioService<MockRuntime>, ledger: PortfolioLedger) {
        let store = service
            .app_handle
            .store(PORTFOLIO_LEDGER_STORE_PATH)
            .expect("store should exist");
        store.set(PORTFOLIO_LEDGER_KEY.to_string(), serde_json::json!(ledger));
        store.save().expect("store should save");
    }

    #[allow(dead_code)]
    fn cash_transaction(id: &str, amount: f64) -> PortfolioTransaction {
        PortfolioTransaction {
            id: id.to_string(),
            kind: TransactionKind::CashDeposit,
            symbol: None,
            company_name: None,
            quantity: None,
            price: None,
            gross_amount: amount,
            fee: 0.0,
            executed_at: "2026-01-01T00:00:00Z".to_string(),
            note: None,
        }
    }

    fn unique_identifier(prefix: &str) -> String {
        let nanos = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("clock should work")
            .as_nanos();
        format!("com.mosaiciq.portfolio.tests.{prefix}.{nanos}")
    }

    fn with_test_home<T>(prefix: &str, test: impl FnOnce() -> T) -> T {
        let _lock = crate::test_support::env_lock()
            .lock()
            .expect("env lock should succeed");
        let home = env::temp_dir().join(unique_identifier(prefix));
        fs::create_dir_all(&home).expect("home dir should exist");

        let original_home = env::var_os("HOME");
        env::set_var("HOME", &home);

        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(test));

        match original_home {
            Some(value) => env::set_var("HOME", value),
            None => env::remove_var("HOME"),
        }

        let _ = fs::remove_dir_all(&home);

        match result {
            Ok(value) => value,
            Err(payload) => std::panic::resume_unwind(payload),
        }
    }
}
