use super::ZedAgent;
use crate::acp;
use crate::acp::AgentSideConnection;
use crate::acp_connection;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use std::str::FromStr;
use std::sync::Arc;
use vtcode_core::config::models::{ModelId, Provider};
use vtcode_core::config::types::ReasoningEffortLevel;
use vtcode_core::core::interfaces::SessionMode;
use vtcode_core::core::threads::{ThreadBootstrap, build_thread_archive_metadata};
use vtcode_core::llm::ModelResolver;
use vtcode_core::llm::factory::get_factory;
use vtcode_core::llm::provider::{FinishReason, Message};
use vtcode_core::utils::session_archive::find_session_by_identifier;

use super::super::constants::SESSION_PREFIX;
use super::super::helpers::{session_config_options, session_mode_id, session_mode_prompt};
use super::super::types::{SessionData, SessionHandle};

impl ZedAgent {
    fn session_reasoning_effort_for_thread(
        &self,
        thread: &vtcode_core::core::threads::ThreadRuntimeHandle,
    ) -> ReasoningEffortLevel {
        thread
            .metadata()
            .and_then(|metadata| ReasoningEffortLevel::parse(&metadata.reasoning_effort))
            .unwrap_or(self.config.reasoning_effort)
    }

    fn session_mode_for_thread(
        &self,
        thread: &vtcode_core::core::threads::ThreadRuntimeHandle,
    ) -> SessionMode {
        thread
            .metadata()
            .and_then(|metadata| metadata.session_mode)
            .as_deref()
            .and_then(SessionMode::parse)
            .unwrap_or(SessionMode::Code)
    }

    fn sync_thread_reasoning_effort(
        &self,
        thread: &vtcode_core::core::threads::ThreadRuntimeHandle,
        reasoning_effort: ReasoningEffortLevel,
    ) {
        if let Some(mut metadata) = thread.metadata() {
            metadata.reasoning_effort = reasoning_effort.as_str().to_string();
            thread.replace_metadata(Some(metadata));
        }
    }

    fn session_provider_for_thread(
        &self,
        thread: &vtcode_core::core::threads::ThreadRuntimeHandle,
    ) -> String {
        thread
            .metadata()
            .map(|metadata| metadata.provider)
            .unwrap_or_else(|| self.config.provider.clone())
    }

    fn session_model_for_thread(
        &self,
        thread: &vtcode_core::core::threads::ThreadRuntimeHandle,
    ) -> String {
        thread
            .metadata()
            .map(|metadata| metadata.model)
            .unwrap_or_else(|| self.config.model.clone())
    }

    fn sync_thread_mode(
        &self,
        thread: &vtcode_core::core::threads::ThreadRuntimeHandle,
        mode: SessionMode,
    ) {
        if let Some(mut metadata) = thread.metadata() {
            metadata.session_mode = Some(mode.as_str().to_string());
            thread.replace_metadata(Some(metadata));
        }
    }

    fn sync_thread_provider_and_model(
        &self,
        thread: &vtcode_core::core::threads::ThreadRuntimeHandle,
        provider: &str,
        model: &str,
    ) {
        if let Some(mut metadata) = thread.metadata() {
            metadata.provider = provider.to_string();
            metadata.model = model.to_string();
            thread.replace_metadata(Some(metadata));
        }
    }

    pub(super) fn model_supports_thought_level(&self, provider: &str, model: &str) -> bool {
        ModelResolver::resolve(Some(provider), model, &[], None)
            .map(|resolved| resolved.reasoning_supported())
            .unwrap_or(false)
    }

    fn build_session_handle(
        &self,
        session_id: acp::SessionId,
        thread: vtcode_core::core::threads::ThreadRuntimeHandle,
    ) -> SessionHandle {
        let reasoning_effort = self.session_reasoning_effort_for_thread(&thread);
        let current_mode = self.session_mode_for_thread(&thread);
        let provider = self.session_provider_for_thread(&thread);
        let model = self.session_model_for_thread(&thread);
        SessionHandle {
            data: Rc::new(RefCell::new(SessionData {
                _session_id: session_id,
                thread,
                tool_notice_sent: false,
                current_mode,
                reasoning_effort,
                provider,
                model,
                last_tool_call_at: None,
            })),
            cancel_flag: Rc::new(Cell::new(false)),
        }
    }

