//! In-process agent service implementation.

use std::collections::HashMap;

use rig::completion::Message;
use tauri::{AppHandle, Runtime};

use crate::agent::{
    panel_context::build_panel_context_message, AgentConfigStatus, AgentProviderKind,
    AgentProviderStatuses, AgentRuntimeConfig, AgentStoredSettings, ChatPromptRequest,
    OllamaProviderSettings, PreparedChatTurn, ProviderConfigStatus, RemoteProviderSettings,
    RigChatGateway, SaveAgentRuntimeConfigRequest, TaskProfile, UpdateRemoteApiKeyRequest,
};
use crate::error::AppError;

use super::gateway::ChatGateway;
use super::routing::{
    compute_overall_configured, compute_provider_configured, normalize_routes, resolve_runtime,
    validate_settings,
};
use super::settings::AgentSettingsService;

const MAX_RECENT_MESSAGES_BEFORE_COMPACTION: usize = 24;
const RECENT_MESSAGE_TAIL: usize = 16;
const SUMMARY_CHAR_LIMIT: usize = 1200;
const FALLBACK_RECENT_MESSAGE_LIMIT: usize = 50;

#[derive(Debug, Default)]
struct SessionState {
    summary: Option<String>,
    recent_messages: Vec<Message>,
}

#[derive(Debug)]
struct SummaryRequest {
    existing_summary: Option<String>,
    messages: Vec<Message>,
}

#[derive(Debug, Default)]
struct SessionManager {
    sessions: HashMap<String, SessionState>,
    next_session_id: u64,
}

impl SessionManager {
    fn resolve_session_id(&mut self, requested_session_id: Option<String>) -> String {
        let session_id = requested_session_id.unwrap_or_else(|| {
            self.next_session_id += 1;
            format!("session-{}", self.next_session_id)
        });

        self.sessions.entry(session_id.clone()).or_default();
        session_id
    }

    fn summary_request(&self, session_id: &str) -> Result<Option<SummaryRequest>, AppError> {
        let state = self
            .sessions
            .get(session_id)
            .ok_or_else(|| AppError::UnknownSession(session_id.to_string()))?;

        if state.recent_messages.len() <= MAX_RECENT_MESSAGES_BEFORE_COMPACTION {
            return Ok(None);
        }

        let split_at = state.recent_messages.len() - RECENT_MESSAGE_TAIL;
        Ok(Some(SummaryRequest {
            existing_summary: state.summary.clone(),
            messages: state.recent_messages[..split_at].to_vec(),
        }))
    }

    fn apply_summary(&mut self, session_id: &str, summary: String) -> Result<(), AppError> {
        let state = self
            .sessions
            .get_mut(session_id)
            .ok_or_else(|| AppError::UnknownSession(session_id.to_string()))?;

        state.summary = normalize_summary(summary);
        trim_recent_messages(&mut state.recent_messages, RECENT_MESSAGE_TAIL);
        Ok(())
    }

    fn apply_summary_failure(&mut self, session_id: &str) -> Result<(), AppError> {
        let state = self
            .sessions
            .get_mut(session_id)
            .ok_or_else(|| AppError::UnknownSession(session_id.to_string()))?;

        trim_recent_messages(&mut state.recent_messages, FALLBACK_RECENT_MESSAGE_LIMIT);
        Ok(())
    }

    fn history(&self, session_id: &str) -> Result<Vec<Message>, AppError> {
        let state = self
            .sessions
            .get(session_id)
            .ok_or_else(|| AppError::UnknownSession(session_id.to_string()))?;

        let mut history =
            Vec::with_capacity(state.recent_messages.len() + usize::from(state.summary.is_some()));

        if let Some(summary) = state.summary.as_ref() {
            history.push(Message::system(summary.clone()));
        }
        history.extend(state.recent_messages.clone());

        Ok(history)
    }

