mod codex_toml;

use std::{
    collections::HashMap,
    future::Future,
    sync::{Arc, Mutex as StdMutex, OnceLock, Weak},
    time::Duration,
};

use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use tokio::sync::RwLock;

use crate::{
    app_config::AppType,
    codex_config::{get_codex_auth_path, get_codex_config_path, write_codex_live_atomic},
    config::{get_claude_settings_path, read_json_file, write_json_file, write_text_file},
    database::Database,
    gemini_config::{
        env_to_json, get_gemini_env_path, json_to_env, read_gemini_env, write_gemini_env_atomic,
    },
    provider::Provider,
    proxy::{
        switch_lock::SwitchLockManager,
        types::{GlobalProxyConfig, ProxyTakeoverStatus},
        ProxyConfig, ProxyServer, ProxyServerInfo, ProxyStatus,
    },
    AppError,
};

const PROXY_TOKEN_PLACEHOLDER: &str = "PROXY_MANAGED";
const PROXY_RUNTIME_SESSION_KEY: &str = "proxy_runtime_session";
const PROXY_RUNTIME_KIND_ENV_KEY: &str = "CC_SWITCH_PROXY_RUNTIME_KIND";
const PROXY_RUNTIME_SESSION_TOKEN_ENV_KEY: &str = "CC_SWITCH_PROXY_SESSION_TOKEN";

const CLAUDE_MODEL_OVERRIDE_ENV_KEYS: [&str; 6] = [
    "ANTHROPIC_MODEL",
    "ANTHROPIC_REASONING_MODEL",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL",
    "ANTHROPIC_DEFAULT_SONNET_MODEL",
    "ANTHROPIC_DEFAULT_OPUS_MODEL",
    "ANTHROPIC_SMALL_FAST_MODEL",
];

#[derive(Clone)]
pub struct ProxyService {
    db: Arc<Database>,
    runtime: Arc<ProxyRuntimeState>,
    switch_locks: SwitchLockManager,
}

struct ProxyRuntimeState {
    server: RwLock<Option<ProxyServer>>,
}

#[derive(Debug, Clone, Copy, Default)]
pub struct HotSwitchOutcome {
    pub logical_target_changed: bool,
}

#[derive(Debug, Clone)]
pub struct GlobalProxySwitchUpdate {
    pub config: GlobalProxyConfig,
    pub cleared_auto_failover: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
enum PersistedProxyRuntimeSessionKind {
    #[serde(alias = "foreground")]
    Foreground,
    ManagedExternal,
}

impl Default for PersistedProxyRuntimeSessionKind {
    fn default() -> Self {
        Self::Foreground
    }
}

impl PersistedProxyRuntimeSessionKind {
    fn from_env() -> Self {
        match std::env::var(PROXY_RUNTIME_KIND_ENV_KEY).ok().as_deref() {
            Some("managed_external") => Self::ManagedExternal,
            _ => Self::Foreground,
        }
    }

    fn as_env_value(&self) -> &'static str {
        match self {
            Self::Foreground => "foreground",
            Self::ManagedExternal => "managed_external",
        }
    }