    pub(crate) fn register_session(&self) -> acp::SessionId {
        let raw_id = self.next_session_id.get();
        self.next_session_id.set(raw_id + 1);
        let session_id = acp::SessionId::new(Arc::from(format!("{SESSION_PREFIX}-{raw_id}")));
        let mut metadata = build_thread_archive_metadata(
            self.config.workspace.as_path(),
            &self.config.model,
            &self.config.provider,
            &self.config.theme,
            self.config.reasoning_effort.as_str(),
        );
        metadata.session_mode = Some(SessionMode::Code.as_str().to_string());
        let thread = self.thread_manager.start_thread_with_identifier(
            session_id.0.to_string(),
            ThreadBootstrap::new(Some(metadata)),
        );
        let handle = self.build_session_handle(session_id.clone(), thread);
        self.sessions
            .borrow_mut()
            .insert(session_id.clone(), handle);
        session_id
    }

    pub(crate) fn session_handle(&self, session_id: &acp::SessionId) -> Option<SessionHandle> {
        self.sessions.borrow().get(session_id).cloned()
    }

    pub(super) fn push_message(&self, session: &SessionHandle, message: Message) {
        session.data.borrow().thread.append_message(message);
    }

    pub(super) fn should_send_tool_notice(&self, session: &SessionHandle) -> bool {
        !session.data.borrow().tool_notice_sent
    }

    pub(super) fn mark_tool_notice_sent(&self, session: &SessionHandle) {
        session.data.borrow_mut().tool_notice_sent = true;
    }

    pub(super) fn update_session_mode(&self, session: &SessionHandle, mode: SessionMode) -> bool {
        let mut data = session.data.borrow_mut();
        if data.current_mode == mode {
            return false;
        }
        data.current_mode = mode;
        self.sync_thread_mode(&data.thread, mode);
        true
    }

    pub(super) async fn apply_session_mode(
        &self,
        session_id: &acp::SessionId,
        session: &SessionHandle,
        mode: SessionMode,
    ) -> Result<bool, acp::Error> {
        if !self.update_session_mode(session, mode) {
            return Ok(false);
        }

        let config_options = self.current_session_config_options(session);
        self.send_update(
            session_id,
            acp::SessionUpdate::CurrentModeUpdate(acp::CurrentModeUpdate::new(session_mode_id(
                mode,
            ))),
        )
        .await?;
        self.send_update(
            session_id,
            acp::SessionUpdate::ConfigOptionUpdate(acp::ConfigOptionUpdate::new(config_options)),
        )
        .await?;

        Ok(true)
    }

    pub(super) fn update_session_reasoning_effort(
        &self,
        session: &SessionHandle,
        reasoning_effort: ReasoningEffortLevel,
    ) -> bool {
        let mut data = session.data.borrow_mut();
        if data.reasoning_effort == reasoning_effort {
            return false;
        }
        data.reasoning_effort = reasoning_effort;
        self.sync_thread_reasoning_effort(&data.thread, reasoning_effort);
        true
    }

    pub(super) fn update_session_provider_and_model(
        &self,
        session: &SessionHandle,
        provider: String,
        model: String,
    ) -> bool {
        let mut data = session.data.borrow_mut();
        if data.provider == provider && data.model == model {
            return false;
        }
        data.provider = provider;
        data.model = model;
        self.sync_thread_provider_and_model(&data.thread, &data.provider, &data.model);
        true
    }

    pub(super) fn provider_default_model(&self, provider: &str) -> Option<String> {
        Provider::from_str(provider).ok().map(|value| {
            ModelId::default_single_for_provider(value)
                .as_str()
                .to_string()
        })
    }

    pub(super) fn provider_supports_model(&self, provider: &str, model: &str) -> bool {
        let Ok(provider) = Provider::from_str(provider) else {
            return true;
        };
        ModelId::models_for_provider(provider)
            .iter()
            .any(|entry| entry.as_str() == model)
    }

    fn provider_select_options(
        &self,
        current_provider: &str,
    ) -> Vec<acp::SessionConfigSelectOption> {
        let mut providers = get_factory()
            .lock()
            .ok()
            .map(|factory| factory.list_providers())
            .unwrap_or_default();
        if providers.is_empty() {
            tracing::warn!(
                "LLM factory has no registered providers, falling back to Provider::all_providers()"
            );
            providers = Provider::all_providers()
                .into_iter()
                .map(|provider| provider.to_string())
                .collect();
        }

        if !providers
            .iter()
            .any(|provider| provider.eq_ignore_ascii_case(current_provider))
        {
            providers.push(current_provider.to_string());
        }

        providers.sort();
        providers.dedup_by(|left, right| left.eq_ignore_ascii_case(right));

        tracing::debug!(
            provider_count = providers.len(),
            providers = ?providers,
            current_provider = current_provider,
            "Building provider select options for ACP"
        );

        providers
            .into_iter()
            .map(|provider| {
                let name = Provider::from_str(&provider)
                    .ok()
                    .map(|parsed| parsed.label().to_string())
                    .unwrap_or_else(|| provider.clone());
                acp::SessionConfigSelectOption::new(provider, name)
            })
            .collect()
    }

