//! `DesktopService` 工作流入口。桌面 / Tauri / 桌面 smoke 统一走这条路径。

use super::dto::{
    DesktopContextResponse, DesktopHistoryResponse, DesktopImportSessionRequest,
    DesktopImportSessionResponse, DesktopLifecycleActionDto, DesktopMemoryActionRequest,
    DesktopMemoryDraftRequest, DesktopPromptOptimizeRequest, DesktopPromptOptimizeResponse,
    DesktopRecordLookupRequest, DesktopRecordResponse, DesktopRouteRequest,
    DesktopSessionActionRequest, DesktopSessionActionResponse, DesktopSessionBrowserRequest,
    DesktopSessionBrowserResponse, DesktopSessionDetailRequest, DesktopSessionDetailResponse,
    DesktopStatusRequest, DesktopWakeupRequest, DesktopWakeupResponse, DesktopWikiIndexRequest,
    DesktopWikiIndexResponse, DesktopWikiLintRequest, DesktopWikiLintResponse,
    DesktopWorkbenchRequest, DesktopWorkbenchResponse, DesktopWriteResponse,
};
use super::errors::{DesktopResult, input_error, runtime_error};
use super::helpers::{
    build_continue_command, delete_session_file, launch_terminal_command, lifecycle_read_options,
    load_session_index, load_session_index_filtered, store_from_config_path,
};
use super::validate::{
    validate_config_path, validate_memory_action_request, validate_memory_draft_request,
    validate_record_lookup_request, validate_route_inputs, validate_route_request,
    validate_session_action_request, validate_session_detail_request,
};
use crate::app;
use crate::daemon::{read_history, read_record, read_workbench};
use crate::desktop_status::{DesktopStatusResponse, collect_status};
use crate::lifecycle_service::{LifecycleService, available_actions};
use crate::lifecycle_store::latest_state_entries;
use crate::lifecycle_summary;
use crate::memory_gateway;
use crate::memory_importer;
use crate::session_sources::{
    ProviderSessionMessages, entry_session_refs, load_provider_messages, load_provider_sessions,
    raw_session_id,
};

#[derive(Debug, Default, Clone, Copy)]
pub struct DesktopService;

impl DesktopService {
    pub fn new() -> Self {
        Self
    }

    pub fn run_context(
        self,
        request: DesktopRouteRequest,
    ) -> DesktopResult<DesktopContextResponse> {
        validate_route_request(&request).map_err(input_error)?;
        let config_path = request.config_path.clone();
        let result = app::run_with_overrides(
            config_path.as_path(),
            request.to_route_input(),
            Some(request.format),
            request.vault_root_override.as_deref(),
        )
        .map_err(runtime_error)?;
        Ok(DesktopContextResponse {
            rendered: result.rendered,
            explain: result.explain,
            used_format: result.used_format,
            used_vault_root: result.used_vault_root,
            bundle: result.bundle,
        })
    }

    pub fn build_wakeup(
        self,
        request: DesktopWakeupRequest,
    ) -> DesktopResult<DesktopWakeupResponse> {
        validate_route_inputs(
            request.config_path.as_path(),
            request.cwd.as_path(),
            &request.task,
            request.vault_root_override.as_deref(),
        )
        .map_err(input_error)?;
        let gateway_request =
            memory_gateway::wakeup_request(request.to_route_input(), request.profile);
        let response = memory_gateway::execute(
            request.config_path.as_path(),
            gateway_request,
            request.vault_root_override.as_deref(),
        )
        .map_err(runtime_error)?;
        let packet = response.wakeup_packet.ok_or_else(|| {
            runtime_error(anyhow::anyhow!(
                "wakeup packet missing from gateway response"
            ))
        })?;
        Ok(DesktopWakeupResponse {
            used_vault_root: response.used_vault_root,
            bundle: response.bundle,
            packet,
        })
    }