    fn is_managed_external(&self) -> bool {
        matches!(self, Self::ManagedExternal)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct PersistedProxyRuntimeSession {
    pid: u32,
    address: String,
    port: u16,
    started_at: String,
    #[serde(default)]
    kind: PersistedProxyRuntimeSessionKind,
    #[serde(default)]
    session_token: Option<String>,
    #[serde(default)]
    app_type: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct PersistedProxyRuntimeSessions {
    workers: HashMap<String, PersistedProxyRuntimeSession>,
}

enum ExternalProxyStatusProbe {
    Matched(ProxyStatus),
    Mismatched,
    Unreachable,
}

fn proxy_runtime_registry() -> &'static StdMutex<HashMap<String, Weak<ProxyRuntimeState>>> {
    static REGISTRY: OnceLock<StdMutex<HashMap<String, Weak<ProxyRuntimeState>>>> = OnceLock::new();
    REGISTRY.get_or_init(|| StdMutex::new(HashMap::new()))
}

impl ProxyService {
    fn run_in_blocking_runtime<T, F, Fut>(&self, task: F) -> Result<T, String>
    where
        T: Send + 'static,
        F: FnOnce(ProxyService) -> Fut + Send + 'static,
        Fut: Future<Output = Result<T, String>> + Send + 'static,
    {
        let service = self.clone();
        let handle = std::thread::Builder::new()
            .name("cc-switch-proxy-blocking".to_string())
            .spawn(move || {
                let runtime = tokio::runtime::Builder::new_current_thread()
                    .enable_all()
                    .build()
                    .map_err(|e| format!("failed to create async runtime: {e}"))?;
                runtime.block_on(task(service))
            })
            .map_err(|e| format!("spawn proxy runtime helper failed: {e}"))?;

        handle
            .join()
            .map_err(|_| "proxy runtime helper panicked".to_string())?
    }

    pub fn is_running_blocking(&self) -> Result<bool, String> {
        self.run_in_blocking_runtime(|service| async move { Ok(service.is_running().await) })
    }

    pub fn is_app_takeover_active_blocking(&self, app_type: &AppType) -> Result<bool, String> {
        let app_type = app_type.clone();
        self.run_in_blocking_runtime(move |service| async move {
            service.is_app_takeover_active(&app_type).await
        })
    }

    pub fn recover_takeovers_on_startup_blocking(&self) -> Result<(), String> {
        self.run_in_blocking_runtime(|service| async move {
            service.recover_takeovers_on_startup().await
        })
    }

    pub fn new(db: Arc<Database>) -> Self {
        let runtime = Self::shared_runtime_state(db.runtime_key());
        Self {
            db,
            runtime,
            switch_locks: SwitchLockManager::new(),
        }
    }

    fn shared_runtime_state(runtime_key: &str) -> Arc<ProxyRuntimeState> {
        let mut registry = proxy_runtime_registry()
            .lock()
            .unwrap_or_else(|poisoned| poisoned.into_inner());

        if let Some(existing) = registry.get(runtime_key).and_then(Weak::upgrade) {
            return existing;
        }

        let runtime = Arc::new(ProxyRuntimeState {
            server: RwLock::new(None),
        });
        registry.insert(runtime_key.to_string(), Arc::downgrade(&runtime));
        runtime
    }

    pub async fn start(&self) -> Result<ProxyServerInfo, String> {
        let _guard = crate::services::state_coordination::acquire_restore_mutation_guard().await?;
        let config = self.get_config().await.map_err(|e| e.to_string())?;
        self.start_with_resolved_config_unlocked(config).await
    }

    pub async fn start_with_runtime_config(
        &self,
        config: ProxyConfig,
    ) -> Result<ProxyServerInfo, String> {
        let _guard = crate::services::state_coordination::acquire_restore_mutation_guard().await?;
        self.start_with_resolved_config_unlocked(config).await
    }

    pub async fn start_managed_session(&self, app_type: &str) -> Result<ProxyServerInfo, String> {
        // This delegates to the daemon, whose IPC handler owns the state
        // mutation guard while it rewrites live config. Holding the guard in
        // the caller while waiting for the daemon can deadlock the handshake.
        self.start_managed_session_unlocked(app_type).await
    }

    async fn start_managed_session_unlocked(
        &self,
        app_type: &str,
    ) -> Result<ProxyServerInfo, String> {
        Self::ensure_managed_sessions_supported()?;

        let app_type = Self::takeover_app_from_str(app_type)?;
        if self.has_running_foreground_runtime().await {
            return Err(
                "proxy is already running in foreground mode; stop the current runtime before attaching another app to a managed session"
                    .to_string(),
            );
        }

        let current_status = self.get_status().await;
        let persisted_sessions = self.load_persisted_runtime_sessions();
        if current_status.running && persisted_sessions.is_empty() {
            return Err(
                "proxy is already running in foreground mode; stop the current runtime before attaching another app to a managed session"
                    .to_string(),
            );
        }
        if current_status.running {
            // Daemon is already running this worker. Just attach the app.
            self.daemon_ensure_worker(app_type.as_str()).await
        } else {
            self.validate_app_proxy_activation(&app_type, None).await?;
            self.daemon_ensure_worker(app_type.as_str()).await
        }
    }

    async fn daemon_ensure_worker(&self, app_type: &str) -> Result<ProxyServerInfo, String> {
        #[cfg(unix)]
        {
            use crate::daemon::ipc::client;
            use crate::daemon::ipc::protocol::{Request, Response};
            let socket_path = crate::daemon::paths::socket_path();
            let app_type = app_type.to_string();
            let response = tokio::task::spawn_blocking(move || {
                let mut stream = client::connect_or_spawn(&socket_path, || {
                    let bin = Self::resolve_managed_proxy_executable()
                        .map_err(client::ClientError::NoDaemon)?;
                    Ok(bin)
                })?;
                client::exchange(&mut stream, &Request::EnsureWorker { app_type })
            })
            .await
            .map_err(|err| format!("daemon ensure worker task panicked: {err}"))?
            .map_err(|err| format!("daemon ensure worker failed: {err}"))?;

            match response {
                Response::Worker { address, port, .. } => Ok(ProxyServerInfo {
                    address,
                    port,
                    started_at: chrono::Utc::now().to_rfc3339(),
                }),
                Response::Error { message } => Err(message),
                other => Err(format!(
                    "daemon ensure worker returned unexpected response: {other:?}"
                )),
            }
        }

        #[cfg(not(unix))]
        {
            let _ = app_type;
            Err("managed sessions are only supported on unix".to_string())
        }
    }

    async fn daemon_drop_takeover(&self, app_type: &str) -> Result<(), String> {
        #[cfg(unix)]
        {
            use crate::daemon::ipc::client;
            use crate::daemon::ipc::protocol::{Request, Response};
            use std::io::ErrorKind;
            let socket_path = crate::daemon::paths::socket_path();
            // No socket at all → daemon isn't running. Do the cleanup the
            // daemon would have done so the DB and live config stay aligned
            // with "this app is no longer being proxied".
            if !socket_path.exists() {
                return self.local_disable_takeover(app_type).await;
            }
            let app_type_owned = app_type.to_string();
            let socket_for_task = socket_path.clone();
            let outcome = tokio::task::spawn_blocking(
                move || -> Result<Option<Response>, client::ClientError> {
                    let mut stream = match client::connect(&socket_for_task) {
                        Ok(s) => s,
                        // ECONNREFUSED / ENOENT here means the socket inode is
                        // a leftover from a daemon that died ungracefully —
                        // nobody is listening. Treat as "no daemon" and let
                        // the caller fall back to local cleanup.
                        Err(client::ClientError::Io(e))
                            if matches!(
                                e.kind(),
                                ErrorKind::ConnectionRefused | ErrorKind::NotFound
                            ) =>
                        {
                            return Ok(None);
                        }
                        Err(e) => return Err(e),
                    };
                    client::exchange(
                        &mut stream,
                        &Request::DropTakeover {
                            app_type: app_type_owned,
                        },
                    )
                    .map(Some)
                },
            )
            .await
            .map_err(|err| format!("daemon drop takeover task panicked: {err}"))?
            .map_err(|err| format!("daemon drop takeover failed: {err}"))?;

            match outcome {
                Some(Response::Ok) => Ok(()),
                Some(Response::Error { message }) => Err(message),
                Some(other) => Err(format!(
                    "daemon drop takeover returned unexpected response: {other:?}"
                )),
                None => {
                    // Stale socket inode — best-effort remove so the next call
                    // takes the !socket_path.exists() short-circuit instead of
                    // tripping over the same ECONNREFUSED again.
                    let _ = std::fs::remove_file(&socket_path);
                    self.local_disable_takeover(app_type).await
                }
            }
        }

        #[cfg(not(unix))]
        {
            let _ = app_type;
            Err("managed sessions are only supported on unix".to_string())
        }
    }

    async fn should_drop_takeover_via_daemon(&self, app_type: &AppType) -> Result<bool, String> {
        let Some(session) = self.load_persisted_runtime_session_for_app(app_type) else {
            self.remove_stale_daemon_socket_if_unreachable();
            return Ok(false);
        };

        if !session.kind.is_managed_external() {
            return Ok(true);
        }

        if !Self::is_process_alive(session.pid) {
            self.clear_persisted_runtime_session_for_app(app_type)?;
            return Ok(false);
        }

        match Self::probe_external_proxy_status(&session).await {
            ExternalProxyStatusProbe::Matched(_) => Ok(true),
            ExternalProxyStatusProbe::Mismatched | ExternalProxyStatusProbe::Unreachable => {
                self.clear_persisted_runtime_session_for_app(app_type)?;
                Ok(false)
            }
        }
    }

    #[cfg(unix)]
    fn remove_stale_daemon_socket_if_unreachable(&self) {
        use std::io::ErrorKind;

        let socket_path = crate::daemon::paths::socket_path();
        if !socket_path.exists() {
            return;
        }

        match std::os::unix::net::UnixStream::connect(&socket_path) {
            Ok(_) => {}
            Err(error)
                if matches!(
                    error.kind(),
                    ErrorKind::ConnectionRefused | ErrorKind::NotFound
                ) =>
            {
                let _ = std::fs::remove_file(socket_path);
            }
            Err(_) => {}
        }
    }

    #[cfg(not(unix))]
    fn remove_stale_daemon_socket_if_unreachable(&self) {}

    /// Foreground-only fallback for when no daemon is reachable. Drops the
    /// per-app takeover via the same code path the supervisor uses, takes the
    /// cross-process state-mutation guard around it (so a concurrent CLI
    /// invocation can't race the live-config restore), and clears the matching
    /// daemon-managed runtime marker.
    async fn local_disable_takeover(&self, app_type: &str) -> Result<(), String> {
        let app = Self::takeover_app_from_str(app_type)?;
        let _guard = crate::services::state_coordination::acquire_restore_mutation_guard().await?;
        self.disable_takeover_for_app_unlocked(&app, false).await?;
        if !self
            .db
            .is_live_takeover_active()
            .await
            .map_err(|error| format!("check active takeovers failed: {error}"))?
        {
            self.sync_persisted_global_proxy_enabled(false).await?;
        }
        if let Some(session) = self.load_persisted_runtime_session_for_app(&app) {
            if session.kind.is_managed_external() {
                if matches!(
                    Self::probe_external_proxy_status(&session).await,
                    ExternalProxyStatusProbe::Matched(_)
                ) && Self::is_process_alive(session.pid)
                {
                    Self::terminate_external_process(session.pid).await?;
                }
            }
        }
        let _ = self.clear_persisted_runtime_session_for_app(&app);
        Ok(())
    }

    pub async fn set_managed_session_for_app(
        &self,
        app_type: &str,
        enabled: bool,
    ) -> Result<(), String> {
        // Intentionally NO state-mutation guard here. This function purely
        // delegates to the daemon (`daemon_ensure_worker` / `daemon_drop_takeover`)
        // which acquires its own cross-process guard inside its IPC handler.
        // Holding the guard on the foreground side and then making a synchronous
        // IPC call deadlocks against the daemon's handler — observed as
        // "Resource temporarily unavailable (os error 35)" once the IPC read
        // times out.
        self.set_managed_session_for_app_unlocked(app_type, enabled)
            .await
    }

    async fn set_managed_session_for_app_unlocked(
        &self,
        app_type: &str,
        enabled: bool,
    ) -> Result<(), String> {
        let app_type_enum = Self::takeover_app_from_str(app_type)?;

        if enabled {
            if self.has_running_foreground_runtime().await {
                return Err(
                    "proxy is already running in foreground mode; stop the current runtime before attaching another app to a managed session"
                        .to_string(),
                );
            }

            self.cleanup_legacy_managed_runtime_session_before_daemon_start()
                .await?;

            let status = self.get_status().await;
            if status.running && self.load_persisted_runtime_sessions().is_empty() {
                return Err(
                    "proxy is already running in foreground mode; stop the current runtime before attaching another app to a managed session"
                        .to_string(),
                );
            }

            // Daemon-driven path: ensure worker is up + takeover is on for this app.
            self.daemon_ensure_worker(app_type_enum.as_str()).await?;
            return Ok(());
        }

        // Disable: route through the daemon when one is running so it stays
        // the sole writer of `proxy_runtime_session`.
        if self.should_drop_takeover_via_daemon(&app_type_enum).await? {
            self.daemon_drop_takeover(app_type_enum.as_str()).await
        } else {
            self.local_disable_takeover(app_type_enum.as_str()).await
        }
    }

    async fn cleanup_legacy_managed_runtime_session_before_daemon_start(
        &self,
    ) -> Result<(), String> {
        let Some(session) = self.load_legacy_persisted_runtime_session() else {
            return Ok(());
        };
        if !session.kind.is_managed_external() {
            return Ok(());
        }

        // v5.6.1 compatibility shim for users upgrading from the pre-daemon
        // managed proxy. Remove after several releases once old single-session
        // runtime markers are no longer expected in the wild.
        if Self::is_process_alive(session.pid) {
            match Self::probe_external_proxy_status(&session).await {
                ExternalProxyStatusProbe::Matched(_) => {
                    Self::terminate_external_process(session.pid).await?;
                }
                ExternalProxyStatusProbe::Unreachable
                    if Self::has_managed_external_ownership_signal(&session) =>
                {
                    Self::terminate_external_process(session.pid).await?;
                }
                ExternalProxyStatusProbe::Mismatched | ExternalProxyStatusProbe::Unreachable => {}
            }
        }
        let _ = self.clear_persisted_runtime_session();
        Ok(())
    }

    async fn start_with_resolved_config_unlocked(
        &self,
        config: ProxyConfig,
    ) -> Result<ProxyServerInfo, String> {
        self.sync_persisted_global_proxy_enabled(true).await?;

        if let Some(server) = self.runtime.server.read().await.as_ref() {
            let status = server.get_status().await;
            if status.running {
                return Ok(ProxyServerInfo {
                    address: status.address,
                    port: status.port,
                    started_at: chrono::Utc::now().to_rfc3339(),
                });
            }
        }

        let server = ProxyServer::new(config, self.db.clone());
        let info = server.start().await?;
        if !Self::defer_runtime_session_publish() {
            if let Err(error) = self.persist_runtime_session(&info) {
                let _ = server.stop().await;
                return Err(error);
            }
        }
        *self.runtime.server.write().await = Some(server);
        Ok(info)
    }

    async fn runtime_config_for_app(&self, app_type: &AppType) -> Result<ProxyConfig, String> {
        let mut config = self.get_config().await.map_err(|e| e.to_string())?;
        config.listen_port = self
            .db
            .get_app_proxy_preferred_port(app_type.as_str())
            .map_err(|error| {
                format!(
                    "load proxy preference for {} failed: {error}",
                    app_type.as_str()
                )
            })?;
        Ok(config)
    }

    pub(crate) fn publish_runtime_session_if_needed(
        &self,
        info: &ProxyServerInfo,
    ) -> Result<(), String> {
        if Self::defer_runtime_session_publish() && self.load_persisted_runtime_session().is_none()
        {
            self.persist_runtime_session(info)?;
        }
        Ok(())
    }

    pub async fn recover_takeovers_on_startup(&self) -> Result<(), String> {
        for app_type in [AppType::Claude, AppType::Codex, AppType::Gemini] {
            let app_key = app_type.as_str();
            let app_proxy = self
                .db
                .get_proxy_config_for_app(app_key)
                .await
                .map_err(|error| format!("load proxy config for {app_key} failed: {error}"))?;
            let has_backup = self
                .db
                .get_live_backup(app_key)
                .await
                .map_err(|error| format!("load live backup for {app_key} failed: {error}"))?
                .is_some();
            let live_taken_over = self.detect_takeover_in_live_config_for_app(&app_type);

            if !app_proxy.enabled && !has_backup && !live_taken_over {
                self.clear_app_proxy_routing_flags(app_proxy).await?;
                continue;
            }

            if has_backup {
                self.restore_live_config_for_app(&app_type).await?;
                self.db
                    .delete_live_backup(app_key)
                    .await
                    .map_err(|error| format!("delete live backup for {app_key} failed: {error}"))?;
            } else if live_taken_over {
                self.restore_live_from_current_provider(&app_type).await?;
            }

            self.clear_app_proxy_routing_flags(app_proxy).await?;
        }

        Ok(())
    }

    pub async fn stop(&self) -> Result<(), String> {
        let _guard = crate::services::state_coordination::acquire_restore_mutation_guard().await?;
        self.stop_server_unlocked().await
    }

    pub async fn stop_with_restore(&self) -> Result<(), String> {
        let _guard = crate::services::state_coordination::acquire_restore_mutation_guard().await?;

        if let Err(error) = self.stop_server_unlocked().await {
            log::warn!("stop proxy runtime before restore failed: {error}");
        }

        self.restore_active_takeovers_on_shutdown_unlocked().await
    }

    async fn stop_server_unlocked(&self) -> Result<(), String> {
        let mut stopped_runtime = false;

        if let Some(server) = self.runtime.server.read().await.as_ref() {
            server.stop().await?;
            self.clear_persisted_runtime_session()?;
            self.sync_persisted_global_proxy_enabled(false).await?;
            return Ok(());
        }

        if let Some(session) = self.load_persisted_runtime_session() {
            if session.kind.is_managed_external() {
                if matches!(
                    Self::probe_external_proxy_status(&session).await,
                    ExternalProxyStatusProbe::Matched(_)
                ) && Self::is_process_alive(session.pid)
                {
                    Self::terminate_external_process(session.pid).await?;
                    stopped_runtime = true;
                }
            }
        }

        self.clear_persisted_runtime_session()?;
        self.sync_persisted_global_proxy_enabled(false).await?;
        if stopped_runtime {
            Ok(())
        } else {
            Err("proxy server is not running".to_string())
        }
    }

    pub async fn is_running(&self) -> bool {
        self.get_status().await.running
    }

    pub async fn get_status(&self) -> ProxyStatus {
        if let Some(server) = self.runtime.server.read().await.as_ref() {
            return server.get_status().await;
        }

        let sessions = self.load_persisted_runtime_sessions();
        if !sessions.is_empty()
            && sessions
                .iter()
                .all(|session| session.kind.is_managed_external())
        {
            let mut workers = Vec::new();
            let mut primary_status = None;
            let mut stale_app_keys = Vec::new();

            for session in sessions {
                if !Self::is_process_alive(session.pid) {
                    stale_app_keys.push(session.app_type.clone());
                    continue;
                }

                match Self::probe_external_proxy_status(&session).await {
                    ExternalProxyStatusProbe::Matched(status) => {
                        workers.push(crate::proxy::types::ActiveWorker {
                            app_type: session
                                .app_type
                                .clone()
                                .unwrap_or_else(|| "proxy".to_string()),
                            address: status.address.clone(),
                            port: status.port,
                            pid: Some(session.pid),
                        });
                        if primary_status.is_none() {
                            primary_status = Some(status);
                        }
                    }
                    ExternalProxyStatusProbe::Mismatched => {
                        stale_app_keys.push(session.app_type.clone());
                    }
                    ExternalProxyStatusProbe::Unreachable => {
                        stale_app_keys.push(session.app_type.clone());
                    }
                }
            }

            if !stale_app_keys.is_empty() {
                let _ = self.clear_persisted_runtime_sessions_for_app_keys(&stale_app_keys);
            }

            if let Some(mut status) = primary_status {
                status.running = !workers.is_empty();
                status.active_workers = workers;
                return status;
            }

            return ProxyStatus::default();
        }

        if let Some(session) = sessions.into_iter().next() {
            if Self::is_process_alive(session.pid) {
                let uptime_seconds = chrono::DateTime::parse_from_rfc3339(&session.started_at)
                    .ok()
                    .map(|started_at| {
                        let started_at = started_at.with_timezone(&chrono::Utc);
                        (chrono::Utc::now() - started_at).num_seconds().max(0) as u64
                    })
                    .unwrap_or(0);

                return ProxyStatus {
                    running: true,
                    address: session.address,
                    port: session.port,
                    uptime_seconds,
                    ..ProxyStatus::default()
                };
            }

            let _ = self.clear_persisted_runtime_session();
        }

        ProxyStatus::default()
    }

    async fn has_running_foreground_runtime(&self) -> bool {
        if let Some(server) = self.runtime.server.read().await.as_ref() {
            return server.get_status().await.running;
        }
        false
    }

    pub async fn get_config(&self) -> Result<ProxyConfig, AppError> {
        self.db.get_proxy_config().await
    }

    pub async fn update_config(&self, config: &ProxyConfig) -> Result<(), AppError> {
        let _guard = crate::services::state_coordination::acquire_restore_mutation_guard()
            .await
            .map_err(AppError::Message)?;
        self.db.update_proxy_config(config.clone()).await
    }

    pub async fn update_circuit_breaker_configs(
        &self,
        config: crate::proxy::circuit_breaker::CircuitBreakerConfig,
    ) -> Result<(), String> {
        if let Some(server) = self.runtime.server.read().await.as_ref() {
            server.update_circuit_breaker_configs(config).await;
        }

        Ok(())
    }

    pub async fn reset_provider_circuit_breaker(
        &self,
        provider_id: &str,
        app_type: &str,
    ) -> Result<(), String> {
        if let Some(server) = self.runtime.server.read().await.as_ref() {
            server
                .reset_provider_circuit_breaker(provider_id, app_type)
                .await;
        }

        Ok(())
    }

    pub async fn get_global_config(&self) -> Result<GlobalProxyConfig, AppError> {
        self.db.get_global_proxy_config().await
    }

    pub async fn set_global_enabled(
        &self,
        enabled: bool,
    ) -> Result<GlobalProxySwitchUpdate, AppError> {
        let _guard = crate::services::state_coordination::acquire_restore_mutation_guard()
            .await
            .map_err(AppError::Message)?;
        let mut config = self.get_global_config().await?;
        config.proxy_enabled = enabled;
        let cleared_auto_failover = if enabled {
            0
        } else {
            self.db.clear_auto_failover_for_supported_apps().await?
        };
        self.db.update_global_proxy_config(config.clone()).await?;

        Ok(GlobalProxySwitchUpdate {
            config,
            cleared_auto_failover,
        })
    }

    fn first_failover_provider_id(&self, app_type: &str) -> Result<String, String> {
        self.db
            .get_failover_queue(app_type)
            .map_err(|error| format!("load failover queue for {app_type} failed: {error}"))?
            .first()
            .map(|item| item.provider_id.clone())
            .ok_or_else(|| "failover queue is empty".to_string())
    }

    async fn persist_auto_failover_for_app(
        &self,
        app_type: &str,
        enabled: bool,
    ) -> Result<(), String> {
        let mut config = self
            .db
            .get_proxy_config_for_app(app_type)
            .await
            .map_err(|error| format!("load proxy config for {app_type} failed: {error}"))?;
        config.auto_failover_enabled = enabled;
        self.db
            .update_proxy_config_for_app(config)
            .await
            .map_err(|error| format!("update proxy config for {app_type} failed: {error}"))
    }

    pub async fn set_auto_failover_for_app(
        &self,
        app_type: &str,
        enabled: bool,
    ) -> Result<(), String> {
        if enabled {
            return self.enable_auto_failover_for_app(app_type).await;
        }

        let app_type = Self::takeover_app_from_str(app_type)?;
        self.persist_auto_failover_for_app(app_type.as_str(), false)
            .await
    }

    async fn ensure_proxy_routing_active_for_app(&self, app_type: &str) -> Result<(), String> {
        let app_type = Self::takeover_app_from_str(app_type)?;
        let has_managed_worker = self
            .load_persisted_runtime_session_for_app(&app_type)
            .is_some_and(|session| {
                session.kind.is_managed_external() && Self::is_process_alive(session.pid)
            });
        if !has_managed_worker {
            return Err(
                "automatic failover requires daemon-managed proxy routing for this app".to_string(),
            );
        }

        let app_key = app_type.as_str();
        let config = self
            .db
            .get_proxy_config_for_app(app_key)
            .await
            .map_err(|error| format!("load proxy config for {app_key} failed: {error}"))?;
        if !config.enabled {
            return Err(
                "automatic failover requires proxy takeover for this app to be enabled".to_string(),
            );
        }

        Ok(())
    }

    pub async fn enable_auto_failover_for_app(&self, app_type: &str) -> Result<(), String> {
        let first_provider_id = self.first_failover_provider_id(app_type)?;
        self.ensure_proxy_routing_active_for_app(app_type).await?;
        self.switch_proxy_target(app_type, &first_provider_id)
            .await?;
        self.persist_auto_failover_for_app(app_type, true).await
    }

    pub async fn enable_proxy_and_auto_failover_for_app(
        &self,
        app_type: &str,
    ) -> Result<(), String> {
        let first_provider_id = self.first_failover_provider_id(app_type)?;
        let app_type = Self::takeover_app_from_str(app_type)?;
        let app_key = app_type.as_str();
        self.set_managed_session_for_app(app_key, true).await?;
        self.switch_proxy_target(app_key, &first_provider_id)
            .await?;
        self.persist_auto_failover_for_app(app_key, true).await?;

        Ok(())
    }

    pub async fn get_takeover_status(&self) -> Result<ProxyTakeoverStatus, String> {
        Ok(ProxyTakeoverStatus {
            claude: self
                .db
                .get_proxy_config_for_app("claude")
                .await
                .map_err(|error| format!("load claude proxy config failed: {error}"))?
                .enabled,
            codex: self
                .db
                .get_proxy_config_for_app("codex")
                .await
                .map_err(|error| format!("load codex proxy config failed: {error}"))?
                .enabled,
            gemini: self
                .db
                .get_proxy_config_for_app("gemini")
                .await
                .map_err(|error| format!("load gemini proxy config failed: {error}"))?
                .enabled,
        })
    }

    pub async fn set_takeover_for_app(&self, app_type: &str, enabled: bool) -> Result<(), String> {
        let app_type = Self::takeover_app_from_str(app_type)?;
        let _guard = crate::services::state_coordination::acquire_restore_mutation_guard().await?;

        if enabled {
            self.enable_takeover_for_app_unlocked(&app_type).await
        } else {
            self.disable_takeover_for_app_unlocked(&app_type, true)
                .await
        }
    }

    pub(crate) async fn clear_daemon_takeover_for_app(&self, app_type: &str) -> Result<(), String> {
        let app_type = Self::takeover_app_from_str(app_type)?;
        let _guard = crate::services::state_coordination::acquire_restore_mutation_guard().await?;
        self.disable_takeover_for_app_unlocked(&app_type, false)
            .await
    }

    pub async fn is_app_takeover_active(&self, app_type: &AppType) -> Result<bool, String> {
        let app_key = app_type.as_str();
        let app_proxy = self
            .db
            .get_proxy_config_for_app(app_key)
            .await
            .map_err(|error| format!("load proxy config for {app_key} failed: {error}"))?;
        if app_proxy.enabled {
            return Ok(true);
        }

        if self
            .db
            .get_live_backup(app_key)
            .await
            .map_err(|error| format!("load live backup for {app_key} failed: {error}"))?
            .is_some()
        {
            return Ok(true);
        }

        Ok(self.detect_takeover_in_live_config_for_app(app_type))
    }

    pub fn detect_takeover_in_live_config_for_app(&self, app_type: &AppType) -> bool {
        match app_type {
            AppType::Claude => self
                .read_claude_live()
                .ok()
                .is_some_and(|live| Self::is_claude_live_taken_over(&live)),
            AppType::Codex => self
                .read_codex_live()
                .ok()
                .is_some_and(|live| Self::is_codex_live_taken_over(&live)),
            AppType::Gemini => self
                .read_gemini_live()
                .ok()
                .is_some_and(|live| Self::is_gemini_live_taken_over(&live)),
            _ => false,
        }
    }

    fn is_claude_live_taken_over(config: &Value) -> bool {
        let Some(env) = config.get("env").and_then(Value::as_object) else {
            return false;
        };

        [
            "ANTHROPIC_AUTH_TOKEN",
            "ANTHROPIC_API_KEY",
            "OPENROUTER_API_KEY",
            "OPENAI_API_KEY",
        ]
        .iter()
        .any(|key| {
            env.get(*key)
                .and_then(Value::as_str)
                .is_some_and(|value| value == PROXY_TOKEN_PLACEHOLDER)
        })
    }

    fn is_codex_live_taken_over(config: &Value) -> bool {
        config
            .get("auth")
            .and_then(|auth| auth.get("OPENAI_API_KEY"))
            .and_then(Value::as_str)
            .is_some_and(|value| value == PROXY_TOKEN_PLACEHOLDER)
    }

    fn is_gemini_live_taken_over(config: &Value) -> bool {
        config
            .get("env")
            .and_then(|env| env.get("GEMINI_API_KEY"))
            .and_then(Value::as_str)
            .is_some_and(|value| value == PROXY_TOKEN_PLACEHOLDER)
    }

    pub async fn update_live_backup_from_provider(
        &self,
        app_type: &str,
        provider: &Provider,
    ) -> Result<(), String> {
        let app_type = Self::takeover_app_from_str(app_type)?;
        let mut backup_snapshot = self.build_live_snapshot_from_provider(&app_type, provider)?;

        if matches!(app_type, AppType::Codex) {
            let existing_backup_value = self
                .db
                .get_live_backup(app_type.as_str())
                .await
                .map_err(|error| {
                    format!(
                        "load {} existing live backup failed: {error}",
                        app_type.as_str()
                    )
                })?
                .map(|backup| {
                    serde_json::from_str::<Value>(&backup.original_config).map_err(|error| {
                        format!(
                            "parse {} existing live backup failed: {error}",
                            app_type.as_str()
                        )
                    })
                })
                .transpose()?;

            if let Some(existing_value) = existing_backup_value.as_ref() {
                Self::preserve_codex_mcp_servers_in_backup(&mut backup_snapshot, existing_value)?;
            }

            let anchor_config_text = existing_backup_value
                .as_ref()
                .and_then(|value| value.get("config"))
                .and_then(Value::as_str);
            crate::codex_config::normalize_codex_settings_config_model_provider(
                &mut backup_snapshot,
                anchor_config_text,
            )
            .map_err(|error| format!("normalize Codex live backup failed: {error}"))?;
        }

        if matches!(app_type, AppType::Gemini) {
            backup_snapshot = json!({
                "env": backup_snapshot
                    .get("env")
                    .cloned()
                    .unwrap_or_else(|| json!({}))
            });
        }

        self.save_live_backup_snapshot(app_type.as_str(), &backup_snapshot)
            .await
    }

    pub async fn hot_switch_provider(
        &self,
        app_type: &str,
        provider_id: &str,
    ) -> Result<HotSwitchOutcome, String> {
        let _guard = self.switch_locks.lock_for_app(app_type).await;

        let app_type_enum = Self::takeover_app_from_str(app_type)?;
        let provider = self
            .db
            .get_provider_by_id(provider_id, app_type)
            .map_err(|e| format!("读取供应商失败: {e}"))?
            .ok_or_else(|| format!("供应商不存在: {provider_id}"))?;

        let logical_target_changed =
            crate::settings::get_effective_current_provider(&self.db, &app_type_enum)
                .map_err(|e| format!("读取当前供应商失败: {e}"))?
                .as_deref()
                != Some(provider_id);

        let has_backup = self
            .db
            .get_live_backup(app_type_enum.as_str())
            .await
            .map_err(|e| format!("读取 {app_type} 备份失败: {e}"))?
            .is_some();
        let live_taken_over = self.detect_takeover_in_live_config_for_app(&app_type_enum);
        let should_sync_backup = has_backup || live_taken_over;

        self.db
            .set_current_provider(app_type_enum.as_str(), provider_id)
            .map_err(|e| format!("更新当前供应商失败: {e}"))?;
        crate::settings::set_current_provider(&app_type_enum, Some(provider_id))
            .map_err(|e| format!("更新本地当前供应商失败: {e}"))?;

        if should_sync_backup {
            self.update_live_backup_from_provider(app_type, &provider)
                .await?;
        }

        if let Some(server) = self.runtime.server.read().await.as_ref() {
            server
                .set_active_target(app_type_enum.as_str(), &provider.id, &provider.name)
                .await;
        }

        Ok(HotSwitchOutcome {
            logical_target_changed,
        })
    }

    pub async fn switch_proxy_target(
        &self,
        app_type: &str,
        provider_id: &str,
    ) -> Result<(), String> {
        let outcome = self.hot_switch_provider(app_type, provider_id).await?;

        if outcome.logical_target_changed {
            log::info!("代理模式：已切换 {app_type} 的目标供应商为 {provider_id}");
        } else {
            log::debug!("代理模式：{app_type} 已对齐到目标供应商 {provider_id}");
        }

        Ok(())
    }

    fn preserve_codex_mcp_servers_in_backup(
        target_settings: &mut Value,
        existing_backup: &Value,
    ) -> Result<(), String> {
        let target_obj = target_settings
            .as_object_mut()
            .ok_or_else(|| "Codex live backup must be a JSON object".to_string())?;

        let target_config = target_obj
            .get("config")
            .and_then(Value::as_str)
            .unwrap_or("");
        let mut target_doc = if target_config.trim().is_empty() {
            toml_edit::DocumentMut::new()
        } else {
            target_config
                .parse::<toml_edit::DocumentMut>()
                .map_err(|error| format!("parse new Codex config.toml failed: {error}"))?
        };

        let existing_config = existing_backup
            .get("config")
            .and_then(Value::as_str)
            .unwrap_or("");
        if existing_config.trim().is_empty() {
            target_obj.insert("config".to_string(), json!(target_doc.to_string()));
            return Ok(());
        }

        let existing_doc = existing_config
            .parse::<toml_edit::DocumentMut>()
            .map_err(|error| format!("parse existing Codex backup failed: {error}"))?;

        if let Some(existing_mcp_servers) = existing_doc.get("mcp_servers") {
            match target_doc.get_mut("mcp_servers") {
                Some(target_mcp_servers) => {
                    if let (Some(target_table), Some(existing_table)) = (
                        target_mcp_servers.as_table_like_mut(),
                        existing_mcp_servers.as_table_like(),
                    ) {
                        for (server_id, server_item) in existing_table.iter() {
                            if target_table.get(server_id).is_none() {
                                target_table.insert(server_id, server_item.clone());
                            }
                        }
                    }
                }
                None => {
                    target_doc["mcp_servers"] = existing_mcp_servers.clone();
                }
            }
        }

        target_obj.insert("config".to_string(), json!(target_doc.to_string()));
        Ok(())
    }

    pub(crate) async fn validate_app_proxy_activation(
        &self,
        app_type: &AppType,
        fallback_provider_id: Option<&str>,
    ) -> Result<(), String> {
        let app_key = app_type.as_str();
        let app_proxy = self
            .db
            .get_proxy_config_for_app(app_key)
            .await
            .map_err(|error| format!("load proxy config for {app_key} failed: {error}"))?;

        if app_proxy.auto_failover_enabled
            && self
                .db
                .get_failover_queue(app_key)
                .map_err(|error| format!("load failover queue for {app_key} failed: {error}"))?
                .is_empty()
        {
            return Err(
                "cannot enable proxy because automatic failover is enabled and the failover queue is empty"
                    .to_string(),
            );
        }

        if let Some(provider_id) = fallback_provider_id {
            if self
                .db
                .get_provider_by_id(provider_id, app_key)
                .map_err(|error| {
                    format!("load provider {provider_id} for {app_key} failed: {error}")
                })?
                .is_some()
            {
                return Ok(());
            }
            return Err("cannot enable proxy because no active provider is selected".to_string());
        }

        if self.read_live_config_for_app(app_type).is_ok() {
            return Ok(());
        }

        let Some(provider_id) =
            crate::settings::get_effective_current_provider(self.db.as_ref(), app_type).map_err(
                |error| {
                    format!(
                        "load effective current provider for {} failed: {error}",
                        app_type.as_str()
                    )
                },
            )?
        else {
            return Err("cannot enable proxy because no active provider is selected".to_string());
        };

        if self
            .db
            .get_provider_by_id(&provider_id, app_key)
            .map_err(|error| format!("load provider {provider_id} for {app_key} failed: {error}"))?
            .is_none()
        {
            return Err("cannot enable proxy because no active provider is selected".to_string());
        }

        Ok(())
    }

    pub async fn save_live_backup_snapshot(
        &self,
        app_type: &str,
        snapshot: &Value,
    ) -> Result<(), String> {
        let app_type = Self::takeover_app_from_str(app_type)?;
        let backup = serde_json::to_string(snapshot).map_err(|error| {
            format!(
                "serialize {} live backup failed: {error}",
                app_type.as_str()
            )
        })?;
        self.db
            .save_live_backup(app_type.as_str(), &backup)
            .await
            .map_err(|error| format!("save {} live backup failed: {error}", app_type.as_str()))
    }

    async fn restore_active_takeovers_on_shutdown_unlocked(&self) -> Result<(), String> {
        for app_type in [AppType::Claude, AppType::Codex, AppType::Gemini] {
            self.disable_takeover_for_app_unlocked(&app_type, false)
                .await?;
        }

        Ok(())
    }

    async fn enable_takeover_for_app_unlocked(&self, app_type: &AppType) -> Result<(), String> {
        self.enable_takeover_for_app_unlocked_with_provider(app_type, None)
            .await
    }

    pub(crate) async fn enable_takeover_for_daemon_worker(
        &self,
        app_type: &str,
    ) -> Result<(), String> {
        let app_type = Self::takeover_app_from_str(app_type)?;
        let _guard = crate::services::state_coordination::acquire_restore_mutation_guard().await?;
        self.enable_takeover_for_app_unlocked_with_options(&app_type, None, true)
            .await
    }

    async fn enable_takeover_for_app_unlocked_with_provider(
        &self,
        app_type: &AppType,
        fallback_provider_id: Option<&str>,
    ) -> Result<(), String> {
        self.enable_takeover_for_app_unlocked_with_options(app_type, fallback_provider_id, false)
            .await
    }

    async fn enable_takeover_for_app_unlocked_with_options(
        &self,
        app_type: &AppType,
        fallback_provider_id: Option<&str>,
        runtime_already_known: bool,
    ) -> Result<(), String> {
        self.validate_app_proxy_activation(app_type, fallback_provider_id)
            .await?;

        if !runtime_already_known && !self.is_running().await {
            let config = self.runtime_config_for_app(app_type).await?;
            self.start_with_resolved_config_unlocked(config).await?;
        }

        let app_key = app_type.as_str();
        let app_proxy = self
            .db
            .get_proxy_config_for_app(app_key)
            .await
            .map_err(|error| format!("load proxy config for {app_key} failed: {error}"))?;
        let has_backup = self
            .db
            .get_live_backup(app_key)
            .await
            .map_err(|error| format!("load live backup for {app_key} failed: {error}"))?
            .is_some();
        let live_taken_over = self.detect_takeover_in_live_config_for_app(app_type);

        if app_proxy.enabled && has_backup && live_taken_over {
            return Ok(());
        }

        let (live, sync_live_token_to_current) = self
            .read_takeover_source_live(app_type, fallback_provider_id)
            .await?;
        if !has_backup {
            let backup = serde_json::to_string(&live)
                .map_err(|error| format!("serialize {app_key} live backup failed: {error}"))?;
            self.db
                .save_live_backup(app_key, &backup)
                .await
                .map_err(|error| format!("save {app_key} live backup failed: {error}"))?;
        }
        if sync_live_token_to_current {
            self.sync_live_config_to_current_provider(app_type, &live)
                .await?;
        }

        let (proxy_url, proxy_codex_base_url) = self.build_proxy_urls_for_app(app_type).await?;
        let mut taken_over = live;
        self.rewrite_live_for_proxy(app_type, &mut taken_over, &proxy_url, &proxy_codex_base_url)?;
        self.write_live_config_for_app(app_type, &taken_over)?;

        if !app_proxy.enabled {
            let mut updated = app_proxy;
            updated.enabled = true;
            self.db
                .update_proxy_config_for_app(updated)
                .await
                .map_err(|error| format!("set takeover flag for {app_key} failed: {error}"))?;
        }

        Ok(())
    }

    async fn disable_takeover_for_app_unlocked(
        &self,
        app_type: &AppType,
        stop_server_when_last: bool,
    ) -> Result<(), String> {
        let app_key = app_type.as_str();
        let app_proxy = self
            .db
            .get_proxy_config_for_app(app_key)
            .await
            .map_err(|error| format!("load proxy config for {app_key} failed: {error}"))?;
        let has_backup = self
            .db
            .get_live_backup(app_key)
            .await
            .map_err(|error| format!("load live backup for {app_key} failed: {error}"))?
            .is_some();
        let live_taken_over = self.detect_takeover_in_live_config_for_app(app_type);

        if !app_proxy.enabled && !has_backup && !live_taken_over {
            self.clear_app_proxy_routing_flags(app_proxy).await?;
            return Ok(());
        }

        if has_backup {
            self.restore_live_config_for_app(app_type).await?;
            self.db
                .delete_live_backup(app_key)
                .await
                .map_err(|error| format!("delete live backup for {app_key} failed: {error}"))?;
        } else if live_taken_over {
            self.restore_live_from_current_provider(app_type).await?;
        }

        self.clear_app_proxy_routing_flags(app_proxy).await?;

        self.db
            .clear_provider_health_for_app(app_key)
            .await
            .map_err(|error| format!("clear provider health for {app_key} failed: {error}"))?;

        if stop_server_when_last
            && !self
                .db
                .is_live_takeover_active()
                .await
                .map_err(|error| format!("check active takeovers failed: {error}"))?
        {
            self.stop_server_unlocked().await?;
        }

        Ok(())
    }

    async fn clear_app_proxy_routing_flags(
        &self,
        mut app_proxy: crate::proxy::types::AppProxyConfig,
    ) -> Result<(), String> {
        if !app_proxy.enabled && !app_proxy.auto_failover_enabled {
            return Ok(());
        }

        let app_key = app_proxy.app_type.clone();
        app_proxy.enabled = false;
        app_proxy.auto_failover_enabled = false;
        self.db
            .update_proxy_config_for_app(app_proxy)
            .await
            .map_err(|error| format!("clear proxy routing flags for {app_key} failed: {error}"))
    }

    async fn restore_live_config_for_app(&self, app_type: &AppType) -> Result<(), String> {
        let app_key = app_type.as_str();
        let Some(backup) = self
            .db
            .get_live_backup(app_key)
            .await
            .map_err(|error| format!("load live backup for {app_key} failed: {error}"))?
        else {
            return Ok(());
        };

        let restored: Value = serde_json::from_str(&backup.original_config)
            .map_err(|error| format!("parse {app_key} live backup failed: {error}"))?;
        self.write_live_config_for_app(app_type, &restored)
    }

    async fn restore_live_from_current_provider(&self, app_type: &AppType) -> Result<(), String> {
        let Some(settings) = self.current_provider_settings(app_type).await? else {
            return self.clear_stale_takeover_from_live_config(app_type);
        };
        self.write_live_config_for_app(app_type, &settings)
    }

    async fn current_provider_settings(&self, app_type: &AppType) -> Result<Option<Value>, String> {
        let Some(current_provider) =
            crate::settings::get_effective_current_provider(self.db.as_ref(), app_type).map_err(
                |error| {
                    format!(
                        "load effective current provider for {} failed: {error}",
                        app_type.as_str()
                    )
                },
            )?
        else {
            return Ok(None);
        };

        self.db
            .get_provider_by_id(&current_provider, app_type.as_str())
            .map_err(|error| {
                format!(
                    "load provider {} for {} failed: {error}",
                    current_provider,
                    app_type.as_str()
                )
            })
            .and_then(|provider| {
                provider
                    .map(|provider| {
                        self.build_current_provider_restore_snapshot(app_type, &provider)
                    })
                    .transpose()
            })
    }

    fn build_current_provider_restore_snapshot(
        &self,
        app_type: &AppType,
        provider: &Provider,
    ) -> Result<Value, String> {
        let common_config_snippet =
            self.db
                .get_config_snippet(app_type.as_str())
                .map_err(|error| {
                    format!(
                        "load common config snippet for {} failed: {error}",
                        app_type.as_str()
                    )
                })?;
        let apply_common_config =
            crate::services::provider::ProviderService::provider_uses_common_config_for_app(
                app_type,
                provider,
                common_config_snippet.as_deref(),
            );

        crate::services::provider::ProviderService::build_live_backup_snapshot(
            app_type,
            provider,
            common_config_snippet.as_deref(),
            apply_common_config,
        )
        .and_then(|mut snapshot| {
            if matches!(app_type, AppType::Codex) {
                crate::codex_config::normalize_codex_settings_config_model_provider(
                    &mut snapshot,
                    None,
                )?;
            }
            Ok(snapshot)
        })
        .map_err(|error| {
            format!(
                "build {} current-provider restore snapshot failed: {error}",
                app_type.as_str()
            )
        })
    }

    async fn sync_live_config_to_current_provider(
        &self,
        app_type: &AppType,
        live_config: &Value,
    ) -> Result<(), String> {
        enum LiveTokenSync {
            Claude(&'static str, String),
            Codex(String),
            Gemini(String),
        }

        let Some(token_sync) = (match app_type {
            AppType::Claude => live_config
                .get("env")
                .and_then(Value::as_object)
                .and_then(|env| {
                    [
                        "ANTHROPIC_AUTH_TOKEN",
                        "ANTHROPIC_API_KEY",
                        "OPENROUTER_API_KEY",
                        "OPENAI_API_KEY",
                    ]
                    .into_iter()
                    .find_map(|key| {
                        env.get(key)
                            .and_then(Value::as_str)
                            .map(|value| (key, value.trim().to_string()))
                    })
                })
                .filter(|(_, token)| !token.is_empty() && token != PROXY_TOKEN_PLACEHOLDER)
                .map(|(key, token)| LiveTokenSync::Claude(key, token)),
            AppType::Codex => live_config
                .get("auth")
                .and_then(|auth| auth.get("OPENAI_API_KEY"))
                .and_then(Value::as_str)
                .map(str::trim)
                .filter(|token| !token.is_empty() && *token != PROXY_TOKEN_PLACEHOLDER)
                .map(|token| LiveTokenSync::Codex(token.to_string())),
            AppType::Gemini => live_config
                .get("env")
                .and_then(|env| env.get("GEMINI_API_KEY"))
                .and_then(Value::as_str)
                .map(str::trim)
                .filter(|token| !token.is_empty() && *token != PROXY_TOKEN_PLACEHOLDER)
                .map(|token| LiveTokenSync::Gemini(token.to_string())),
            _ => None,
        }) else {
            return Ok(());
        };

        let Some(provider_id) =
            crate::settings::get_effective_current_provider(self.db.as_ref(), app_type).map_err(
                |error| {
                    format!(
                        "load effective current provider for {} failed: {error}",
                        app_type.as_str()
                    )
                },
            )?
        else {
            return Ok(());
        };

        let Some(mut provider) = self
            .db
            .get_provider_by_id(&provider_id, app_type.as_str())
            .map_err(|error| {
                format!(
                    "load provider {} for {} failed: {error}",
                    provider_id,
                    app_type.as_str()
                )
            })?
        else {
            return Ok(());
        };

        let Some(root) = (match &mut provider.settings_config {
            Value::Object(root) => Some(root),
            Value::Null => {
                provider.settings_config = json!({});
                provider.settings_config.as_object_mut()
            }
            _ => None,
        }) else {
            log::warn!(
                "skip syncing {} live token because provider {} settings root is not an object",
                app_type.as_str(),
                provider_id
            );
            return Ok(());
        };

        match token_sync {
            LiveTokenSync::Claude(token_key, token) => {
                let env_value = root.entry("env".to_string()).or_insert_with(|| json!({}));
                if !env_value.is_object() {
                    *env_value = json!({});
                }

                let Some(env) = env_value.as_object_mut() else {
                    log::warn!(
                        "skip syncing {} live token because provider {} env is not an object",
                        app_type.as_str(),
                        provider_id
                    );
                    return Ok(());
                };

                if matches!(token_key, "ANTHROPIC_AUTH_TOKEN" | "ANTHROPIC_API_KEY") {
                    let mut updated = false;
                    if env.contains_key("ANTHROPIC_AUTH_TOKEN") {
                        env.insert("ANTHROPIC_AUTH_TOKEN".to_string(), json!(token));
                        updated = true;
                    }
                    if env.contains_key("ANTHROPIC_API_KEY") {
                        env.insert("ANTHROPIC_API_KEY".to_string(), json!(token));
                        updated = true;
                    }
                    if !updated {
                        env.insert(token_key.to_string(), json!(token));
                    }
                } else {
                    env.insert(token_key.to_string(), json!(token));
                }
            }
            LiveTokenSync::Codex(token) => {
                let auth_value = root.entry("auth".to_string()).or_insert_with(|| json!({}));
                if !auth_value.is_object() {
                    *auth_value = json!({});
                }

                let Some(auth) = auth_value.as_object_mut() else {
                    log::warn!(
                        "skip syncing {} live token because provider {} auth is not an object",
                        app_type.as_str(),
                        provider_id
                    );
                    return Ok(());
                };

                auth.insert("OPENAI_API_KEY".to_string(), json!(token));
            }
            LiveTokenSync::Gemini(token) => {
                let env_value = root.entry("env".to_string()).or_insert_with(|| json!({}));
                if !env_value.is_object() {
                    *env_value = json!({});
                }

                let Some(env) = env_value.as_object_mut() else {
                    log::warn!(
                        "skip syncing {} live token because provider {} env is not an object",
                        app_type.as_str(),
                        provider_id
                    );
                    return Ok(());
                };

                env.insert("GEMINI_API_KEY".to_string(), json!(token));
            }
        }

        if let Err(error) = self.db.update_provider_settings_config(
            app_type.as_str(),
            &provider_id,
            &provider.settings_config,
        ) {
            log::warn!(
                "sync {} live token to provider {} failed: {error}",
                app_type.as_str(),
                provider_id
            );
        }

        Ok(())
    }

    async fn read_takeover_source_live(
        &self,
        app_type: &AppType,
        fallback_provider_id: Option<&str>,
    ) -> Result<(Value, bool), String> {
        if let Ok(live) = self.read_live_config_for_app(app_type) {
            return Ok((live, true));
        }

        if let Some(provider_id) = fallback_provider_id {
            let provider = self
                .db
                .get_provider_by_id(provider_id, app_type.as_str())
                .map_err(|error| {
                    format!(
                        "load provider {} for {} failed: {error}",
                        provider_id,
                        app_type.as_str()
                    )
                })?
                .ok_or_else(|| format!("provider does not exist: {provider_id}"))?;
            return self
                .build_current_provider_restore_snapshot(app_type, &provider)
                .map(|snapshot| (snapshot, false));
        }

        self.current_provider_settings(app_type)
            .await?
            .ok_or_else(|| {
                format!(
                    "missing live config and current provider for {}",
                    app_type.as_str()
                )
            })
            .map(|live| (live, false))
    }

    async fn build_proxy_urls_for_app(
        &self,
        app_type: &AppType,
    ) -> Result<(String, String), String> {
        let persisted = self.get_config().await.map_err(|e| e.to_string())?;
        let preferred_port = self
            .db
            .get_app_proxy_preferred_port(app_type.as_str())
            .map_err(|error| {
                format!(
                    "load proxy preference for {} failed: {error}",
                    app_type.as_str()
                )
            })?;
        let session = self.load_persisted_runtime_session_for_app(app_type);
        let listen_address = session
            .as_ref()
            .map(|session| session.address.clone())
            .filter(|address| !address.trim().is_empty())
            .unwrap_or_else(|| persisted.listen_address.clone());
        let listen_port = session
            .as_ref()
            .map(|session| session.port)
            .filter(|port| *port != 0)
            .unwrap_or(preferred_port);

        let connect_host = match listen_address.as_str() {
            "0.0.0.0" => "127.0.0.1".to_string(),
            "::" => "::1".to_string(),
            _ => listen_address,
        };
        let connect_host_for_url = if connect_host.contains(':') && !connect_host.starts_with('[') {
            format!("[{connect_host}]")
        } else {
            connect_host
        };

        let proxy_origin = format!("http://{}:{}", connect_host_for_url, listen_port);
        let proxy_codex_base_url = format!("{}/v1", proxy_origin.trim_end_matches('/'));
        Ok((proxy_origin, proxy_codex_base_url))
    }

    fn rewrite_live_for_proxy(
        &self,
        app_type: &AppType,
        live: &mut Value,
        proxy_url: &str,
        proxy_codex_base_url: &str,
    ) -> Result<(), String> {
        match app_type {
            AppType::Claude => {
                if !live.is_object() {
                    *live = json!({});
                }

                let root = live
                    .as_object_mut()
                    .ok_or_else(|| "claude live config root must be an object".to_string())?;
                if !root.get("env").is_some_and(Value::is_object) {
                    root.insert("env".to_string(), json!({}));
                }

                let env = root
                    .get_mut("env")
                    .and_then(Value::as_object_mut)
                    .ok_or_else(|| "claude env must be an object".to_string())?;
                env.insert("ANTHROPIC_BASE_URL".to_string(), json!(proxy_url));
                for key in CLAUDE_MODEL_OVERRIDE_ENV_KEYS {
                    env.remove(key);
                }

                let token_keys = [
                    "ANTHROPIC_AUTH_TOKEN",
                    "ANTHROPIC_API_KEY",
                    "OPENROUTER_API_KEY",
                    "OPENAI_API_KEY",
                ];
                let mut replaced_any = false;
                for key in token_keys {
                    if env.contains_key(key) {
                        env.insert(key.to_string(), json!(PROXY_TOKEN_PLACEHOLDER));
                        replaced_any = true;
                    }
                }

                if !replaced_any {
                    env.insert(
                        "ANTHROPIC_AUTH_TOKEN".to_string(),
                        json!(PROXY_TOKEN_PLACEHOLDER),
                    );
                }
            }
            AppType::Codex => {
                if !live.is_object() {
                    *live = json!({});
                }

                let root = live
                    .as_object_mut()
                    .ok_or_else(|| "codex live config root must be an object".to_string())?;
                if !root.get("auth").is_some_and(Value::is_object) {
                    root.insert("auth".to_string(), json!({}));
                }

                let auth = root
                    .get_mut("auth")
                    .and_then(Value::as_object_mut)
                    .ok_or_else(|| "codex auth must be an object".to_string())?;
                auth.insert("OPENAI_API_KEY".to_string(), json!(PROXY_TOKEN_PLACEHOLDER));

                let config_text = root
                    .get("config")
                    .and_then(Value::as_str)
                    .unwrap_or_default()
                    .to_string();
                root.insert(
                    "config".to_string(),
                    json!(codex_toml::update_toml_base_url(
                        &config_text,
                        proxy_codex_base_url
                    )),
                );
            }
            AppType::Gemini => {
                if !live.is_object() {
                    *live = json!({});
                }

                let root = live
                    .as_object_mut()
                    .ok_or_else(|| "gemini live config root must be an object".to_string())?;
                if !root.get("env").is_some_and(Value::is_object) {
                    root.insert("env".to_string(), json!({}));
                }

                let env = root
                    .get_mut("env")
                    .and_then(Value::as_object_mut)
                    .ok_or_else(|| "gemini env must be an object".to_string())?;
                env.insert("GOOGLE_GEMINI_BASE_URL".to_string(), json!(proxy_url));
                env.insert("GEMINI_API_KEY".to_string(), json!(PROXY_TOKEN_PLACEHOLDER));
            }
            _ => {
                return Err(format!(
                    "proxy takeover not supported for {}",
                    app_type.as_str()
                ));
            }
        }

        Ok(())
    }

    fn read_live_config_for_app(&self, app_type: &AppType) -> Result<Value, String> {
        match app_type {
            AppType::Claude => self.read_claude_live(),
            AppType::Codex => self.read_codex_live(),
            AppType::Gemini => self.read_gemini_live(),
            _ => Err(format!(
                "proxy takeover not supported for {}",
                app_type.as_str()
            )),
        }
    }

    fn write_live_config_for_app(&self, app_type: &AppType, config: &Value) -> Result<(), String> {
        match app_type {
            AppType::Claude => self.write_claude_live(config),
            AppType::Codex => self.write_codex_live(config),
            AppType::Gemini => self.write_gemini_live(config),
            _ => Err(format!(
                "proxy takeover not supported for {}",
                app_type.as_str()
            )),
        }
    }

    fn clear_stale_takeover_from_live_config(&self, app_type: &AppType) -> Result<(), String> {
        let mut live = match self.read_live_config_for_app(app_type) {
            Ok(live) => live,
            Err(_) => return Ok(()),
        };

        match app_type {
            AppType::Claude => {
                if let Some(env) = live.get_mut("env").and_then(Value::as_object_mut) {
                    if env
                        .get("ANTHROPIC_BASE_URL")
                        .and_then(Value::as_str)
                        .is_some_and(codex_toml::is_loopback_proxy_url)
                    {
                        env.remove("ANTHROPIC_BASE_URL");
                    }

                    for key in [
                        "ANTHROPIC_AUTH_TOKEN",
                        "ANTHROPIC_API_KEY",
                        "OPENROUTER_API_KEY",
                        "OPENAI_API_KEY",
                    ] {
                        if env
                            .get(key)
                            .and_then(Value::as_str)
                            .is_some_and(|value| value == PROXY_TOKEN_PLACEHOLDER)
                        {
                            env.remove(key);
                        }
                    }
                }
            }
            AppType::Codex => {
                if let Some(auth) = live.get_mut("auth").and_then(Value::as_object_mut) {
                    if auth
                        .get("OPENAI_API_KEY")
                        .and_then(Value::as_str)
                        .is_some_and(|value| value == PROXY_TOKEN_PLACEHOLDER)
                    {
                        auth.remove("OPENAI_API_KEY");
                    }
                }

                if let Some(config_text) = live.get("config").and_then(Value::as_str) {
                    live["config"] =
                        json!(codex_toml::remove_loopback_base_url_from_toml(config_text));
                }
            }
            AppType::Gemini => {
                if let Some(env) = live.get_mut("env").and_then(Value::as_object_mut) {
                    if env
                        .get("GOOGLE_GEMINI_BASE_URL")
                        .and_then(Value::as_str)
                        .is_some_and(codex_toml::is_loopback_proxy_url)
                    {
                        env.remove("GOOGLE_GEMINI_BASE_URL");
                    }
                    if env
                        .get("GEMINI_API_KEY")
                        .and_then(Value::as_str)
                        .is_some_and(|value| value == PROXY_TOKEN_PLACEHOLDER)
                    {
                        env.remove("GEMINI_API_KEY");
                    }
                }
            }
            _ => {
                return Err(format!(
                    "proxy takeover not supported for {}",
                    app_type.as_str()
                ));
            }
        }

        self.write_live_config_for_app(app_type, &live)
    }

    fn read_claude_live(&self) -> Result<Value, String> {
        let path = get_claude_settings_path();
        if !path.exists() {
            return Err("Claude settings.json does not exist".to_string());
        }

        let value: Value = read_json_file(&path)
            .map_err(|error| format!("read Claude settings.json failed: {error}"))?;
        if value.is_object() {
            Ok(value)
        } else {
            Err("Claude settings.json root must be an object".to_string())
        }
    }

    fn write_claude_live(&self, config: &Value) -> Result<(), String> {
        write_json_file(&get_claude_settings_path(), config)
            .map_err(|error| format!("write Claude settings.json failed: {error}"))
    }

    fn read_codex_live(&self) -> Result<Value, String> {
        let auth_path = get_codex_auth_path();
        let config_path = get_codex_config_path();
        if !auth_path.exists() && !config_path.exists() {
            return Err("Codex live config does not exist".to_string());
        }

        let auth = if auth_path.exists() {
            read_json_file(&auth_path)
                .map_err(|error| format!("read Codex auth.json failed: {error}"))?
        } else {
            json!({})
        };
        let config = if config_path.exists() {
            std::fs::read_to_string(&config_path)
                .map_err(|error| format!("read Codex config.toml failed: {error}"))?
        } else {
            String::new()
        };

        Ok(json!({
            "auth": auth,
            "config": config,
        }))
    }

    fn write_codex_live(&self, config: &Value) -> Result<(), String> {
        let auth = config
            .get("auth")
            .filter(|value| !value.as_object().is_some_and(|object| object.is_empty()));
        let config_text = config.get("config").and_then(Value::as_str);
        let auth_path = get_codex_auth_path();

        // Proxy restore applies the saved backup config without another stable-provider rewrite.
        match (auth, config_text) {
            (Some(auth), Some(config_text)) => write_codex_live_atomic(auth, Some(config_text))
                .map_err(|error| format!("write Codex live config failed: {error}")),
            (Some(auth), None) => write_json_file(&get_codex_auth_path(), auth)
                .map_err(|error| format!("write Codex auth.json failed: {error}")),
            (None, Some(config_text)) => {
                if auth_path.exists() {
                    std::fs::remove_file(&auth_path)
                        .map_err(|error| format!("remove Codex auth.json failed: {error}"))?;
                }
                write_text_file(&get_codex_config_path(), config_text)
                    .map_err(|error| format!("write Codex config.toml failed: {error}"))
            }
            (None, None) => {
                if auth_path.exists() {
                    std::fs::remove_file(&auth_path)
                        .map_err(|error| format!("remove Codex auth.json failed: {error}"))?;
                }
                Ok(())
            }
        }
    }

    fn build_live_snapshot_from_provider(
        &self,
        app_type: &AppType,
        provider: &Provider,
    ) -> Result<Value, String> {
        let common_config_snippet =
            self.db
                .get_config_snippet(app_type.as_str())
                .map_err(|error| {
                    format!(
                        "load common config snippet for {} failed: {error}",
                        app_type.as_str()
                    )
                })?;
        let apply_common_config =
            crate::services::provider::ProviderService::provider_uses_common_config_for_app(
                app_type,
                provider,
                common_config_snippet.as_deref(),
            );

        crate::services::provider::ProviderService::build_live_backup_snapshot(
            app_type,
            provider,
            common_config_snippet.as_deref(),
            apply_common_config,
        )
        .map_err(|error| {
            format!(
                "build {} live snapshot from provider failed: {error}",
                app_type.as_str()
            )
        })
    }

    fn persist_runtime_session(&self, info: &ProxyServerInfo) -> Result<(), String> {
        let session = PersistedProxyRuntimeSession {
            pid: std::process::id(),
            address: info.address.clone(),
            port: info.port,
            started_at: info.started_at.clone(),
            kind: PersistedProxyRuntimeSessionKind::from_env(),
            session_token: std::env::var(PROXY_RUNTIME_SESSION_TOKEN_ENV_KEY)
                .ok()
                .filter(|value| !value.trim().is_empty()),
            app_type: None,
        };
        let serialized = serde_json::to_string(&session)
            .map_err(|error| format!("serialize proxy runtime session failed: {error}"))?;
        self.db
            .set_setting(PROXY_RUNTIME_SESSION_KEY, &serialized)
            .map_err(|error| format!("persist proxy runtime session failed: {error}"))
    }

    fn defer_runtime_session_publish() -> bool {
        PersistedProxyRuntimeSessionKind::from_env().is_managed_external()
    }

    fn clear_persisted_runtime_session(&self) -> Result<(), String> {
        self.db
            .delete_setting(PROXY_RUNTIME_SESSION_KEY)
            .map_err(|error| format!("clear proxy runtime session failed: {error}"))
    }

    fn load_raw_persisted_runtime_session(&self) -> Option<String> {
        let raw = self
            .db
            .get_setting(PROXY_RUNTIME_SESSION_KEY)
            .ok()
            .flatten()?;
        let raw = raw.trim();
        if raw.is_empty() {
            None
        } else {
            Some(raw.to_string())
        }
    }

    fn load_persisted_runtime_sessions_map(
        &self,
    ) -> Option<HashMap<String, PersistedProxyRuntimeSession>> {
        let raw = self.load_raw_persisted_runtime_session()?;
        if let Ok(sessions) = serde_json::from_str::<PersistedProxyRuntimeSessions>(&raw) {
            return Some(sessions.workers);
        }
        None
    }

    fn load_legacy_persisted_runtime_session(&self) -> Option<PersistedProxyRuntimeSession> {
        let raw = self.load_raw_persisted_runtime_session()?;
        if serde_json::from_str::<PersistedProxyRuntimeSessions>(&raw).is_ok() {
            return None;
        }
        serde_json::from_str::<PersistedProxyRuntimeSession>(&raw).ok()
    }

    fn persist_persisted_runtime_sessions_map(
        &self,
        sessions: HashMap<String, PersistedProxyRuntimeSession>,
    ) -> Result<(), String> {
        if sessions.is_empty() {
            return self.clear_persisted_runtime_session();
        }

        let serialized =
            serde_json::to_string(&PersistedProxyRuntimeSessions { workers: sessions })
                .map_err(|error| format!("serialize proxy runtime sessions failed: {error}"))?;
        self.db
            .set_setting(PROXY_RUNTIME_SESSION_KEY, &serialized)
            .map_err(|error| format!("persist proxy runtime sessions failed: {error}"))
    }

    fn clear_persisted_runtime_session_for_app(&self, app_type: &AppType) -> Result<(), String> {
        let Some(mut sessions) = self.load_persisted_runtime_sessions_map() else {
            return self.clear_persisted_runtime_session();
        };
        sessions.remove(app_type.as_str());
        self.persist_persisted_runtime_sessions_map(sessions)
    }

    fn clear_persisted_runtime_sessions_for_app_keys(
        &self,
        app_keys: &[Option<String>],
    ) -> Result<(), String> {
        if app_keys.iter().any(Option::is_none) {
            return self.clear_persisted_runtime_session();
        }
        let Some(mut sessions) = self.load_persisted_runtime_sessions_map() else {
            return self.clear_persisted_runtime_session();
        };
        for app_key in app_keys.iter().filter_map(Option::as_deref) {
            sessions.remove(app_key);
        }
        self.persist_persisted_runtime_sessions_map(sessions)
    }

    fn load_persisted_runtime_sessions(&self) -> Vec<PersistedProxyRuntimeSession> {
        let Some(raw) = self.load_raw_persisted_runtime_session() else {
            return Vec::new();
        };

        if let Ok(sessions) = serde_json::from_str::<PersistedProxyRuntimeSessions>(&raw) {
            return sessions
                .workers
                .into_iter()
                .map(|(app_type, mut session)| {
                    if session.app_type.is_none() {
                        session.app_type = Some(app_type);
                    }
                    session
                })
                .collect();
        }

        match serde_json::from_str::<PersistedProxyRuntimeSession>(&raw) {
            Ok(session) => vec![session],
            Err(_) => {
                let _ = self.clear_persisted_runtime_session();
                Vec::new()
            }
        }
    }

    fn load_persisted_runtime_session(&self) -> Option<PersistedProxyRuntimeSession> {
        self.load_persisted_runtime_sessions().into_iter().next()
    }

    fn load_persisted_runtime_session_for_app(
        &self,
        app_type: &AppType,
    ) -> Option<PersistedProxyRuntimeSession> {
        let app_key = app_type.as_str();
        if let Some(mut sessions) = self.load_persisted_runtime_sessions_map() {
            return sessions.remove(app_key).map(|mut session| {
                if session.app_type.is_none() {
                    session.app_type = Some(app_key.to_string());
                }
                session
            });
        }

        self.load_persisted_runtime_session()
    }

    fn is_process_alive(pid: u32) -> bool {
        if pid == 0 {
            return false;
        }

        #[cfg(unix)]
        {
            let rc = unsafe { libc::kill(pid as i32, 0) };
            return rc == 0 || std::io::Error::last_os_error().raw_os_error() == Some(libc::EPERM);
        }

        #[cfg(not(unix))]
        {
            pid == std::process::id()
        }
    }

    fn has_managed_external_ownership_signal(session: &PersistedProxyRuntimeSession) -> bool {
        session.session_token.is_some() && Self::is_detached_session_leader(session.pid)
    }

    #[cfg(unix)]
    fn is_detached_session_leader(pid: u32) -> bool {
        if pid == 0 {
            return false;
        }

        let sid = unsafe { libc::getsid(pid as i32) };
        let pgid = unsafe { libc::getpgid(pid as i32) };
        sid == pid as i32 && pgid == pid as i32
    }

    #[cfg(not(unix))]
    fn is_detached_session_leader(_pid: u32) -> bool {
        false
    }

    async fn managed_session_ready_info(
        &self,
        child_pid: u32,
        session_token: &str,
    ) -> Option<ProxyServerInfo> {
        let session = self
            .load_persisted_runtime_session()
            .filter(|session| session.pid == child_pid)
            .filter(|session| session.kind.is_managed_external())
            .filter(|session| session.session_token.as_deref() == Some(session_token))?;

        match Self::probe_external_proxy_status(&session).await {
            ExternalProxyStatusProbe::Matched(status) => Some(ProxyServerInfo {
                address: status.address,
                port: status.port,
                started_at: session.started_at,
            }),
            ExternalProxyStatusProbe::Unreachable => Some(ProxyServerInfo {
                address: session.address,
                port: session.port,
                started_at: session.started_at,
            }),
            ExternalProxyStatusProbe::Mismatched => None,
        }
    }

    async fn probe_external_proxy_status(
        session: &PersistedProxyRuntimeSession,
    ) -> ExternalProxyStatusProbe {
        let Some(expected_session_token) = session.session_token.as_deref() else {
            return ExternalProxyStatusProbe::Unreachable;
        };
        let client = reqwest::Client::builder()
            .timeout(Duration::from_millis(500))
            .build();
        let Ok(client) = client else {
            return ExternalProxyStatusProbe::Unreachable;
        };

        let response = client
            .get(Self::build_session_status_url(session))
            .send()
            .await;
        let Ok(response) = response else {
            return ExternalProxyStatusProbe::Unreachable;
        };
        if !response.status().is_success() {
            return ExternalProxyStatusProbe::Unreachable;
        }

        let mut status = match response.json::<ProxyStatus>().await {
            Ok(status) => status,
            Err(_) => return ExternalProxyStatusProbe::Unreachable,
        };
        if status.managed_session_token.as_deref() != Some(expected_session_token) {
            return ExternalProxyStatusProbe::Mismatched;
        }
        status.running = true;
        if status.address.trim().is_empty() {
            status.address = session.address.clone();
        }
        if status.port == 0 {
            status.port = session.port;
        }
        ExternalProxyStatusProbe::Matched(status)
    }

    fn build_session_status_url(session: &PersistedProxyRuntimeSession) -> String {
        let connect_host = match session.address.as_str() {
            "0.0.0.0" => "127.0.0.1".to_string(),
            "::" => "::1".to_string(),
            value => value.to_string(),
        };
        let connect_host = if connect_host.contains(':') && !connect_host.starts_with('[') {
            format!("[{connect_host}]")
        } else {
            connect_host
        };

        format!("http://{}:{}/status", connect_host, session.port)
    }

    async fn terminate_external_process(pid: u32) -> Result<(), String> {
        if pid == 0 || pid == std::process::id() || !Self::is_process_alive(pid) {
            return Ok(());
        }

        #[cfg(unix)]
        {
            let rc = unsafe { libc::kill(pid as i32, libc::SIGTERM) };
            if rc != 0 {
                let error = std::io::Error::last_os_error();
                if error.raw_os_error() != Some(libc::ESRCH) {
                    return Err(format!("stop managed proxy session failed: {error}"));
                }
            }

            let deadline = tokio::time::Instant::now() + Duration::from_secs(5);
            while tokio::time::Instant::now() < deadline {
                if !Self::is_process_alive(pid) {
                    return Ok(());
                }
                tokio::time::sleep(Duration::from_millis(100)).await;
            }

            let rc = unsafe { libc::kill(pid as i32, libc::SIGKILL) };
            if rc != 0 {
                let error = std::io::Error::last_os_error();
                if error.raw_os_error() != Some(libc::ESRCH) {
                    return Err(format!("force stop managed proxy session failed: {error}"));
                }
            }

            let deadline = tokio::time::Instant::now() + Duration::from_secs(1);
            while tokio::time::Instant::now() < deadline {
                if !Self::is_process_alive(pid) {
                    return Ok(());
                }
                tokio::time::sleep(Duration::from_millis(50)).await;
            }

            return Err(format!(
                "managed proxy session did not exit after termination signal: pid {}",
                pid
            ));
        }

        #[cfg(not(unix))]
        {
            let _ = pid;
            Err("managed proxy session stop is only supported on unix in this build".to_string())
        }
    }

    fn spawn_managed_child_reaper(mut child: std::process::Child) {
        tokio::task::spawn_blocking(move || {
            let _ = child.wait();
        });
    }

    fn ensure_managed_sessions_supported() -> Result<(), String> {
        #[cfg(unix)]
        {
            Ok(())
        }

        #[cfg(not(unix))]
        {
            Err("managed proxy sessions are unsupported on non-unix platforms".to_string())
        }
    }

    fn resolve_managed_proxy_executable() -> Result<std::path::PathBuf, String> {
        if let Some(path) = std::env::var_os("CARGO_BIN_EXE_cc-switch") {
            return Ok(path.into());
        }

        let current_exe = std::env::current_exe()
            .map_err(|error| format!("resolve managed proxy executable failed: {error}"))?;

        if current_exe
            .file_stem()
            .and_then(|value| value.to_str())
            .is_some_and(|value| value.starts_with("cc-switch"))
        {
            return Ok(current_exe);
        }

        if let Some(debug_dir) = current_exe.parent().and_then(|parent| parent.parent()) {
            let candidate = debug_dir.join(format!("cc-switch{}", std::env::consts::EXE_SUFFIX));
            if candidate.exists() {
                return Ok(candidate);
            }
        }

        Ok(current_exe)
    }

    async fn sync_persisted_global_proxy_enabled(&self, enabled: bool) -> Result<(), String> {
        let mut config = self
            .db
            .get_global_proxy_config()
            .await
            .map_err(|error| format!("load global proxy config failed: {error}"))?;
        if config.proxy_enabled == enabled {
            if !enabled {
                self.db
                    .clear_auto_failover_for_supported_apps()
                    .await
                    .map_err(|error| {
                        format!("clear auto failover after proxy stop failed: {error}")
                    })?;
            }
            return Ok(());
        }

        if !enabled {
            self.db
                .clear_auto_failover_for_supported_apps()
                .await
                .map_err(|error| format!("clear auto failover after proxy stop failed: {error}"))?;
        }

        config.proxy_enabled = enabled;
        self.db
            .update_global_proxy_config(config)
            .await
            .map_err(|error| format!("update global proxy switch failed: {error}"))?;

        Ok(())
    }

    fn read_gemini_live(&self) -> Result<Value, String> {
        let env_path = get_gemini_env_path();
        if !env_path.exists() {
            return Err("Gemini .env does not exist".to_string());
        }

        let env = read_gemini_env().map_err(|error| format!("read Gemini .env failed: {error}"))?;
        Ok(env_to_json(&env))
    }

    fn write_gemini_live(&self, config: &Value) -> Result<(), String> {
        let env =
            json_to_env(config).map_err(|error| format!("build Gemini .env failed: {error}"))?;
        write_gemini_env_atomic(&env).map_err(|error| format!("write Gemini .env failed: {error}"))
    }

    fn takeover_app_from_str(app_type: &str) -> Result<AppType, String> {
        match app_type {
            "claude" => Ok(AppType::Claude),
            "codex" => Ok(AppType::Codex),
            "gemini" => Ok(AppType::Gemini),
            _ => Err(format!("proxy takeover not supported for app: {app_type}")),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::provider::ProviderMeta;
    use crate::proxy::circuit_breaker::CircuitBreakerConfig;
    use crate::test_support::{lock_test_home_and_settings, set_test_home_override};
    use serial_test::serial;
    use std::ffi::OsString;
    #[cfg(unix)]
    use std::os::unix::process::CommandExt;
    use std::path::Path;
    use std::sync::atomic::{AtomicBool, Ordering};
    use tempfile::TempDir;

    fn seed_proxy_flags_raw(
        db: &Database,
        app_type: &str,
        enabled: bool,
        auto_failover_enabled: bool,
    ) {
        let conn = db.conn.lock().expect("lock db");
        conn.execute(
            "UPDATE proxy_config
             SET enabled = ?2, auto_failover_enabled = ?3
             WHERE app_type = ?1",
            rusqlite::params![
                app_type,
                if enabled { 1 } else { 0 },
                if auto_failover_enabled { 1 } else { 0 },
            ],
        )
        .expect("seed raw proxy flags");
    }

    fn save_queue_provider(db: &Database, app_type: &str, id: &str) {
        let provider = Provider::with_id(
            id.to_string(),
            id.to_string(),
            json!({"env": {"BASE_URL": "https://example.com"}}),
            None,
        );
        db.save_provider(app_type, &provider)
            .expect("save failover provider");
        db.add_to_failover_queue(app_type, &provider.id)
            .expect("queue failover provider");
    }

    struct ManagedRuntimeEnvGuard {
        old_kind: Option<OsString>,
        old_token: Option<OsString>,
    }

    impl ManagedRuntimeEnvGuard {
        fn set(token: &str) -> Self {
            let old_kind = std::env::var_os(PROXY_RUNTIME_KIND_ENV_KEY);
            let old_token = std::env::var_os(PROXY_RUNTIME_SESSION_TOKEN_ENV_KEY);
            std::env::set_var(
                PROXY_RUNTIME_KIND_ENV_KEY,
                PersistedProxyRuntimeSessionKind::ManagedExternal.as_env_value(),
            );
            std::env::set_var(PROXY_RUNTIME_SESSION_TOKEN_ENV_KEY, token);
            Self {
                old_kind,
                old_token,
            }
        }
    }

    impl Drop for ManagedRuntimeEnvGuard {
        fn drop(&mut self) {
            match &self.old_kind {
                Some(value) => std::env::set_var(PROXY_RUNTIME_KIND_ENV_KEY, value),
                None => std::env::remove_var(PROXY_RUNTIME_KIND_ENV_KEY),
            }
            match &self.old_token {
                Some(value) => std::env::set_var(PROXY_RUNTIME_SESSION_TOKEN_ENV_KEY, value),
                None => std::env::remove_var(PROXY_RUNTIME_SESSION_TOKEN_ENV_KEY),
            }
        }
    }

    struct TestHomeEnvGuard {
        _lock: crate::test_support::TestHomeSettingsLock,
        old_home: Option<OsString>,
        old_userprofile: Option<OsString>,
        old_config_dir: Option<OsString>,
    }

    impl TestHomeEnvGuard {
        fn set(home: &Path) -> Self {
            let lock = lock_test_home_and_settings();
            let old_home = std::env::var_os("HOME");
            let old_userprofile = std::env::var_os("USERPROFILE");
            let old_config_dir = std::env::var_os("CC_SWITCH_CONFIG_DIR");
            std::env::set_var("HOME", home);
            std::env::set_var("USERPROFILE", home);
            std::env::set_var("CC_SWITCH_CONFIG_DIR", home.join(".cc-switch"));
            set_test_home_override(Some(home));
            crate::settings::reload_test_settings();
            Self {
                _lock: lock,
                old_home,
                old_userprofile,
                old_config_dir,
            }
        }
    }

    impl Drop for TestHomeEnvGuard {
        fn drop(&mut self) {
            match &self.old_home {
                Some(value) => std::env::set_var("HOME", value),
                None => std::env::remove_var("HOME"),
            }
            match &self.old_userprofile {
                Some(value) => std::env::set_var("USERPROFILE", value),
                None => std::env::remove_var("USERPROFILE"),
            }
            match &self.old_config_dir {
                Some(value) => std::env::set_var("CC_SWITCH_CONFIG_DIR", value),
                None => std::env::remove_var("CC_SWITCH_CONFIG_DIR"),
            }
            set_test_home_override(self.old_home.as_deref().map(Path::new));
            crate::settings::reload_test_settings();
        }
    }

    #[tokio::test]
    #[serial]
    async fn enable_auto_failover_for_app_switches_to_queue_head() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        let mut provider_a = Provider::with_id(
            "provider-a".to_string(),
            "Provider A".to_string(),
            json!({"env":{"ANTHROPIC_BASE_URL":"https://a.example","ANTHROPIC_AUTH_TOKEN":"a"}}),
            None,
        );
        provider_a.sort_index = Some(2);
        let mut provider_b = Provider::with_id(
            "provider-b".to_string(),
            "Provider B".to_string(),
            json!({"env":{"ANTHROPIC_BASE_URL":"https://b.example","ANTHROPIC_AUTH_TOKEN":"b"}}),
            None,
        );
        provider_b.sort_index = Some(1);
        db.save_provider("claude", &provider_a)
            .expect("save provider a");
        db.save_provider("claude", &provider_b)
            .expect("save provider b");
        db.set_current_provider("claude", &provider_a.id)
            .expect("set current provider");
        crate::settings::set_current_provider(&AppType::Claude, Some(&provider_a.id))
            .expect("set settings current provider");
        db.add_to_failover_queue("claude", &provider_b.id)
            .expect("queue provider b");
        db.add_to_failover_queue("claude", &provider_a.id)
            .expect("queue provider a");
        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        service
            .update_config(&runtime_config)
            .await
            .expect("persist runtime config");
        service
            .set_takeover_for_app("claude", true)
            .await
            .expect("enable claude takeover");

        service
            .enable_auto_failover_for_app("claude")
            .await
            .expect("enable auto failover");

        let config = db
            .get_proxy_config_for_app("claude")
            .await
            .expect("load claude proxy config");
        assert!(config.auto_failover_enabled);
        assert_eq!(
            db.get_current_provider("claude")
                .expect("load database current provider")
                .as_deref(),
            Some("provider-b")
        );
        assert_eq!(
            crate::settings::get_effective_current_provider(db.as_ref(), &AppType::Claude)
                .expect("load effective current provider")
                .as_deref(),
            Some("provider-b")
        );

        service.stop().await.expect("stop proxy runtime");
    }

    #[tokio::test]
    #[serial]
    async fn enable_auto_failover_for_app_rejects_when_proxy_is_not_routed() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        let provider = Provider::with_id(
            "queue-head".to_string(),
            "Queue Head".to_string(),
            json!({"env":{"ANTHROPIC_BASE_URL":"https://a.example","ANTHROPIC_AUTH_TOKEN":"a"}}),
            None,
        );
        db.save_provider("claude", &provider)
            .expect("save queued provider");
        db.add_to_failover_queue("claude", &provider.id)
            .expect("queue provider");

        let error = service
            .enable_auto_failover_for_app("claude")
            .await
            .expect_err("failover should require active proxy routing");

        assert!(
            error.contains("local proxy") || error.contains("proxy takeover"),
            "{error}"
        );
        let config = db
            .get_proxy_config_for_app("claude")
            .await
            .expect("load claude proxy config");
        assert!(!config.auto_failover_enabled);
    }

    #[tokio::test]
    #[serial]
    async fn disable_takeover_for_app_clears_auto_failover_without_stopping_other_takeovers() {
        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        db.set_proxy_flags_sync("claude", true, true)
            .expect("seed claude takeover and failover");
        db.set_proxy_flags_sync("codex", true, false)
            .expect("seed codex takeover");

        service
            .disable_takeover_for_app_unlocked(&AppType::Claude, true)
            .await
            .expect("disable claude takeover");

        assert_eq!(db.get_proxy_flags_sync("claude"), (false, false));
        assert_eq!(db.get_proxy_flags_sync("codex"), (true, false));
    }

    #[tokio::test]
    #[serial]
    async fn disabling_global_proxy_clears_auto_failover_and_reports_count() {
        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        save_queue_provider(&db, "claude", "claude-p1");
        save_queue_provider(&db, "codex", "codex-p1");
        db.set_proxy_flags_sync("claude", true, true)
            .expect("seed claude takeover and failover");
        db.set_proxy_flags_sync("codex", true, true)
            .expect("seed codex takeover and failover");

        let update = service
            .set_global_enabled(false)
            .await
            .expect("disable global proxy");

        assert!(!update.config.proxy_enabled);
        assert_eq!(update.cleared_auto_failover, 2);
        assert_eq!(db.get_proxy_flags_sync("claude"), (true, false));
        assert_eq!(db.get_proxy_flags_sync("codex"), (true, false));
    }

    #[tokio::test]
    #[serial]
    async fn startup_recovery_clears_orphaned_auto_failover() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        seed_proxy_flags_raw(&db, "claude", false, true);

        service
            .recover_takeovers_on_startup()
            .await
            .expect("recover takeovers");

        assert_eq!(db.get_proxy_flags_sync("claude"), (false, false));
    }

    #[tokio::test]
    #[serial]
    async fn enable_proxy_and_auto_failover_uses_queue_head_without_existing_current_provider() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        let provider = Provider::with_id(
            "queue-head".to_string(),
            "Queue Head".to_string(),
            json!({
                "env": {
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com",
                    "ANTHROPIC_AUTH_TOKEN": "token"
                }
            }),
            None,
        );
        db.save_provider("claude", &provider)
            .expect("save queued provider");
        db.add_to_failover_queue("claude", &provider.id)
            .expect("queue provider");
        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        service
            .update_config(&runtime_config)
            .await
            .expect("persist runtime config");

        service
            .enable_proxy_and_auto_failover_for_app("claude")
            .await
            .expect("enable proxy and auto failover");

        let global = db
            .get_global_proxy_config()
            .await
            .expect("load global proxy config");
        let app_config = db
            .get_proxy_config_for_app("claude")
            .await
            .expect("load claude proxy config");
        assert!(global.proxy_enabled);
        assert!(app_config.enabled);
        assert!(app_config.auto_failover_enabled);
        assert_eq!(
            crate::settings::get_effective_current_provider(db.as_ref(), &AppType::Claude)
                .expect("load effective current provider")
                .as_deref(),
            Some("queue-head")
        );

        service.stop().await.expect("stop proxy runtime");
    }

    #[tokio::test]
    #[serial]
    async fn takeover_activation_rejects_missing_current_provider_when_failover_disabled() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());