    fn model_select_options(
        &self,
        provider: &str,
        current_model: &str,
    ) -> Vec<acp::SessionConfigSelectOption> {
        let mut options = Provider::from_str(provider)
            .ok()
            .map(|provider| {
                ModelId::models_for_provider(provider)
                    .into_iter()
                    .map(|model| {
                        acp::SessionConfigSelectOption::new(model.as_str(), model.display_name())
                    })
                    .collect::<Vec<_>>()
            })
            .unwrap_or_default();

        if !options
            .iter()
            .any(|option| option.value.0.as_ref() == current_model)
        {
            options.push(acp::SessionConfigSelectOption::new(
                current_model.to_string(),
                current_model.to_string(),
            ));
        }

        options.sort_by(|left, right| left.value.0.cmp(&right.value.0));
        options
    }

    pub(super) fn supports_provider(&self, provider: &str, current_provider: &str) -> bool {
        self.provider_select_options(current_provider)
            .iter()
            .any(|option| option.value.0.as_ref() == provider)
    }

    pub(super) fn current_session_config_options(
        &self,
        session: &SessionHandle,
    ) -> Vec<acp::SessionConfigOption> {
        let data = session.data.borrow();
        let provider_options = self.provider_select_options(&data.provider);
        let model_options = self.model_select_options(&data.provider, &data.model);
        let config_options = session_config_options(
            data.current_mode,
            data.reasoning_effort,
            self.model_supports_thought_level(&data.provider, &data.model),
            &data.provider,
            provider_options,
            &data.model,
            model_options,
        );

        tracing::debug!(
            config_option_count = config_options.len(),
            current_provider = %data.provider,
            current_model = %data.model,
            "Built session config options for ACP"
        );

        config_options
    }

    pub(super) fn resolved_messages(&self, session: &SessionHandle) -> Vec<Message> {
        let mut messages = Vec::with_capacity(10);
        if !self.system_prompt.trim().is_empty() {
            messages.push(Message::system(self.system_prompt.clone()));
        }

        let history = session.data.borrow();
        if let Some(prompt) = session_mode_prompt(history.current_mode) {
            messages.push(Message::system(prompt.to_string()));
        }
        messages.extend(history.thread.messages());
        messages
    }

    pub(super) async fn attach_thread_from_archive(
        &self,
        session_id: &acp::SessionId,
        identifier: &str,
    ) -> anyhow::Result<SessionHandle> {
        let listing = find_session_by_identifier(identifier)
            .await?
            .ok_or_else(|| anyhow::anyhow!("unknown archived session '{identifier}'"))?;
        let thread = self.thread_manager.start_thread_with_identifier(
            listing.identifier(),
            ThreadBootstrap::from_listing(listing),
        );
        let handle = self.build_session_handle(session_id.clone(), thread);
        self.sessions
            .borrow_mut()
            .insert(session_id.clone(), handle.clone());
        Ok(handle)
    }

    pub(super) fn stop_reason_from_finish(finish: FinishReason) -> acp::StopReason {
        match finish {
            FinishReason::Stop | FinishReason::ToolCalls => acp::StopReason::EndTurn,
            FinishReason::Length => acp::StopReason::MaxTokens,
            FinishReason::ContentFilter | FinishReason::Refusal | FinishReason::Error(_) => {
                acp::StopReason::Refusal
            }
            FinishReason::Pause => acp::StopReason::EndTurn,
        }
    }