    fn record_user_prompt(&mut self, session_id: &str, prompt: &str) -> Result<(), AppError> {
        let state = self
            .sessions
            .get_mut(session_id)
            .ok_or_else(|| AppError::UnknownSession(session_id.to_string()))?;

        state.recent_messages.push(Message::user(prompt));
        Ok(())
    }

    fn record_assistant_reply(&mut self, session_id: &str, reply: &str) -> Result<(), AppError> {
        let state = self
            .sessions
            .get_mut(session_id)
            .ok_or_else(|| AppError::UnknownSession(session_id.to_string()))?;

        state.recent_messages.push(Message::assistant(reply));
        Ok(())
    }

    #[cfg(test)]
    fn prepare_turn(
        &mut self,
        request: ChatPromptRequest,
        runtime: AgentRuntimeConfig,
    ) -> Result<PreparedChatTurn, AppError> {
        let prompt = request.prompt.trim();
        if prompt.is_empty() {
            return Err(AppError::EmptyPrompt);
        }

        let task_profile = request
            .agent_profile
            .unwrap_or(TaskProfile::InteractiveChat);
        let session_id = self.resolve_session_id(request.session_id);
        self.build_prepared_turn(
            request.workspace_id,
            session_id,
            prompt.to_string(),
            runtime,
            Vec::new(),
            task_profile,
        )
    }
}

fn trim_recent_messages(messages: &mut Vec<Message>, keep: usize) {
    if messages.len() > keep {
        let drain_count = messages.len() - keep;
        messages.drain(..drain_count);
    }
}

fn normalize_summary(summary: String) -> Option<String> {
    let trimmed = summary.trim();
    if trimmed.is_empty() {
        return None;
    }

    let mut normalized = trimmed.to_string();
    if normalized.len() > SUMMARY_CHAR_LIMIT {
        normalized.truncate(SUMMARY_CHAR_LIMIT.saturating_sub(3));
        normalized.push_str("...");
    }

    Some(normalized)
}

fn build_context_messages(
    panel_context: Option<&crate::agent::ChatPanelContext>,
) -> Result<Vec<Message>, AppError> {
    Ok(panel_context
        .map(build_panel_context_message)
        .transpose()?
        .into_iter()
        .collect())
}

impl SessionManager {
    fn build_prepared_turn(
        &mut self,
        workspace_id: String,
        session_id: String,
        prompt: String,
        runtime: AgentRuntimeConfig,
        context_messages: Vec<Message>,
        task_profile: TaskProfile,
    ) -> Result<PreparedChatTurn, AppError> {
        let prior_history = self.history(&session_id)?;
        self.record_user_prompt(&session_id, &prompt)?;

        Ok(PreparedChatTurn {
            workspace_id,
            session_id,
            prompt,
            history: prior_history,
            context_messages,
            runtime,
            task_profile,
        })
    }
}

/// Stateful backend agent service combining settings, plaintext key storage, and session history.
#[derive(Debug)]
pub struct AgentService<R: Runtime, G: ChatGateway = RigChatGateway> {
    session_manager: SessionManager,
    settings: AgentSettingsService<R>,
    gateway: G,
}

impl<R: Runtime> AgentService<R, RigChatGateway> {
    pub fn new(app_handle: &AppHandle<R>) -> Result<Self, AppError> {
        Self::new_with_gateway(app_handle, RigChatGateway)
    }
}

impl<R: Runtime, G: ChatGateway> AgentService<R, G> {
    pub fn new_with_gateway(app_handle: &AppHandle<R>, gateway: G) -> Result<Self, AppError> {
        Ok(Self {
            session_manager: SessionManager::default(),
            settings: AgentSettingsService::new(app_handle),
            gateway,
        })
    }

    pub fn gateway(&self) -> G {
        self.gateway.clone()
    }