        let error = service
            .set_takeover_for_app("claude", true)
            .await
            .expect_err("takeover should require an active provider when failover is disabled");

        assert!(
            error.contains("cannot enable proxy because no active provider is selected"),
            "{error}"
        );
        assert!(
            !db.get_proxy_config_for_app("claude")
                .await
                .expect("load claude proxy config")
                .enabled,
            "failed validation should not enable takeover state"
        );
    }

    #[tokio::test]
    #[serial]
    async fn takeover_activation_allows_current_provider_when_failover_disabled() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());
        std::fs::create_dir_all(
            get_claude_settings_path()
                .parent()
                .expect("claude settings parent dir"),
        )
        .expect("create ~/.claude");
        write_json_file(
            &get_claude_settings_path(),
            &json!({
                "env": {
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com",
                    "ANTHROPIC_AUTH_TOKEN": "fresh-live-token"
                }
            }),
        )
        .expect("seed claude live config");

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        let provider = Provider::with_id(
            "claude-provider".to_string(),
            "Claude Provider".to_string(),
            json!({
                "env": {
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com",
                    "ANTHROPIC_AUTH_TOKEN": "stale-provider-token"
                }
            }),
            None,
        );
        db.save_provider("claude", &provider)
            .expect("save claude provider");
        db.set_current_provider("claude", &provider.id)
            .expect("set current claude provider");
        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        service
            .update_config(&runtime_config)
            .await
            .expect("persist runtime config");

        service
            .set_takeover_for_app("claude", true)
            .await
            .expect("takeover should allow an active provider when failover is disabled");

        assert!(
            db.get_proxy_config_for_app("claude")
                .await
                .expect("load claude proxy config")
                .enabled
        );
        service.stop().await.expect("stop proxy runtime");
    }

    #[tokio::test]
    #[serial]
    async fn takeover_activation_uses_app_preferred_port_from_settings_kv() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());
        std::fs::create_dir_all(
            get_claude_settings_path()
                .parent()
                .expect("claude settings parent dir"),
        )
        .expect("create ~/.claude");
        write_json_file(
            &get_claude_settings_path(),
            &json!({
                "env": {
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com",
                    "ANTHROPIC_AUTH_TOKEN": "fresh-live-token"
                }
            }),
        )
        .expect("seed claude live config");

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        let provider = Provider::with_id(
            "claude-provider".to_string(),
            "Claude Provider".to_string(),
            json!({
                "env": {
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com",
                    "ANTHROPIC_AUTH_TOKEN": "provider-token"
                }
            }),
            None,
        );
        db.save_provider("claude", &provider)
            .expect("save claude provider");
        db.set_current_provider("claude", &provider.id)
            .expect("set current claude provider");

        let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0))
            .await
            .expect("reserve free local port");
        let preferred_port = listener
            .local_addr()
            .expect("read reserved listener address")
            .port();
        drop(listener);
        db.set_app_proxy_preferred_port("claude", preferred_port)
            .expect("persist claude preferred proxy port");

        service
            .set_takeover_for_app("claude", true)
            .await
            .expect("enable claude takeover");

        let status = service.get_status().await;
        assert_eq!(status.port, preferred_port);
        let live: Value =
            read_json_file(&get_claude_settings_path()).expect("read claude live config");
        let expected_proxy_url = format!("http://127.0.0.1:{preferred_port}");
        assert_eq!(
            live.pointer("/env/ANTHROPIC_BASE_URL")
                .and_then(Value::as_str),
            Some(expected_proxy_url.as_str())
        );

        service.stop().await.expect("stop proxy runtime");
    }

    #[tokio::test]
    #[serial]
    async fn takeover_activation_rejects_empty_queue_when_failover_enabled() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        let provider = Provider::with_id(
            "claude-provider".to_string(),
            "Claude Provider".to_string(),
            json!({
                "env": {
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com",
                    "ANTHROPIC_AUTH_TOKEN": "token"
                }
            }),
            None,
        );
        db.save_provider("claude", &provider)
            .expect("save claude provider");
        db.set_current_provider("claude", &provider.id)
            .expect("set current claude provider");
        let app_proxy = db
            .get_proxy_config_for_app("claude")
            .await
            .expect("load claude proxy config");
        seed_proxy_flags_raw(&db, &app_proxy.app_type, app_proxy.enabled, true);

        let error = service
            .set_takeover_for_app("claude", true)
            .await
            .expect_err("takeover should require a non-empty failover queue");

        assert!(
            error.contains(
                "cannot enable proxy because automatic failover is enabled and the failover queue is empty"
            ),
            "{error}"
        );
        assert!(
            !db.get_proxy_config_for_app("claude")
                .await
                .expect("load claude proxy config")
                .enabled,
            "failed validation should not enable takeover state"
        );
    }

    #[tokio::test]
    #[serial]
    async fn takeover_activation_allows_non_empty_queue_when_failover_enabled() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());
        std::fs::create_dir_all(
            get_claude_settings_path()
                .parent()
                .expect("claude settings parent dir"),
        )
        .expect("create ~/.claude");
        write_json_file(
            &get_claude_settings_path(),
            &json!({
                "env": {
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com",
                    "ANTHROPIC_AUTH_TOKEN": "fresh-live-token"
                }
            }),
        )
        .expect("seed claude live config");

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        let provider = Provider::with_id(
            "claude-provider".to_string(),
            "Claude Provider".to_string(),
            json!({
                "env": {
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com",
                    "ANTHROPIC_AUTH_TOKEN": "stale-provider-token"
                }
            }),
            None,
        );
        db.save_provider("claude", &provider)
            .expect("save claude provider");
        db.add_to_failover_queue("claude", &provider.id)
            .expect("queue claude provider");
        let app_proxy = db
            .get_proxy_config_for_app("claude")
            .await
            .expect("load claude proxy config");
        seed_proxy_flags_raw(&db, &app_proxy.app_type, app_proxy.enabled, true);
        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        service
            .update_config(&runtime_config)
            .await
            .expect("persist runtime config");

        service
            .set_takeover_for_app("claude", true)
            .await
            .expect("takeover should allow a non-empty failover queue");

        assert!(
            db.get_proxy_config_for_app("claude")
                .await
                .expect("load claude proxy config")
                .enabled
        );
        service.stop().await.expect("stop proxy runtime");
    }

    #[tokio::test]
    #[serial]
    async fn recover_takeovers_on_startup_cleans_claude_placeholder_only_residue_without_backup() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());
        std::fs::create_dir_all(
            get_claude_settings_path()
                .parent()
                .expect("claude settings parent dir"),
        )
        .expect("create ~/.claude");

        write_json_file(
            &get_claude_settings_path(),
            &json!({
                "env": {
                    "ANTHROPIC_AUTH_TOKEN": PROXY_TOKEN_PLACEHOLDER,
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com"
                }
            }),
        )
        .expect("seed placeholder-only claude live config");

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db);

        service
            .recover_takeovers_on_startup()
            .await
            .expect("startup recovery should clean placeholder-only residue");

        let live: Value =
            read_json_file(&get_claude_settings_path()).expect("read claude live config");
        let env = live
            .get("env")
            .and_then(Value::as_object)
            .expect("claude env should be object");
        assert!(
            !env.contains_key("ANTHROPIC_AUTH_TOKEN"),
            "startup recovery should remove stale proxy placeholder tokens even without loopback base_url"
        );
        assert_eq!(
            env.get("ANTHROPIC_BASE_URL").and_then(Value::as_str),
            Some("https://api.anthropic.com"),
            "startup recovery should keep a non-proxy base_url when only clearing stale placeholder residue"
        );
    }

    #[tokio::test]
    #[serial]
    async fn enabling_claude_takeover_syncs_live_token_back_to_current_provider() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());
        std::fs::create_dir_all(
            get_claude_settings_path()
                .parent()
                .expect("claude settings parent dir"),
        )
        .expect("create ~/.claude");

        write_json_file(
            &get_claude_settings_path(),
            &json!({
                "env": {
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com",
                    "ANTHROPIC_AUTH_TOKEN": "fresh-live-token"
                }
            }),
        )
        .expect("seed claude live config");

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());

        let provider = Provider::with_id(
            "claude-provider".to_string(),
            "Claude Provider".to_string(),
            json!({
                "env": {
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com",
                    "ANTHROPIC_AUTH_TOKEN": "stale-provider-token"
                }
            }),
            None,
        );
        db.save_provider("claude", &provider)
            .expect("save claude provider");
        db.set_current_provider("claude", &provider.id)
            .expect("set current claude provider");

        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        service
            .update_config(&runtime_config)
            .await
            .expect("persist runtime config");

        service
            .set_takeover_for_app("claude", true)
            .await
            .expect("enable claude takeover");
        service.stop().await.expect("stop proxy runtime");

        let updated = db
            .get_provider_by_id("claude-provider", "claude")
            .expect("read claude provider")
            .expect("claude provider exists");
        let env = updated
            .settings_config
            .get("env")
            .and_then(Value::as_object)
            .expect("provider env should exist");

        assert_eq!(
            env.get("ANTHROPIC_AUTH_TOKEN").and_then(Value::as_str),
            Some("fresh-live-token"),
            "enabling takeover should sync the live Claude token back to the current provider before takeover rewrites it"
        );
        assert!(
            !env.contains_key("ANTHROPIC_API_KEY"),
            "sync should not introduce ANTHROPIC_API_KEY when the provider only used ANTHROPIC_AUTH_TOKEN"
        );
    }

    #[tokio::test]
    #[serial]
    async fn enabling_codex_takeover_syncs_live_token_back_to_current_provider() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());
        std::fs::create_dir_all(
            get_codex_auth_path()
                .parent()
                .expect("codex auth parent dir"),
        )
        .expect("create ~/.codex");

        write_json_file(
            &get_codex_auth_path(),
            &json!({
                "OPENAI_API_KEY": "fresh-live-token"
            }),
        )
        .expect("seed codex auth.json");
        write_text_file(
            &get_codex_config_path(),
            r#"model_provider = "default"

[model_providers.default]
base_url = "https://api.openai.com/v1"
"#,
        )
        .expect("seed codex config.toml");

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());

        let provider = Provider::with_id(
            "codex-provider".to_string(),
            "Codex Provider".to_string(),
            json!({
                "auth": {
                    "OPENAI_API_KEY": "stale-provider-token"
                },
                "config": r#"model_provider = "default"

[model_providers.default]
base_url = "https://api.openai.com/v1"
"#
            }),
            None,
        );
        db.save_provider("codex", &provider)
            .expect("save codex provider");
        db.set_current_provider("codex", &provider.id)
            .expect("set current codex provider");

        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        service
            .update_config(&runtime_config)
            .await
            .expect("persist runtime config");

        service
            .set_takeover_for_app("codex", true)
            .await
            .expect("enable codex takeover");
        service.stop().await.expect("stop proxy runtime");

        let updated = db
            .get_provider_by_id("codex-provider", "codex")
            .expect("read codex provider")
            .expect("codex provider exists");
        let auth = updated
            .settings_config
            .get("auth")
            .and_then(Value::as_object)
            .expect("provider auth should exist");

        assert_eq!(
            auth.get("OPENAI_API_KEY").and_then(Value::as_str),
            Some("fresh-live-token"),
            "enabling takeover should sync the live Codex token back to the current provider before takeover rewrites it"
        );
    }

    #[tokio::test]
    #[serial]
    async fn enabling_codex_takeover_syncs_live_token_to_effective_current_provider() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());
        std::fs::create_dir_all(
            get_codex_auth_path()
                .parent()
                .expect("codex auth parent dir"),
        )
        .expect("create ~/.codex");

        write_json_file(
            &get_codex_auth_path(),
            &json!({
                "OPENAI_API_KEY": "fresh-live-token"
            }),
        )
        .expect("seed codex auth.json");
        write_text_file(
            &get_codex_config_path(),
            r#"model_provider = "default"

[model_providers.default]
base_url = "https://api.openai.com/v1"
"#,
        )
        .expect("seed codex config.toml");

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());

        let db_current = Provider::with_id(
            "codex-db-current".to_string(),
            "Codex DB Current".to_string(),
            json!({
                "auth": {
                    "OPENAI_API_KEY": "db-current-token"
                },
                "config": r#"model_provider = "default"

[model_providers.default]
base_url = "https://api.openai.com/v1"
"#
            }),
            None,
        );
        let local_current = Provider::with_id(
            "codex-local-current".to_string(),
            "Codex Local Current".to_string(),
            json!({
                "auth": {
                    "OPENAI_API_KEY": "local-current-token"
                },
                "config": r#"model_provider = "default"

[model_providers.default]
base_url = "https://api.openai.com/v1"
"#
            }),
            None,
        );
        db.save_provider("codex", &db_current)
            .expect("save db-current codex provider");
        db.save_provider("codex", &local_current)
            .expect("save local-current codex provider");
        db.set_current_provider("codex", &db_current.id)
            .expect("set db current codex provider");
        crate::settings::set_current_provider(&AppType::Codex, Some(&local_current.id))
            .expect("set local current codex provider");

        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        service
            .update_config(&runtime_config)
            .await
            .expect("persist runtime config");

        service
            .set_takeover_for_app("codex", true)
            .await
            .expect("enable codex takeover");
        service.stop().await.expect("stop proxy runtime");

        let updated_local = db
            .get_provider_by_id("codex-local-current", "codex")
            .expect("read local-current codex provider")
            .expect("local-current codex provider exists");
        let local_auth = updated_local
            .settings_config
            .get("auth")
            .and_then(Value::as_object)
            .expect("local-current provider auth should exist");
        assert_eq!(
            local_auth.get("OPENAI_API_KEY").and_then(Value::as_str),
            Some("fresh-live-token"),
            "takeover sync should follow the effective current provider from local settings"
        );

        let unchanged_db = db
            .get_provider_by_id("codex-db-current", "codex")
            .expect("read db-current codex provider")
            .expect("db-current codex provider exists");
        let db_auth = unchanged_db
            .settings_config
            .get("auth")
            .and_then(Value::as_object)
            .expect("db-current provider auth should exist");
        assert_eq!(
            db_auth.get("OPENAI_API_KEY").and_then(Value::as_str),
            Some("db-current-token"),
            "DB current provider should remain unchanged when local settings override it"
        );
    }

    #[tokio::test]
    #[serial]
    async fn enabling_gemini_takeover_syncs_live_token_to_effective_current_provider() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());

        service
            .write_gemini_live(&json!({
                "env": {
                    "GEMINI_API_KEY": "fresh-live-token"
                }
            }))
            .expect("seed gemini env");

        let db_current = Provider::with_id(
            "gemini-db-current".to_string(),
            "Gemini DB Current".to_string(),
            json!({
                "env": {
                    "GEMINI_API_KEY": "db-current-token"
                }
            }),
            None,
        );
        let local_current = Provider::with_id(
            "gemini-local-current".to_string(),
            "Gemini Local Current".to_string(),
            json!({
                "env": {
                    "GEMINI_API_KEY": "local-current-token"
                }
            }),
            None,
        );
        db.save_provider("gemini", &db_current)
            .expect("save db-current gemini provider");
        db.save_provider("gemini", &local_current)
            .expect("save local-current gemini provider");
        db.set_current_provider("gemini", &db_current.id)
            .expect("set db current gemini provider");
        crate::settings::set_current_provider(&AppType::Gemini, Some(&local_current.id))
            .expect("set local current gemini provider");

        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        service
            .update_config(&runtime_config)
            .await
            .expect("persist runtime config");

        service
            .set_takeover_for_app("gemini", true)
            .await
            .expect("enable gemini takeover");
        service.stop().await.expect("stop proxy runtime");

        let updated_local = db
            .get_provider_by_id("gemini-local-current", "gemini")
            .expect("read local-current gemini provider")
            .expect("local-current gemini provider exists");
        let local_env = updated_local
            .settings_config
            .get("env")
            .and_then(Value::as_object)
            .expect("local-current provider env should exist");
        assert_eq!(
            local_env.get("GEMINI_API_KEY").and_then(Value::as_str),
            Some("fresh-live-token"),
            "takeover sync should follow the effective current provider from local settings"
        );

        let unchanged_db = db
            .get_provider_by_id("gemini-db-current", "gemini")
            .expect("read db-current gemini provider")
            .expect("db-current gemini provider exists");
        let db_env = unchanged_db
            .settings_config
            .get("env")
            .and_then(Value::as_object)
            .expect("db-current provider env should exist");
        assert_eq!(
            db_env.get("GEMINI_API_KEY").and_then(Value::as_str),
            Some("db-current-token"),
            "DB current provider should remain unchanged when local settings override it"
        );
    }

    #[tokio::test]
    #[serial]
    async fn foreground_runtime_start_and_stop_syncs_global_proxy_switch() {
        let db = Arc::new(Database::memory().expect("create database"));
        seed_proxy_flags_raw(&db, "claude", false, true);
        let service = ProxyService::new(db.clone());

        assert!(
            !service
                .get_global_config()
                .await
                .expect("read initial global proxy config")
                .proxy_enabled,
            "precondition: global proxy switch should start disabled"
        );

        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        service
            .start_with_runtime_config(runtime_config)
            .await
            .expect("start foreground proxy runtime");

        assert!(
            service
                .get_global_config()
                .await
                .expect("read global proxy config after start")
                .proxy_enabled,
            "starting the foreground proxy runtime should enable the persisted global proxy switch"
        );

        service.stop().await.expect("stop foreground proxy runtime");

        assert!(
            !service
                .get_global_config()
                .await
                .expect("read global proxy config after stop")
                .proxy_enabled,
            "stopping the foreground proxy runtime should disable the persisted global proxy switch"
        );
        assert_eq!(
            db.get_proxy_flags_sync("claude"),
            (false, false),
            "stopping the proxy runtime should also clear app auto failover"
        );
    }

    #[tokio::test]
    #[serial]
    async fn managed_external_runtime_does_not_publish_session_before_ready_signal() {
        let _env = ManagedRuntimeEnvGuard::set("test-managed-session-token");
        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());

        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        service
            .start_with_runtime_config(runtime_config)
            .await
            .expect("start proxy runtime");

        assert!(
            db.get_setting(PROXY_RUNTIME_SESSION_KEY)
                .expect("read runtime session")
                .is_none(),
            "managed external runtime should wait to publish its session until takeover is finished"
        );

        service.stop().await.expect("stop proxy runtime");
    }

    #[test]
    fn loads_per_app_managed_runtime_sessions() {
        let db = Arc::new(Database::memory().expect("create database"));
        db.set_setting(
            PROXY_RUNTIME_SESSION_KEY,
            &json!({
                "workers": {
                    "claude": {
                        "pid": std::process::id(),
                        "address": "127.0.0.1",
                        "port": 15721,
                        "started_at": chrono::Utc::now().to_rfc3339(),
                        "kind": "managed_external",
                        "session_token": "claude-token"
                    },
                    "codex": {
                        "pid": std::process::id(),
                        "address": "127.0.0.1",
                        "port": 15722,
                        "started_at": chrono::Utc::now().to_rfc3339(),
                        "kind": "managed_external",
                        "session_token": "codex-token"
                    }
                }
            })
            .to_string(),
        )
        .expect("write runtime sessions");
        let service = ProxyService::new(db);

        let claude = service
            .load_persisted_runtime_session_for_app(&AppType::Claude)
            .expect("claude session");
        let codex = service
            .load_persisted_runtime_session_for_app(&AppType::Codex)
            .expect("codex session");

        assert_eq!(claude.app_type.as_deref(), Some("claude"));
        assert_eq!(claude.port, 15721);
        assert_eq!(codex.app_type.as_deref(), Some("codex"));
        assert_eq!(codex.port, 15722);
    }

    #[test]
    fn loads_legacy_managed_runtime_session_only_from_single_session_shape() {
        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        db.set_setting(
            PROXY_RUNTIME_SESSION_KEY,
            &serde_json::to_string(&PersistedProxyRuntimeSession {
                pid: 4242,
                address: "127.0.0.1".to_string(),
                port: 15721,
                started_at: "2026-03-10T00:00:00Z".to_string(),
                kind: PersistedProxyRuntimeSessionKind::ManagedExternal,
                session_token: Some("legacy-token".to_string()),
                app_type: None,
            })
            .expect("serialize legacy runtime session"),
        )
        .expect("write legacy runtime session");

        assert!(
            service.load_legacy_persisted_runtime_session().is_some(),
            "single-session managed runtime marker should be recognized as legacy"
        );

        db.set_setting(
            PROXY_RUNTIME_SESSION_KEY,
            &json!({
                "workers": {
                    "claude": {
                        "pid": 4242,
                        "address": "127.0.0.1",
                        "port": 15721,
                        "started_at": "2026-03-10T00:00:00Z",
                        "kind": "managed_external",
                        "session_token": "daemon-token"
                    }
                }
            })
            .to_string(),
        )
        .expect("write daemon runtime sessions");

        assert!(
            service.load_legacy_persisted_runtime_session().is_none(),
            "daemon workers map must not be treated as legacy upgrade residue"
        );
    }

    #[cfg(unix)]
    #[tokio::test]
    async fn legacy_managed_runtime_cleanup_terminates_owned_unreachable_session() {
        let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0))
            .await
            .expect("bind unused legacy proxy status port");
        let port = listener
            .local_addr()
            .expect("read unused legacy proxy status port")
            .port();
        drop(listener);

        let mut command = std::process::Command::new("sleep");
        command.arg("30");
        unsafe {
            command.pre_exec(|| {
                if libc::setsid() == -1 {
                    return Err(std::io::Error::last_os_error());
                }
                Ok(())
            });
        }
        let mut child = command.spawn().expect("spawn detached legacy worker");
        let pid = child.id();
        let reaper = std::thread::spawn(move || child.wait());

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        db.set_setting(
            PROXY_RUNTIME_SESSION_KEY,
            &serde_json::to_string(&PersistedProxyRuntimeSession {
                pid,
                address: "127.0.0.1".to_string(),
                port,
                started_at: "2026-03-10T00:00:00Z".to_string(),
                kind: PersistedProxyRuntimeSessionKind::ManagedExternal,
                session_token: Some("legacy-token".to_string()),
                app_type: None,
            })
            .expect("serialize legacy runtime session"),
        )
        .expect("write legacy runtime session");

        service
            .cleanup_legacy_managed_runtime_session_before_daemon_start()
            .await
            .expect("cleanup legacy owned worker");

        let stopped = !ProxyService::is_process_alive(pid);
        if !stopped {
            let _ = unsafe { libc::kill(pid as i32, libc::SIGKILL) };
        }
        let _ = reaper.join();

        assert!(
            stopped,
            "legacy owned managed worker should be stopped even when /status is unreachable"
        );
        assert!(
            db.get_setting(PROXY_RUNTIME_SESSION_KEY)
                .expect("read runtime session after cleanup")
                .is_none(),
            "legacy runtime marker should be cleared after cleanup"
        );
    }

    #[tokio::test]
    #[serial]
    async fn daemon_known_worker_takeover_does_not_bind_proxy_port_again() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());
        std::fs::create_dir_all(
            get_claude_settings_path()
                .parent()
                .expect("claude settings parent dir"),
        )
        .expect("create ~/.claude");

        write_json_file(
            &get_claude_settings_path(),
            &json!({
                "env": {
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com",
                    "ANTHROPIC_AUTH_TOKEN": "live-token"
                }
            }),
        )
        .expect("seed claude live config");

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        let provider = Provider::with_id(
            "claude-provider".to_string(),
            "Claude Provider".to_string(),
            json!({
                "env": {
                    "ANTHROPIC_BASE_URL": "https://api.anthropic.com",
                    "ANTHROPIC_AUTH_TOKEN": "provider-token"
                }
            }),
            None,
        );
        db.save_provider("claude", &provider)
            .expect("save claude provider");
        db.set_current_provider("claude", &provider.id)
            .expect("set current claude provider");

        let occupied = tokio::net::TcpListener::bind(("127.0.0.1", 0))
            .await
            .expect("reserve daemon worker port");
        let occupied_port = occupied
            .local_addr()
            .expect("read occupied listener address")
            .port();
        db.set_app_proxy_preferred_port("claude", occupied_port)
            .expect("persist occupied preferred port");

        db.set_setting(
            PROXY_RUNTIME_SESSION_KEY,
            &json!({
                "workers": {
                    "claude": {
                        "pid": std::process::id(),
                        "address": "127.0.0.1",
                        "port": occupied_port,
                        "started_at": chrono::Utc::now().to_rfc3339(),
                        "kind": "managed_external",
                        "session_token": "daemon-token"
                    }
                }
            })
            .to_string(),
        )
        .expect("write daemon worker marker");

        service
            .enable_takeover_for_daemon_worker("claude")
            .await
            .expect("daemon-owned worker should skip a second bind attempt");

        let live: Value =
            read_json_file(&get_claude_settings_path()).expect("read claude live config");
        let expected_proxy_url = format!("http://127.0.0.1:{occupied_port}");
        assert_eq!(
            live.pointer("/env/ANTHROPIC_BASE_URL")
                .and_then(Value::as_str),
            Some(expected_proxy_url.as_str())
        );

        drop(occupied);
    }

    #[tokio::test]
    #[serial]
    async fn managed_external_runtime_publishes_session_when_ready_signal_is_sent() {
        let _env = ManagedRuntimeEnvGuard::set("test-managed-session-token");
        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());

        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        let info = service
            .start_with_runtime_config(runtime_config)
            .await
            .expect("start proxy runtime");

        service
            .publish_runtime_session_if_needed(&info)
            .expect("publish managed runtime session");

        let session: PersistedProxyRuntimeSession = serde_json::from_str(
            &db.get_setting(PROXY_RUNTIME_SESSION_KEY)
                .expect("read runtime session")
                .expect("persisted runtime session"),
        )
        .expect("parse runtime session");
        assert!(session.kind.is_managed_external());
        assert_eq!(
            session.session_token.as_deref(),
            Some("test-managed-session-token")
        );

        service.stop().await.expect("stop proxy runtime");
    }

    #[tokio::test]
    #[serial]
    async fn takeover_mutation_waits_for_shared_restore_guard() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());
        std::fs::create_dir_all(
            get_claude_settings_path()
                .parent()
                .expect("claude settings parent dir"),
        )
        .expect("create claude settings dir");
        write_json_file(
            &get_claude_settings_path(),
            &json!({
                "env": {
                    "ANTHROPIC_API_KEY": "live-key"
                }
            }),
        )
        .expect("seed claude live config");

        let db = Arc::new(Database::init().expect("create database"));
        let provider = Provider::with_id(
            "claude-provider".to_string(),
            "Claude Provider".to_string(),
            json!({
                "env": {
                    "ANTHROPIC_API_KEY": "db-key"
                }
            }),
            Some("claude".to_string()),
        );
        db.save_provider("claude", &provider)
            .expect("save claude provider");
        db.set_current_provider("claude", &provider.id)
            .expect("set current claude provider");

        let service = ProxyService::new(db.clone());
        let mut config = service.get_config().await.expect("read proxy config");
        config.listen_port = 0;
        service
            .update_config(&config)
            .await
            .expect("update proxy config");

        let guard = crate::services::state_coordination::acquire_restore_mutation_guard()
            .await
            .expect("acquire shared restore guard");
        let completed = Arc::new(AtomicBool::new(false));
        let completed_bg = Arc::clone(&completed);
        let service_bg = service.clone();
        let handle = tokio::spawn(async move {
            service_bg
                .set_takeover_for_app("claude", true)
                .await
                .expect("enable takeover after guard release");
            completed_bg.store(true, Ordering::SeqCst);
        });

        tokio::time::sleep(Duration::from_millis(150)).await;
        assert!(
            !completed.load(Ordering::SeqCst),
            "takeover mutation should wait behind the shared restore guard"
        );
        assert!(
            !handle.is_finished(),
            "spawned takeover task should still be blocked on the shared guard"
        );

        drop(guard);

        tokio::time::timeout(Duration::from_secs(5), handle)
            .await
            .expect("takeover task should complete after guard release")
            .expect("takeover task should succeed");
        assert!(
            completed.load(Ordering::SeqCst),
            "takeover mutation should complete once the shared guard is released"
        );

        service
            .set_takeover_for_app("claude", false)
            .await
            .expect("disable takeover for cleanup");
    }

    #[tokio::test]
    #[serial]
    async fn proxy_config_update_waits_for_shared_restore_guard() {
        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());

        let initial = service.get_config().await.expect("read initial config");
        let mut updated = initial.clone();
        updated.listen_port = if initial.listen_port == 15721 {
            15722
        } else {
            initial.listen_port.saturating_add(1)
        };
        let expected_port = updated.listen_port;

        let guard = crate::services::state_coordination::acquire_restore_mutation_guard()
            .await
            .expect("acquire shared restore guard");
        let completed = Arc::new(AtomicBool::new(false));
        let completed_bg = Arc::clone(&completed);
        let service_bg = service.clone();
        let handle = tokio::spawn(async move {
            service_bg
                .update_config(&updated)
                .await
                .expect("update proxy config after guard release");
            completed_bg.store(true, Ordering::SeqCst);
        });

        tokio::time::sleep(Duration::from_millis(150)).await;
        assert!(
            !completed.load(Ordering::SeqCst),
            "proxy config update should wait behind the shared restore guard"
        );
        assert!(
            !handle.is_finished(),
            "spawned config-update task should still be blocked on the shared guard"
        );

        drop(guard);

        tokio::time::timeout(Duration::from_secs(5), handle)
            .await
            .expect("config update task should complete after guard release")
            .expect("config update task should succeed");
        assert!(
            completed.load(Ordering::SeqCst),
            "proxy config update should complete once the shared guard is released"
        );

        assert_eq!(
            service
                .get_config()
                .await
                .expect("read config after guard release")
                .listen_port,
            expected_port
        );
    }

    #[tokio::test]
    #[serial]
    async fn managed_session_ready_info_accepts_persisted_session_without_status_probe() {
        let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0))
            .await
            .expect("bind unused proxy status port");
        let port = listener
            .local_addr()
            .expect("read unused proxy status port")
            .port();
        drop(listener);

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());

        db.set_setting(
            PROXY_RUNTIME_SESSION_KEY,
            &serde_json::to_string(&PersistedProxyRuntimeSession {
                pid: 4242,
                address: "127.0.0.1".to_string(),
                port,
                started_at: "2026-03-10T00:00:00Z".to_string(),
                kind: PersistedProxyRuntimeSessionKind::ManagedExternal,
                session_token: Some("expected-session-token".to_string()),
                app_type: Some("claude".to_string()),
            })
            .expect("serialize runtime session"),
        )
        .expect("persist runtime session");

        let info = service
            .managed_session_ready_info(4242, "expected-session-token")
            .await
            .expect("persisted managed runtime marker should be treated as ready");

        assert_eq!(info.address, "127.0.0.1");
        assert_eq!(info.port, port);
        assert_eq!(info.started_at, "2026-03-10T00:00:00Z");
    }

    #[tokio::test]
    async fn managed_session_ready_info_prefers_matching_status_snapshot_when_available() {
        let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0))
            .await
            .expect("bind fake proxy status listener");
        let port = listener
            .local_addr()
            .expect("read fake proxy listener addr")
            .port();
        let status = serde_json::json!({
            "running": true,
            "address": "127.0.0.1",
            "port": port,
            "active_connections": 0,
            "total_requests": 0,
            "success_requests": 0,
            "failed_requests": 0,
            "success_rate": 0.0,
            "uptime_seconds": 12,
            "current_provider": null,
            "current_provider_id": null,
            "last_request_at": null,
            "last_error": null,
            "failover_count": 0,
            "managed_session_token": "expected-session-token"
        });

        let server = tokio::spawn(async move {
            let (mut socket, _) = listener.accept().await.expect("accept status request");
            let response = format!(
                "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{}",
                status.to_string().len(),
                status
            );
            use tokio::io::AsyncWriteExt;
            socket
                .write_all(response.as_bytes())
                .await
                .expect("write fake status response");
        });

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        db.set_setting(
            PROXY_RUNTIME_SESSION_KEY,
            &serde_json::to_string(&PersistedProxyRuntimeSession {
                pid: 4242,
                address: "127.0.0.1".to_string(),
                port,
                started_at: "2026-03-10T00:00:00Z".to_string(),
                kind: PersistedProxyRuntimeSessionKind::ManagedExternal,
                session_token: Some("expected-session-token".to_string()),
                app_type: Some("claude".to_string()),
            })
            .expect("serialize runtime session"),
        )
        .expect("persist runtime session");

        let info = service
            .managed_session_ready_info(4242, "expected-session-token")
            .await
            .expect("matching external status should make the session ready");

        assert_eq!(info.address, "127.0.0.1");
        assert_eq!(info.port, port);

        server.await.expect("fake status server should finish");
    }

    #[tokio::test]
    async fn managed_session_ready_info_rejects_mismatched_status_snapshot() {
        let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0))
            .await
            .expect("bind fake proxy status listener");
        let port = listener
            .local_addr()
            .expect("read fake proxy listener addr")
            .port();
        let status = serde_json::json!({
            "running": true,
            "address": "127.0.0.1",
            "port": port,
            "active_connections": 0,
            "total_requests": 0,
            "success_requests": 0,
            "failed_requests": 0,
            "success_rate": 0.0,
            "uptime_seconds": 12,
            "current_provider": null,
            "current_provider_id": null,
            "last_request_at": null,
            "last_error": null,
            "failover_count": 0,
            "managed_session_token": "other-session-token"
        });

        let server = tokio::spawn(async move {
            let (mut socket, _) = listener.accept().await.expect("accept status request");
            let response = format!(
                "HTTP/1.1 200 OK\r\ncontent-type: application/json\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{}",
                status.to_string().len(),
                status
            );
            use tokio::io::AsyncWriteExt;
            socket
                .write_all(response.as_bytes())
                .await
                .expect("write fake status response");
        });

        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());
        db.set_setting(
            PROXY_RUNTIME_SESSION_KEY,
            &serde_json::to_string(&PersistedProxyRuntimeSession {
                pid: 4242,
                address: "127.0.0.1".to_string(),
                port,
                started_at: "2026-03-10T00:00:00Z".to_string(),
                kind: PersistedProxyRuntimeSessionKind::ManagedExternal,
                session_token: Some("expected-session-token".to_string()),
                app_type: Some("claude".to_string()),
            })
            .expect("serialize runtime session"),
        )
        .expect("persist runtime session");

        assert!(
            service
                .managed_session_ready_info(4242, "expected-session-token")
                .await
                .is_none(),
            "startup should keep waiting when /status reports a different managed session token"
        );

        server.await.expect("fake status server should finish");
    }

    #[tokio::test]
    #[serial]
    async fn hot_updating_running_breaker_configs_refreshes_existing_breakers() {
        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());

        let provider = Provider::with_id(
            "p1".to_string(),
            "Provider One".to_string(),
            json!({}),
            None,
        );
        db.save_provider("claude", &provider)
            .expect("save provider");

        let mut app_proxy = db
            .get_proxy_config_for_app("claude")
            .await
            .expect("load app proxy config");
        app_proxy.circuit_failure_threshold = 1;
        app_proxy.circuit_timeout_seconds = 3600;
        db.update_proxy_config_for_app(app_proxy)
            .await
            .expect("persist initial breaker config");

        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        service
            .start_with_runtime_config(runtime_config)
            .await
            .expect("start proxy");

        let router = {
            let server_guard = service.runtime.server.read().await;
            server_guard
                .as_ref()
                .expect("running server")
                .provider_router()
        };

        router
            .record_result("p1", "claude", false, false, Some("fail".to_string()))
            .await
            .expect("open breaker");
        assert!(!router.allow_provider_request("p1", "claude").await.allowed);

        let updated = CircuitBreakerConfig {
            failure_threshold: 1,
            success_threshold: 2,
            timeout_seconds: 0,
            error_rate_threshold: 1.0,
            min_requests: u32::MAX,
        };
        db.update_circuit_breaker_config(&updated)
            .await
            .expect("persist updated breaker config");
        service
            .update_circuit_breaker_configs(updated)
            .await
            .expect("hot update running breaker config");

        let permit = router.allow_provider_request("p1", "claude").await;
        assert!(permit.allowed);
        assert!(permit.used_half_open_permit);

        service.stop().await.expect("stop proxy");
    }

    #[tokio::test]
    #[serial]
    async fn resetting_running_provider_breaker_clears_existing_breaker_state() {
        let db = Arc::new(Database::memory().expect("create database"));
        let service = ProxyService::new(db.clone());

        let provider = Provider::with_id(
            "p1".to_string(),
            "Provider One".to_string(),
            json!({}),
            None,
        );
        db.save_provider("claude", &provider)
            .expect("save provider");

        let mut app_proxy = db
            .get_proxy_config_for_app("claude")
            .await
            .expect("load app proxy config");
        app_proxy.circuit_failure_threshold = 1;
        app_proxy.circuit_timeout_seconds = 3600;
        db.update_proxy_config_for_app(app_proxy)
            .await
            .expect("persist breaker config");

        let mut runtime_config = service.get_config().await.expect("get proxy config");
        runtime_config.listen_port = 0;
        service
            .start_with_runtime_config(runtime_config)
            .await
            .expect("start proxy");

        let router = {
            let server_guard = service.runtime.server.read().await;
            server_guard
                .as_ref()
                .expect("running server")
                .provider_router()
        };

        router
            .record_result("p1", "claude", false, false, Some("fail".to_string()))
            .await
            .expect("open breaker");
        assert!(!router.allow_provider_request("p1", "claude").await.allowed);

        service
            .reset_provider_circuit_breaker("p1", "claude")
            .await
            .expect("reset running breaker");

        let permit = router.allow_provider_request("p1", "claude").await;
        assert!(permit.allowed);
        assert!(!permit.used_half_open_permit);

        service.stop().await.expect("stop proxy");
    }

    #[tokio::test]
    #[serial]
    async fn update_live_backup_from_provider_preserves_codex_mcp_servers() {
        let db = Arc::new(Database::memory().expect("init db"));
        let service = ProxyService::new(db.clone());

        db.save_live_backup(
            "codex",
            &serde_json::to_string(&json!({
                "auth": {
                    "OPENAI_API_KEY": "old-token"
                },
                "config": r#"model_provider = "any"
model = "gpt-4"

[model_providers.any]
base_url = "https://old.example/v1"

[mcp_servers.echo]
command = "npx"
args = ["echo-server"]
"#
            }))
            .expect("serialize seed backup"),
        )
        .await
        .expect("seed live backup");

        let provider = Provider::with_id(
            "p2".to_string(),
            "P2".to_string(),
            json!({
                "auth": {
                    "OPENAI_API_KEY": "new-token"
                },
                "config": r#"model_provider = "any"
model = "gpt-5"

[model_providers.any]
base_url = "https://new.example/v1"
"#
            }),
            None,
        );

        service
            .update_live_backup_from_provider("codex", &provider)
            .await
            .expect("update live backup");

        let backup = db
            .get_live_backup("codex")
            .await
            .expect("get live backup")
            .expect("backup exists");
        let stored: Value =
            serde_json::from_str(&backup.original_config).expect("parse backup json");
        let config = stored
            .get("config")
            .and_then(|v| v.as_str())
            .expect("config string");

        assert!(
            config.contains("[mcp_servers.echo]"),
            "existing Codex MCP section should survive proxy hot-switch backup update"
        );
        assert!(
            config.contains("https://new.example/v1"),
            "provider-specific base_url should still update to the new provider"
        );
    }

    #[tokio::test]
    #[serial]
    async fn hot_switch_codex_provider_keeps_model_provider_stable_in_backup_and_restore() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let db = Arc::new(Database::memory().expect("init db"));
        let service = ProxyService::new(db.clone());

        let provider_a = Provider::with_id(
            "a".to_string(),
            "RightCode".to_string(),
            json!({
                "auth": {
                    "OPENAI_API_KEY": "rightcode-key"
                },
                "config": r#"model_provider = "rightcode"
model = "gpt-5.4"

[model_providers.rightcode]
name = "RightCode"
base_url = "https://rightcode.example/v1"
wire_api = "responses"
requires_openai_auth = true
"#
            }),
            None,
        );
        let provider_b = Provider::with_id(
            "b".to_string(),
            "AiHubMix".to_string(),
            json!({
                "auth": {
                    "OPENAI_API_KEY": "aihubmix-key"
                },
                "config": r#"model_provider = "aihubmix"
model = "gpt-5.4"

[model_providers.aihubmix]
name = "AiHubMix"
base_url = "https://aihubmix.example/v1"
wire_api = "responses"
requires_openai_auth = true
"#
            }),
            None,
        );

        db.save_provider("codex", &provider_a)
            .expect("save provider a");
        db.save_provider("codex", &provider_b)
            .expect("save provider b");
        db.set_current_provider("codex", "a")
            .expect("set current provider");
        crate::settings::set_current_provider(&AppType::Codex, Some("a"))
            .expect("set local current provider");
        db.save_live_backup(
            "codex",
            &serde_json::to_string(&provider_a.settings_config).expect("serialize provider a"),
        )
        .await
        .expect("seed live backup");
        service
            .write_codex_live(&json!({
                "auth": {
                    "OPENAI_API_KEY": PROXY_TOKEN_PLACEHOLDER
                },
                "config": r#"model_provider = "rightcode"
model = "gpt-5.4"

[model_providers.rightcode]
name = "RightCode"
base_url = "http://127.0.0.1:15721/v1"
wire_api = "responses"
requires_openai_auth = true
"#
            }))
            .expect("seed taken-over Codex live config");

        service
            .hot_switch_provider("codex", "b")
            .await
            .expect("hot switch Codex provider");

        let backup = db
            .get_live_backup("codex")
            .await
            .expect("get live backup")
            .expect("backup exists");
        let stored: Value =
            serde_json::from_str(&backup.original_config).expect("parse backup json");
        let backup_config = stored
            .get("config")
            .and_then(Value::as_str)
            .expect("backup config string");
        let parsed_backup: toml::Value =
            toml::from_str(backup_config).expect("parse backup config");
        assert_eq!(
            parsed_backup.get("model_provider").and_then(|v| v.as_str()),
            Some("rightcode"),
            "provider-derived restore backup should retain stable Codex model_provider"
        );
        let backup_model_providers = parsed_backup
            .get("model_providers")
            .and_then(|v| v.as_table())
            .expect("backup model_providers");
        assert!(backup_model_providers.get("aihubmix").is_none());
        assert_eq!(
            backup_model_providers
                .get("rightcode")
                .and_then(|v| v.get("base_url"))
                .and_then(|v| v.as_str()),
            Some("https://aihubmix.example/v1"),
            "stable provider id should point at the hot-switched provider endpoint"
        );

        service
            .restore_live_config_for_app(&AppType::Codex)
            .await
            .expect("restore Codex live config");

        let live = service.read_codex_live().expect("read Codex live config");
        let live_config = live
            .get("config")
            .and_then(Value::as_str)
            .expect("live config string");
        let parsed_live: toml::Value = toml::from_str(live_config).expect("parse live config");
        assert_eq!(
            parsed_live.get("model_provider").and_then(|v| v.as_str()),
            Some("rightcode"),
            "restored Codex live config should not switch history buckets"
        );
        assert_eq!(
            live.get("auth")
                .and_then(|auth| auth.get("OPENAI_API_KEY"))
                .and_then(Value::as_str),
            Some("aihubmix-key"),
            "restore should still use the hot-switched provider auth"
        );
    }

    #[tokio::test]
    #[serial]
    async fn update_live_backup_from_provider_for_gemini_keeps_only_env_snapshot() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let settings_path = crate::gemini_config::get_gemini_settings_path();
        std::fs::create_dir_all(
            settings_path
                .parent()
                .expect("gemini settings parent directory"),
        )
        .expect("create gemini settings directory");
        write_json_file(
            &settings_path,
            &json!({
                "mcpServers": {
                    "echo": {
                        "command": "npx",
                        "args": ["echo-server"]
                    }
                }
            }),
        )
        .expect("seed gemini settings.json");

        let db = Arc::new(Database::memory().expect("init db"));
        let service = ProxyService::new(db.clone());
        db.save_live_backup("gemini", r#"{"env":{"GEMINI_API_KEY":"stale-token"}}"#)
            .await
            .expect("seed active gemini backup");
        let provider = Provider::with_id(
            "gemini-provider".to_string(),
            "Gemini Provider".to_string(),
            json!({
                "env": {
                    "GEMINI_API_KEY": "gemini-token"
                },
                "config": {
                    "theme": "solarized"
                }
            }),
            None,
        );

        service
            .update_live_backup_from_provider("gemini", &provider)
            .await
            .expect("update gemini live backup");

        let backup = db
            .get_live_backup("gemini")
            .await
            .expect("get gemini live backup")
            .expect("gemini backup exists");
        let stored: Value =
            serde_json::from_str(&backup.original_config).expect("parse gemini backup json");

        assert_eq!(
            stored.get("env"),
            Some(&json!({
                "GEMINI_API_KEY": "gemini-token"
            })),
            "Gemini live backup should keep the provider env for takeover restore"
        );
        assert!(
            stored.get("config").is_none(),
            "Gemini live backup should not snapshot settings.json/config; upstream keeps only env"
        );
    }

    #[tokio::test]
    #[serial]
    async fn update_live_backup_from_provider_applies_claude_common_config_without_takeover() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let db = Arc::new(Database::memory().expect("init db"));
        db.set_config_snippet(
            "claude",
            Some(
                serde_json::json!({
                    "includeCoAuthoredBy": false
                })
                .to_string(),
            ),
        )
        .expect("set common config snippet");

        let service = ProxyService::new(db.clone());

        let mut provider = Provider::with_id(
            "claude-provider".to_string(),
            "Claude Provider".to_string(),
            json!({
                "env": {
                    "ANTHROPIC_AUTH_TOKEN": "token",
                    "ANTHROPIC_BASE_URL": "https://claude.example"
                }
            }),
            None,
        );
        provider.meta = Some(ProviderMeta {
            apply_common_config: Some(true),
            ..Default::default()
        });

        service
            .update_live_backup_from_provider("claude", &provider)
            .await
            .expect("update claude live backup");

        let backup = db
            .get_live_backup("claude")
            .await
            .expect("get claude live backup")
            .expect("claude backup exists");
        let stored: Value =
            serde_json::from_str(&backup.original_config).expect("parse claude backup json");

        assert_eq!(
            stored.get("includeCoAuthoredBy").and_then(Value::as_bool),
            Some(false),
            "common config should be applied into Claude restore backup even before takeover is active"
        );
    }

    #[tokio::test]
    #[serial]
    async fn update_live_backup_from_provider_requires_explicit_common_config_opt_in() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let db = Arc::new(Database::memory().expect("init db"));
        db.set_config_snippet(
            "claude",
            Some(
                serde_json::json!({
                    "includeCoAuthoredBy": false
                })
                .to_string(),
            ),
        )
        .expect("set common config snippet");

        let service = ProxyService::new(db.clone());
        let provider = Provider::with_id(
            "claude-provider".to_string(),
            "Claude Provider".to_string(),
            json!({
                "env": {
                    "ANTHROPIC_AUTH_TOKEN": "token",
                    "ANTHROPIC_BASE_URL": "https://claude.example"
                }
            }),
            None,
        );

        service
            .update_live_backup_from_provider("claude", &provider)
            .await
            .expect("update claude live backup");

        let backup = db
            .get_live_backup("claude")
            .await
            .expect("get claude live backup")
            .expect("claude backup exists");
        let stored: Value =
            serde_json::from_str(&backup.original_config).expect("parse claude backup json");

        assert!(
            stored.get("includeCoAuthoredBy").is_none(),
            "proxy backup must not apply common config when provider did not opt in"
        );
    }

    #[tokio::test]
    #[serial]
    async fn switch_proxy_target_updates_live_backup_when_taken_over() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let db = Arc::new(Database::memory().expect("init db"));
        let service = ProxyService::new(db.clone());

        let provider_a = Provider::with_id(
            "a".to_string(),
            "A".to_string(),
            json!({
                "env": {
                    "ANTHROPIC_API_KEY": "a-key"
                }
            }),
            None,
        );
        let provider_b = Provider::with_id(
            "b".to_string(),
            "B".to_string(),
            json!({
                "env": {
                    "ANTHROPIC_API_KEY": "b-key"
                }
            }),
            None,
        );
        db.save_provider("claude", &provider_a)
            .expect("save provider a");
        db.save_provider("claude", &provider_b)
            .expect("save provider b");
        db.set_current_provider("claude", "a")
            .expect("set current provider");

        db.save_live_backup("claude", "{\"env\":{}}")
            .await
            .expect("seed live backup");

        service
            .switch_proxy_target("claude", "b")
            .await
            .expect("switch proxy target");

        assert_eq!(
            crate::settings::get_current_provider(&AppType::Claude).as_deref(),
            Some("b")
        );

        let backup = db
            .get_live_backup("claude")
            .await
            .expect("get live backup")
            .expect("backup exists");
        let expected = serde_json::to_string(&provider_b.settings_config).expect("serialize");
        assert_eq!(backup.original_config, expected);
    }

    #[tokio::test]
    #[serial]
    async fn restore_live_from_current_provider_applies_codex_common_config() {
        let temp_home = TempDir::new().expect("create temp home");
        let _env = TestHomeEnvGuard::set(temp_home.path());

        let db = Arc::new(Database::memory().expect("init db"));
        db.set_config_snippet(
            "codex",
            Some("disable_response_storage = true\n".to_string()),
        )
        .expect("set codex common config snippet");

        let service = ProxyService::new(db.clone());
        let mut provider = Provider::with_id(
            "codex-provider".to_string(),
            "Codex Provider".to_string(),
            json!({
                "auth": {
                    "OPENAI_API_KEY": "codex-token"
                },
                "config": r#"model_provider = "default"
model = "gpt-5.2-codex"

[model_providers.default]
base_url = "https://api.example/v1"
"#
            }),
            None,
        );
        provider.meta = Some(ProviderMeta {
            apply_common_config: Some(true),
            ..Default::default()
        });
        db.save_provider("codex", &provider)
            .expect("save codex provider");
        db.set_current_provider("codex", &provider.id)
            .expect("set current codex provider");

        service
            .restore_live_from_current_provider(&AppType::Codex)
            .await
            .expect("restore codex live from current provider");

        let auth: Value = read_json_file(&get_codex_auth_path()).expect("read codex auth.json");
        assert_eq!(
            auth.get("OPENAI_API_KEY").and_then(Value::as_str),
            Some("codex-token"),
            "Codex fallback restore should write auth.json from the current provider"
        );

        let config_text =
            std::fs::read_to_string(get_codex_config_path()).expect("read codex config.toml");
        assert!(
            config_text.contains("disable_response_storage = true"),
            "Codex fallback restore should apply the common config snippet like upstream"
        );
    }
}