    pub fn optimize_prompt(
        self,
        request: DesktopPromptOptimizeRequest,
    ) -> DesktopResult<DesktopPromptOptimizeResponse> {
        validate_route_inputs(
            request.config_path.as_path(),
            request.cwd.as_path(),
            &request.task,
            request.vault_root_override.as_deref(),
        )
        .map_err(input_error)?;
        memory_gateway::execute_prompt_optimize(
            request.config_path.as_path(),
            memory_gateway::prompt_optimize_request(
                request.to_route_input(),
                request.profile,
                request.provider,
                request.session_id,
                false,
            ),
            request.vault_root_override.as_deref(),
        )
        .map_err(runtime_error)
    }

    pub fn load_workbench(
        self,
        request: DesktopWorkbenchRequest,
    ) -> DesktopResult<DesktopWorkbenchResponse> {
        validate_config_path(request.config_path.as_path()).map_err(input_error)?;
        let snapshot = read_workbench(
            request.config_path.as_path(),
            &lifecycle_read_options(request.daemon.as_ref()),
        )
        .map_err(runtime_error)?;
        Ok(DesktopWorkbenchResponse {
            payload: serde_json::json!({
                "pending_review": lifecycle_summary::queue_payload(&snapshot.pending_review, "pending_review"),
                "wakeup_ready": lifecycle_summary::queue_payload(&snapshot.wakeup_ready, "wakeup_ready"),
            }),
            snapshot,
        })
    }

    pub fn get_record(
        self,
        request: DesktopRecordLookupRequest,
    ) -> DesktopResult<Option<DesktopRecordResponse>> {
        validate_record_lookup_request(&request).map_err(input_error)?;
        let record = read_record(
            request.config_path.as_path(),
            &request.record_id,
            &lifecycle_read_options(request.daemon.as_ref()),
        )
        .map_err(runtime_error)?;
        Ok(record.map(|record| DesktopRecordResponse {
            payload: lifecycle_summary::record_payload(&record),
            rendered: lifecycle_summary::render_record_text(&record, false, true),
            available_actions: available_actions(&record.record)
                .iter()
                .copied()
                .map(DesktopLifecycleActionDto::from_lifecycle_action)
                .collect(),
            record,
        }))
    }

    pub fn get_history(
        self,
        request: DesktopRecordLookupRequest,
    ) -> DesktopResult<DesktopHistoryResponse> {
        validate_record_lookup_request(&request).map_err(input_error)?;
        let history = read_history(
            request.config_path.as_path(),
            &request.record_id,
            &lifecycle_read_options(request.daemon.as_ref()),
        )
        .map_err(runtime_error)?;
        Ok(DesktopHistoryResponse {
            payload: lifecycle_summary::history_payload(&request.record_id, &history),
            rendered: lifecycle_summary::render_history_text(&request.record_id, &history, false),
            record_id: request.record_id,
            history,
        })
    }

    pub fn record_manual(
        self,
        request: DesktopMemoryDraftRequest,
    ) -> DesktopResult<DesktopWriteResponse> {
        validate_memory_draft_request(&request).map_err(input_error)?;
        let service = LifecycleService::new();
        let result = service
            .record_manual(request.config_path.as_path(), request.to_record_request())
            .map_err(runtime_error)?;
        crate::vault_writer::writeback_from_config(request.config_path.as_path(), &result.entry);
        Ok(DesktopWriteResponse {
            payload: lifecycle_summary::create_payload(
                "record_manual",
                &result.entry,
                &result.snapshot,
            ),
            entry: result.entry,
        })
    }

    pub fn propose_memory(
        self,
        request: DesktopMemoryDraftRequest,
    ) -> DesktopResult<DesktopWriteResponse> {
        validate_memory_draft_request(&request).map_err(input_error)?;
        let service = LifecycleService::new();
        let result = service
            .propose_ai(request.config_path.as_path(), request.to_propose_request())
            .map_err(runtime_error)?;
        crate::vault_writer::writeback_from_config(request.config_path.as_path(), &result.entry);
        Ok(DesktopWriteResponse {
            payload: lifecycle_summary::create_payload("propose", &result.entry, &result.snapshot),
            entry: result.entry,
        })
    }