    pub async fn prepare_turn(
        &mut self,
        request: ChatPromptRequest,
    ) -> Result<PreparedChatTurn, AppError> {
        let prompt = request.prompt.trim();
        if prompt.is_empty() {
            return Err(AppError::EmptyPrompt);
        }

        let task_profile = request
            .agent_profile
            .unwrap_or(TaskProfile::InteractiveChat);
        let context_messages = build_context_messages(request.panel_context.as_ref())?;
        let runtime = self.resolve_runtime(Some(task_profile), request.model_override.clone())?;
        let session_id = self.session_manager.resolve_session_id(request.session_id);

        if let Some(summary_request) = self.session_manager.summary_request(&session_id)? {
            let summary_runtime = self.resolve_runtime(Some(TaskProfile::Summarization), None)?;
            match self
                .gateway
                .summarize_history(
                    summary_runtime,
                    summary_request.existing_summary,
                    summary_request.messages,
                )
                .await
            {
                Ok(summary) => self.session_manager.apply_summary(&session_id, summary)?,
                Err(_) => self.session_manager.apply_summary_failure(&session_id)?,
            }
        }

        self.session_manager.build_prepared_turn(
            request.workspace_id,
            session_id,
            prompt.to_string(),
            runtime,
            context_messages,
            task_profile,
        )
    }

    pub fn record_assistant_reply(
        &mut self,
        session_id: &str,
        reply: &str,
    ) -> Result<(), AppError> {
        self.session_manager
            .record_assistant_reply(session_id, reply)
    }

    pub fn get_config_status(&self) -> Result<AgentConfigStatus, AppError> {
        let settings = self.settings.load()?;
        Ok(self.build_status(settings))
    }

    pub fn save_runtime_config(
        &mut self,
        request: SaveAgentRuntimeConfigRequest,
    ) -> Result<AgentConfigStatus, AppError> {
        let mut settings = self.settings.load()?;
        settings.remote = RemoteProviderSettings {
            enabled: request.remote.enabled,
            base_url: request.remote.base_url.trim().to_string(),
            api_key: settings.remote.api_key,
            default_model: request.remote.default_model.trim().to_string(),
        };
        settings.ollama = OllamaProviderSettings {
            enabled: request.ollama.enabled,
            base_url: request.ollama.base_url.trim().to_string(),
            default_model: request.ollama.default_model.trim().to_string(),
        };
        settings.task_defaults = request.task_defaults;
        settings.sec_edgar_user_agent = request.sec_edgar_user_agent.trim().to_string();
        normalize_routes(&mut settings)?;
        validate_settings(&settings)?;

        let persisted = self.settings.save(settings)?;
        Ok(self.build_status(persisted))
    }

    pub fn update_remote_api_key(
        &mut self,
        request: UpdateRemoteApiKeyRequest,
    ) -> Result<AgentConfigStatus, AppError> {
        let api_key = request.api_key.trim().to_string();
        if api_key.is_empty() {
            return Err(AppError::RemoteApiKeyMissing);
        }

        let settings = self.settings.set_remote_api_key(api_key)?;
        Ok(self.build_status(settings))
    }

    pub fn clear_remote_api_key(&mut self) -> Result<AgentConfigStatus, AppError> {
        let settings = self.settings.set_remote_api_key(String::new())?;
        Ok(self.build_status(settings))
    }

    fn build_status(&self, settings: AgentStoredSettings) -> AgentConfigStatus {
        AgentConfigStatus {
            configured: compute_overall_configured(&settings),
            providers: AgentProviderStatuses {
                remote: ProviderConfigStatus {
                    enabled: settings.remote.enabled,
                    configured: compute_provider_configured(&settings, AgentProviderKind::Remote),
                    base_url: settings.remote.base_url.clone(),
                    default_model: settings.remote.default_model.clone(),
                    has_api_key: !settings.remote.api_key.trim().is_empty(),
                },
                ollama: ProviderConfigStatus {
                    enabled: settings.ollama.enabled,
                    configured: compute_provider_configured(&settings, AgentProviderKind::Ollama),
                    base_url: settings.ollama.base_url.clone(),
                    default_model: settings.ollama.default_model.clone(),
                    has_api_key: false,
                },
            },
            task_defaults: settings.task_defaults,
            has_sec_edgar_user_agent: !settings.sec_edgar_user_agent.trim().is_empty(),
            sec_edgar_user_agent: settings.sec_edgar_user_agent,
            require_mapping_approval: settings.require_mapping_approval,
        }
    }