    pub(super) fn client(&self) -> Option<Arc<AgentSideConnection>> {
        acp_connection()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::zed::types::NotificationEnvelope;
    use assert_fs::TempDir;
    use chrono::Utc;
    use std::collections::BTreeMap;
    use std::path::Path;
    use tokio::sync::mpsc;
    use vtcode_core::config::core::PromptCachingConfig;
    use vtcode_core::config::types::{
        AgentConfig as CoreAgentConfig, ModelSelectionSource, UiSurfacePreference,
    };
    use vtcode_core::config::{AgentClientProtocolZedConfig, CommandsConfig, ToolsConfig};
    use vtcode_core::core::agent::snapshots::{
        DEFAULT_CHECKPOINTS_ENABLED, DEFAULT_MAX_AGE_DAYS, DEFAULT_MAX_SNAPSHOTS,
    };
    use vtcode_core::utils::session_archive::{
        SessionArchiveMetadata, SessionListing, SessionSnapshot,
    };

    async fn build_agent(workspace: &Path) -> ZedAgent {
        let core_config = CoreAgentConfig {
            model: "gpt-5.4".to_string(),
            api_key: String::new(),
            provider: "openai".to_string(),
            api_key_env: "TEST_API_KEY".to_string(),
            workspace: workspace.to_path_buf(),
            verbose: false,
            quiet: false,
            theme: "test".to_string(),
            reasoning_effort: ReasoningEffortLevel::Low,
            ui_surface: UiSurfacePreference::default(),
            prompt_cache: PromptCachingConfig::default(),
            model_source: ModelSelectionSource::WorkspaceConfig,
            custom_api_keys: BTreeMap::new(),
            checkpointing_enabled: DEFAULT_CHECKPOINTS_ENABLED,
            checkpointing_storage_dir: None,
            checkpointing_max_snapshots: DEFAULT_MAX_SNAPSHOTS,
            checkpointing_max_age_days: Some(DEFAULT_MAX_AGE_DAYS),
            max_conversation_turns: 1000,
            model_behavior: None,
            openai_chatgpt_auth: None,
        };

        let (tx, mut rx) = mpsc::unbounded_channel::<NotificationEnvelope>();
        tokio::spawn(async move {
            while let Some(envelope) = rx.recv().await {
                let _ = envelope.completion.send(());
            }
        });

        ZedAgent::new(
            core_config,
            AgentClientProtocolZedConfig::default(),
            ToolsConfig::default(),
            CommandsConfig::default(),
            String::new(),
            tx,
            Some("Zed".to_string()),
        )
        .await
    }

    #[tokio::test]
    async fn build_session_handle_restores_session_config_from_thread_metadata() {
        let temp = TempDir::new().unwrap();
        let agent = build_agent(temp.path()).await;
        let listing = SessionListing {
            path: temp.path().join("session-vtcode-acp-archive.json"),
            snapshot: SessionSnapshot {
                metadata: SessionArchiveMetadata::new(
                    "vtcode",
                    temp.path().to_string_lossy(),
                    "gpt-5.4",
                    "openai",
                    "test",
                    "xhigh",
                ),
                started_at: Utc::now(),
                ended_at: Utc::now(),
                total_messages: 0,
                distinct_tools: Vec::new(),
                transcript: Vec::new(),
                messages: Vec::new(),
                progress: None,
                error_logs: Vec::new(),
            },
        };
        let thread = agent.thread_manager.start_thread_with_identifier(
            "session-vtcode-acp-archive",
            ThreadBootstrap::from_listing({
                let mut listing = listing;
                listing.snapshot.metadata.session_mode = Some("architect".to_string());
                listing
            }),
        );

        let handle = agent.build_session_handle(acp::SessionId::new("session-1"), thread);

        assert_eq!(handle.data.borrow().current_mode, SessionMode::Architect);
        assert_eq!(
            handle.data.borrow().reasoning_effort,
            ReasoningEffortLevel::XHigh
        );
        assert_eq!(handle.data.borrow().provider, "openai");
        assert_eq!(handle.data.borrow().model, "gpt-5.4");
    }

    #[tokio::test]
    async fn update_session_mode_syncs_thread_metadata() {
        let temp = TempDir::new().unwrap();
        let agent = build_agent(temp.path()).await;
        let session_id = agent.register_session();
        let session = agent.session_handle(&session_id).unwrap();

        assert!(agent.update_session_mode(&session, SessionMode::Ask));
        assert_eq!(
            session
                .data
                .borrow()
                .thread
                .metadata()
                .unwrap()
                .session_mode
                .as_deref(),
            Some("ask")
        );
    }

    #[tokio::test]
    async fn update_session_reasoning_effort_syncs_thread_metadata() {
        let temp = TempDir::new().unwrap();
        let agent = build_agent(temp.path()).await;
        let session_id = agent.register_session();
        let session = agent.session_handle(&session_id).unwrap();

        assert!(agent.update_session_reasoning_effort(&session, ReasoningEffortLevel::High));
        assert_eq!(
            session
                .data
                .borrow()
                .thread
                .metadata()
                .unwrap()
                .reasoning_effort,
            "high"
        );
    }

    #[tokio::test]
    async fn current_session_config_options_omit_thought_level_when_model_lacks_support() {
        let temp = TempDir::new().unwrap();
        let mut agent = build_agent(temp.path()).await;
        agent.config.model = "claude-haiku-3".to_string();
        agent.config.provider = "anthropic".to_string();
        let session_id = agent.register_session();
        let session = agent.session_handle(&session_id).unwrap();

        let config_options = agent.current_session_config_options(&session);

        assert_eq!(config_options.len(), 3);
        assert_eq!(config_options[0].id, acp::SessionConfigId::new("mode"));
    }
}