    pub fn apply_memory_action(
        self,
        request: DesktopMemoryActionRequest,
    ) -> DesktopResult<DesktopWriteResponse> {
        validate_memory_action_request(&request).map_err(input_error)?;
        let service = LifecycleService::new();
        let action = request.action.into_lifecycle_action();
        let result = service
            .apply_action_with_metadata(
                request.config_path.as_path(),
                &request.record_id,
                action,
                request.metadata.into_transition_metadata(),
            )
            .map_err(runtime_error)?;
        crate::vault_writer::writeback_from_config(request.config_path.as_path(), &result.entry);
        Ok(DesktopWriteResponse {
            payload: lifecycle_summary::action_payload(&result.entry, &result.snapshot, action),
            entry: result.entry,
        })
    }

    pub fn browse_sessions(
        self,
        request: DesktopSessionBrowserRequest,
    ) -> DesktopResult<DesktopSessionBrowserResponse> {
        validate_config_path(request.config_path.as_path()).map_err(input_error)?;
        let sessions =
            load_session_index_filtered(request.config_path.as_path(), request.provider.as_deref())
                .map_err(runtime_error)?;
        let page = request.page.max(1);
        let per_page = request.per_page.max(1);
        let total = sessions.len();
        let start = (page - 1) * per_page;
        let paged = sessions
            .into_iter()
            .skip(start)
            .take(per_page)
            .collect::<Vec<_>>();
        let has_more = start + paged.len() < total;

        Ok(DesktopSessionBrowserResponse {
            sessions: paged,
            page,
            per_page,
            total,
            has_more,
        })
    }

    pub fn get_session(
        self,
        request: DesktopSessionDetailRequest,
    ) -> DesktopResult<Option<DesktopSessionDetailResponse>> {
        validate_session_detail_request(&request).map_err(input_error)?;
        let entries = latest_state_entries(&store_from_config_path(request.config_path.as_path()))
            .map_err(runtime_error)?;
        let sessions = load_session_index(request.config_path.as_path()).map_err(runtime_error)?;
        let session = sessions
            .iter()
            .find(|item| item.session_id == request.session_id)
            .cloned();
        let Some(session) = session else {
            return Ok(None);
        };

        let mut records: Vec<_> = entries
            .into_iter()
            .filter(|entry| {
                entry_session_refs(entry).contains(&raw_session_id(&request.session_id))
            })
            .collect();
        records.sort_by(|left, right| right.recorded_at.cmp(&left.recorded_at));

        let offset = request.message_offset.unwrap_or(0);
        let limit = request.message_limit.unwrap_or(0);
        let ProviderSessionMessages {
            messages,
            total_messages,
            has_more_messages,
        } = load_provider_messages(&session, offset, limit).map_err(runtime_error)?;
        let latest_user_message = messages
            .iter()
            .rev()
            .find(|message| message.role == "user")
            .map(|message| message.content.clone());

        Ok(Some(DesktopSessionDetailResponse {
            session,
            records,
            total_messages,
            showing_recent_messages: messages.len(),
            has_more_messages,
            messages,
            latest_user_message,
        }))
    }

    pub fn continue_session(
        self,
        request: DesktopSessionActionRequest,
    ) -> DesktopResult<DesktopSessionActionResponse> {
        validate_session_action_request(&request).map_err(input_error)?;
        let sessions = load_session_index(request.config_path.as_path()).map_err(runtime_error)?;
        let session = sessions
            .into_iter()
            .find(|item| item.session_id == request.session_id)
            .ok_or_else(|| input_error(format!("未找到会话：{}", request.session_id)))?;
        let command = build_continue_command(&session).ok_or_else(|| {
            input_error(format!(
                "当前 provider 暂不支持继续会话：{}",
                session.provider
            ))
        })?;

        launch_terminal_command(&command)
            .map_err(|error| runtime_error(anyhow::anyhow!("继续对话失败：{error}")))?;

        Ok(DesktopSessionActionResponse {
            session_id: session.session_id,
            provider: session.provider,
            command: Some(command),
            message: "已在终端中打开继续对话命令。".to_string(),
        })
    }