    fn resolve_runtime(
        &self,
        task_profile: Option<TaskProfile>,
        model_override: Option<String>,
    ) -> Result<AgentRuntimeConfig, AppError> {
        let settings = self.settings.load()?;
        resolve_runtime(
            &settings,
            task_profile.unwrap_or(TaskProfile::InteractiveChat),
            model_override,
        )
        .map_err(|error| {
            if error.is_configuration_issue() {
                AppError::AgentNotConfigured
            } else {
                error
            }
        })
    }

    pub fn resolve_runtime_for_job(
        &self,
        task_profile: Option<TaskProfile>,
    ) -> Result<AgentRuntimeConfig, AppError> {
        self.resolve_runtime(task_profile, None)
    }
}

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

    use super::SessionManager;
    use crate::agent::{
        default_task_defaults, AgentProviderKind, AgentRuntimeConfig, AgentService, AgentTaskRoute,
        ChatPanelContext, ChatPromptRequest, SaveAgentRuntimeConfigRequest, TaskProfile,
        UpdateRemoteApiKeyRequest, DEFAULT_OLLAMA_BASE_URL, DEFAULT_OLLAMA_COMPAT_API_KEY,
        DEFAULT_REMOTE_BASE_URL, DEFAULT_REMOTE_MODEL,
    };
    use crate::error::AppError;
    use crate::terminal::{Company, CompanyProfile, PanelPayload};

    use rig::completion::Message;
    use tauri::test::{mock_builder, mock_context, noop_assets, MockRuntime};

    fn request(prompt: &str) -> ChatPromptRequest {
        ChatPromptRequest {
            request_id: "request-1".to_string(),
            workspace_id: "workspace-1".to_string(),
            session_id: None,
            prompt: prompt.to_string(),
            agent_profile: None,
            model_override: None,
            panel_context: None,
        }
    }

    fn prepare_turn(
        service: &mut AgentService<MockRuntime>,
        request: ChatPromptRequest,
    ) -> Result<crate::agent::PreparedChatTurn, AppError> {
        futures::executor::block_on(service.prepare_turn(request))
    }

    fn remote_config_request(
        task_defaults: std::collections::HashMap<TaskProfile, AgentTaskRoute>,
    ) -> SaveAgentRuntimeConfigRequest {
        SaveAgentRuntimeConfigRequest {
            remote: crate::agent::types::SaveRemoteProviderSettings {
                enabled: true,
                base_url: "https://example.test/v4".to_string(),
                default_model: "glm-test".to_string(),
            },
            ollama: crate::agent::types::SaveOllamaProviderSettings {
                enabled: false,
                base_url: DEFAULT_OLLAMA_BASE_URL.to_string(),
                default_model: String::new(),
            },
            task_defaults,
            sec_edgar_user_agent: String::new(),
        }
    }

    fn ollama_config_request(
        task_defaults: std::collections::HashMap<TaskProfile, AgentTaskRoute>,
    ) -> SaveAgentRuntimeConfigRequest {
        SaveAgentRuntimeConfigRequest {
            remote: crate::agent::types::SaveRemoteProviderSettings {
                enabled: false,
                base_url: DEFAULT_REMOTE_BASE_URL.to_string(),
                default_model: DEFAULT_REMOTE_MODEL.to_string(),
            },
            ollama: crate::agent::types::SaveOllamaProviderSettings {
                enabled: true,
                base_url: DEFAULT_OLLAMA_BASE_URL.to_string(),
                default_model: "qwen3-coder".to_string(),
            },
            task_defaults,
            sec_edgar_user_agent: String::new(),
        }
    }

    #[test]
    fn returns_empty_prompt_error_when_request_contains_only_whitespace() {
        let mut sessions = SessionManager::default();

        let result = sessions.prepare_turn(request("   "), sample_runtime());

        assert_eq!(result.unwrap_err(), AppError::EmptyPrompt);
    }

    #[test]
    fn creates_new_session_when_request_does_not_provide_one() {
        let mut sessions = SessionManager::default();

        let result = sessions
            .prepare_turn(request("Summarize AAPL"), sample_runtime())
            .unwrap();

        assert_eq!(result.session_id, "session-1");
        assert!(result.history.is_empty());
    }

    #[test]
    fn reuses_existing_session_and_returns_prior_history() {
        let mut sessions = SessionManager::default();
        let session_id = "session-42".to_string();

        sessions
            .prepare_turn(
                ChatPromptRequest {
                    session_id: Some(session_id.clone()),
                    ..request("First prompt")
                },
                sample_runtime(),
            )
            .unwrap();
        sessions
            .record_assistant_reply(&session_id, "First reply")
            .unwrap();

        let result = sessions
            .prepare_turn(
                ChatPromptRequest {
                    session_id: Some(session_id),
                    ..request("Second prompt")
                },
                sample_runtime(),
            )
            .unwrap();

        assert_eq!(result.history.len(), 2);
        assert_eq!(result.history[0], Message::user("First prompt"));
        assert_eq!(result.history[1], Message::assistant("First reply"));
    }

    #[test]
    fn persists_settings_and_plaintext_api_key_in_store() {
        with_test_home("config", || {
            let app = build_test_app();
            let mut service = AgentService::new(app.handle()).unwrap();

            let initial = service.get_config_status().unwrap();
            assert!(!initial.configured);
            assert!(!initial.providers.remote.has_api_key);
            assert_eq!(initial.providers.remote.base_url, DEFAULT_REMOTE_BASE_URL);
            assert_eq!(initial.providers.remote.default_model, DEFAULT_REMOTE_MODEL);

            service
                .update_remote_api_key(UpdateRemoteApiKeyRequest {
                    api_key: "z-ai-key-1".to_string(),
                })
                .unwrap();

            let saved = service
                .save_runtime_config(SaveAgentRuntimeConfigRequest {
                    sec_edgar_user_agent: "MosaicIQ admin@example.com".to_string(),
                    ..remote_config_request(default_task_defaults())
                })
                .unwrap();
            assert_eq!(saved.providers.remote.base_url, "https://example.test/v4");
            assert_eq!(saved.providers.remote.default_model, "glm-test");
            assert!(saved.providers.remote.has_api_key);
            assert!(saved.has_sec_edgar_user_agent);
            assert_eq!(saved.sec_edgar_user_agent, "MosaicIQ admin@example.com");

            let updated = service
                .update_remote_api_key(UpdateRemoteApiKeyRequest {
                    api_key: "z-ai-key-1".to_string(),
                })
                .unwrap();
            assert!(updated.configured);
            assert!(updated.providers.remote.has_api_key);

            let prepared = prepare_turn(&mut service, request("hello")).unwrap();
            assert_eq!(prepared.runtime.base_url, "https://example.test/v4");
            assert_eq!(prepared.runtime.model, "glm-test");
            assert_eq!(prepared.runtime.api_key.as_deref(), Some("z-ai-key-1"));
        });
    }

    #[test]
    fn clears_plaintext_remote_api_key_from_store() {
        with_test_home("clear", || {
            let app = build_test_app();
            let mut service = AgentService::new(app.handle()).unwrap();

            service
                .update_remote_api_key(UpdateRemoteApiKeyRequest {
                    api_key: "z-ai-key-1".to_string(),
                })
                .unwrap();

            service
                .save_runtime_config(remote_config_request(default_task_defaults()))
                .unwrap();

            service
                .update_remote_api_key(UpdateRemoteApiKeyRequest {
                    api_key: "z-ai-key-1".to_string(),
                })
                .unwrap();

            let cleared = service.clear_remote_api_key().unwrap();
            assert!(!cleared.configured);
            assert!(!cleared.providers.remote.has_api_key);

            let result = prepare_turn(&mut service, request("hello"));
            assert_eq!(result.unwrap_err(), AppError::AgentNotConfigured);
        });
    }

    #[test]
    fn model_override_replaces_task_default() {
        with_test_home("model-override", || {
            let app = build_test_app();
            let mut service = AgentService::new(app.handle()).unwrap();
            service
                .update_remote_api_key(UpdateRemoteApiKeyRequest {
                    api_key: "z-ai-key-1".to_string(),
                })
                .unwrap();
            service
                .save_runtime_config(remote_config_request(default_task_defaults()))
                .unwrap();

            let prepared = prepare_turn(
                &mut service,
                ChatPromptRequest {
                    model_override: Some("glm-override".to_string()),
                    ..request("hello")
                },
            )
            .unwrap();

            assert_eq!(prepared.runtime.model, "glm-override");
        });
    }

    #[test]
    fn empty_task_model_falls_back_to_default_remote_model() {
        with_test_home("task-default", || {
            let app = build_test_app();
            let mut service = AgentService::new(app.handle()).unwrap();
            service
                .update_remote_api_key(UpdateRemoteApiKeyRequest {
                    api_key: "z-ai-key-1".to_string(),
                })
                .unwrap();
            let mut task_defaults = default_task_defaults();
            task_defaults.insert(
                TaskProfile::InteractiveChat,
                crate::agent::AgentTaskRoute {
                    provider: AgentProviderKind::Remote,
                    model: String::new(),
                },
            );

            let _saved = service
                .save_runtime_config(remote_config_request(task_defaults))
                .unwrap();

            let prepared = prepare_turn(&mut service, request("hello")).unwrap();
            assert_eq!(prepared.runtime.model, "glm-test");
        });
    }

    #[test]
    fn remote_task_without_api_key_fails_validation_at_save_time() {
        with_test_home("remote-validation", || {
            let app = build_test_app();
            let mut service = AgentService::new(app.handle()).unwrap();
            let result =
                service.save_runtime_config(remote_config_request(default_task_defaults()));
            assert!(matches!(result, Err(AppError::InvalidSettings(_))));
        });
    }

    #[test]
    fn ollama_provider_is_configured_without_a_stored_api_key() {
        with_test_home("ollama-openai-config", || {
            let app = build_test_app();
            let mut service = AgentService::new(app.handle()).unwrap();
            let mut task_defaults = default_task_defaults();
            for route in task_defaults.values_mut() {
                route.provider = AgentProviderKind::Ollama;
            }

            let saved = service
                .save_runtime_config(ollama_config_request(task_defaults))
                .unwrap();

            assert!(saved.configured);
            assert!(saved.providers.ollama.configured);
            assert!(!saved.providers.remote.has_api_key);

            let prepared = prepare_turn(&mut service, request("hello")).unwrap();
            assert_eq!(prepared.runtime.provider_kind, AgentProviderKind::Ollama);
            assert_eq!(
                prepared.runtime.api_key.as_deref(),
                Some(DEFAULT_OLLAMA_COMPAT_API_KEY)
            );
        });
    }

    #[test]
    fn prepare_turn_without_panel_context_yields_no_context_messages() {
        with_test_home("context-none", || {
            let app = build_test_app();
            let mut service = configured_service(&app);

            let prepared = prepare_turn(&mut service, request("hello")).unwrap();

            assert!(prepared.context_messages.is_empty());
        });
    }

    #[test]
    fn prepare_turn_with_panel_context_yields_one_system_context_message() {
        with_test_home("context-present", || {
            let app = build_test_app();
            let mut service = configured_service(&app);

            let prepared = prepare_turn(
                &mut service,
                ChatPromptRequest {
                    panel_context: Some(sample_panel_context()),
                    ..request("hello")
                },
            )
            .unwrap();

            assert_eq!(prepared.context_messages.len(), 1);
            assert!(matches!(
                prepared.context_messages[0],
                Message::System { .. }
            ));
        });
    }

    #[test]
    fn panel_context_is_not_persisted_in_follow_up_history() {
        with_test_home("context-history", || {
            let app = build_test_app();
            let mut service = configured_service(&app);

            let first = prepare_turn(
                &mut service,
                ChatPromptRequest {
                    panel_context: Some(sample_panel_context()),
                    ..request("hello")
                },
            )
            .unwrap();

            assert_eq!(first.context_messages.len(), 1);

            service
                .record_assistant_reply(&first.session_id, "reply")
                .unwrap();

            let follow_up = prepare_turn(
                &mut service,
                ChatPromptRequest {
                    session_id: Some(first.session_id),
                    ..request("follow up")
                },
            )
            .unwrap();

            assert!(follow_up.context_messages.is_empty());
            assert_eq!(follow_up.history.len(), 2);
            assert_eq!(follow_up.history[0], Message::user("hello"));
            assert_eq!(follow_up.history[1], Message::assistant("reply"));
        });
    }

    fn sample_runtime() -> AgentRuntimeConfig {
        AgentRuntimeConfig {
            provider_kind: AgentProviderKind::Remote,
            base_url: "https://example.com".to_string(),
            model: "glm-5.1".to_string(),
            api_key: Some("key".to_string()),
        }
    }

    fn sample_panel_context() -> ChatPanelContext {
        ChatPanelContext {
            source_command: Some("/search AAPL".to_string()),
            captured_at: Some("2026-04-06T10:00:00Z".to_string()),
            panel: PanelPayload::Company {
                data: Company {
                    symbol: "AAPL".to_string(),
                    name: "Apple Inc.".to_string(),
                    price: 200.0,
                    change: 1.0,
                    change_percent: 0.5,
                    market_cap: 3_000_000_000_000.0,
                    volume: Some(100),
                    volume_label: Some("Volume".to_string()),
                    pe: Some(30.0),
                    eps: Some(6.0),
                    high52_week: Some(210.0),
                    low52_week: Some(150.0),
                    profile: Some(CompanyProfile {
                        description: Some("Description".to_string()),
                        wiki_url: None,
                        ceo: Some("Tim Cook".to_string()),
                        headquarters: Some("Cupertino".to_string()),
                        employees: Some(10),
                        founded: Some(1976),
                        sector: Some("Technology".to_string()),
                        website: Some("https://apple.com".to_string()),
                    }),
                    price_chart: None,
                    price_chart_ranges: None,
                },
            },
        }
    }

    fn build_test_app() -> tauri::App<MockRuntime> {
        mock_builder()
            .plugin(tauri_plugin_store::Builder::new().build())
            .build(mock_context(noop_assets()))
            .unwrap()
    }

    fn configured_service(app: &tauri::App<MockRuntime>) -> AgentService<MockRuntime> {
        let mut service = AgentService::new(app.handle()).unwrap();
        service
            .update_remote_api_key(UpdateRemoteApiKeyRequest {
                api_key: "z-ai-key-1".to_string(),
            })
            .unwrap();
        service
            .save_runtime_config(remote_config_request(default_task_defaults()))
            .unwrap();
        service
    }

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

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

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

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

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

        cleanup_test_data_dir(home);

        match result {
            Ok(value) => value,
            Err(payload) => std::panic::resume_unwind(payload),
        }
    }
    fn cleanup_test_data_dir(path: PathBuf) {
        let _ = fs::remove_dir_all(path);
    }
}