    pub fn delete_session(
        self,
        request: DesktopSessionActionRequest,
    ) -> DesktopResult<DesktopSessionActionResponse> {
        validate_session_action_request(&request).map_err(input_error)?;
        let sessions = load_session_index(request.config_path.as_path()).map_err(runtime_error)?;
        let session = sessions
            .into_iter()
            .find(|item| item.session_id == request.session_id)
            .ok_or_else(|| input_error(format!("未找到会话：{}", request.session_id)))?;
        delete_session_file(&session)
            .map_err(|error| runtime_error(anyhow::anyhow!("删除会话失败：{error}")))?;

        Ok(DesktopSessionActionResponse {
            session_id: session.session_id,
            provider: session.provider,
            command: None,
            message: "已删除本地会话文件。".to_string(),
        })
    }

    pub fn collect_status(
        self,
        request: DesktopStatusRequest,
    ) -> DesktopResult<DesktopStatusResponse> {
        validate_config_path(request.config_path.as_path()).map_err(input_error)?;
        let provider_sessions = load_provider_sessions(None).map_err(runtime_error)?;
        Ok(collect_status(
            request.config_path.as_path(),
            request.cwd.as_path(),
            request.vault_root_override.as_deref(),
            provider_sessions.len(),
        ))
    }

    pub fn import_session(
        self,
        request: DesktopImportSessionRequest,
    ) -> DesktopResult<DesktopImportSessionResponse> {
        validate_config_path(request.config_path.as_path()).map_err(input_error)?;
        if request.session_id.trim().is_empty() {
            return Err(input_error("session_id 不能为空".to_string()));
        }
        let provider = memory_importer::ImportProvider::parse(request.provider.as_str())
            .map_err(|err| input_error(err.to_string()))?;
        let response = memory_importer::import_session(
            request.config_path.as_path(),
            provider,
            &request.session_id,
            request.apply,
            request.actor.clone(),
        )
        .map_err(runtime_error)?;
        Ok(response)
    }

    pub fn wiki_lint(
        self,
        request: DesktopWikiLintRequest,
    ) -> DesktopResult<DesktopWikiLintResponse> {
        validate_config_path(request.config_path.as_path()).map_err(input_error)?;
        let vault_root = resolve_vault_root(request.config_path.as_path())?;
        let report = crate::wiki_lint::run_lint_from_config(request.config_path.as_path())
            .map_err(runtime_error)?;
        let markdown = crate::wiki_lint::render_lint_markdown(&report);
        Ok(DesktopWikiLintResponse {
            used_vault_root: vault_root,
            report,
            markdown,
        })
    }

    pub fn read_wiki_index(
        self,
        request: DesktopWikiIndexRequest,
    ) -> DesktopResult<DesktopWikiIndexResponse> {
        validate_config_path(request.config_path.as_path()).map_err(input_error)?;
        let vault_root = resolve_vault_root(request.config_path.as_path())?;
        let markdown =
            crate::wiki_index::load_index_section(&vault_root, request.project_id.as_deref());
        Ok(DesktopWikiIndexResponse {
            used_vault_root: vault_root,
            markdown,
        })
    }
}

fn resolve_vault_root(config_path: &std::path::Path) -> DesktopResult<std::path::PathBuf> {
    let config = app::load(config_path).map_err(runtime_error)?;
    app::resolve_override_path(&config.vault.root, config_path).map_err(runtime_error)
}
