use crate::canonical::{
    CanonicalSchema, CanonicalSession, EventBlock, EventLinks, EventMetadata, EventRole,
    EventSource, ExportedSession, ImportedSession, MappingDirection, MappingDisposition,
    MappingIssue, MappingIssueLevel, MappingReport, ProviderSessionRef, SessionContext,
    SessionEvent, SessionEventKind, SessionIdentity, SessionProvenance,
};
use crate::provider::{
    canonical_block_text, canonical_export_result, canonical_session_title, Provider,
    ProviderCapabilities, ProviderSessionSummary,
};
use crate::utils::truncate_summary;
use anyhow::{Context, Result};
use base64::{engine::general_purpose::STANDARD, Engine as _};
use chrono::Utc;
use serde_json::{json, Value};
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use uuid::Uuid;

pub struct KiroProvider;

const PROVIDER_ID: &str = "kiro";
const TITLE_MAX_CHARS: usize = 80;

impl Provider for KiroProvider {
    fn id(&self) -> &'static str {
        PROVIDER_ID
    }

    fn name(&self) -> &'static str {
        "Kiro"
    }

    fn capabilities(&self) -> ProviderCapabilities {
        ProviderCapabilities {
            scan: true,
            import: true,
            export: true,
            delete: true,
            rename: true,
            resume: false,
        }
    }

    fn scan_sessions(&self) -> Result<Vec<ProviderSessionSummary>> {
        let global_dir = kiro_global_storage_dir()?;
        if !global_dir.exists() {
            return Ok(Vec::new());
        }
        scan_sessions_in(&global_dir)
    }

    fn import_session(&self, source_path: &str) -> Result<ImportedSession> {
        import_canonical_session_from_path(Path::new(source_path))
    }

    fn export_session(
        &self,
        session: &CanonicalSession,
        target_dir: &Path,
    ) -> Result<ExportedSession> {
        let global_dir = kiro_global_storage_dir()?;
        let session_id = export_canonical_session_in(&global_dir, session, target_dir)?;
        Ok(canonical_export_result(
            PROVIDER_ID,
            session_id.clone(),
            self.resume_command(&session_id),
        ))
    }

    fn delete_session(&self, session_id: &str) -> Result<()> {
        let global_dir = kiro_global_storage_dir()?;
        delete_session_in(&global_dir, session_id)
    }

    fn rename_session(&self, session_id: &str, new_title: &str) -> Result<()> {
        let global_dir = kiro_global_storage_dir()?;
        rename_session_in(&global_dir, session_id, new_title)
    }

    fn session_size(&self, session_id: &str) -> Result<u64> {
        let global_dir = kiro_global_storage_dir()?;
        for list_path in session_list_paths(&global_dir)? {
            let Some(session_dir) = list_path.parent() else {
                continue;
            };
            let session_path = session_dir.join(format!("{}.json", session_id));
            if session_path.exists() {
                return Ok(std::fs::metadata(session_path)?.len());
            }
        }
        Ok(0)
    }
}

fn kiro_data_dir() -> Result<PathBuf> {
    #[cfg(target_os = "macos")]
    {
        let home = dirs::home_dir().context("Unable to locate user home directory")?;
        return Ok(home.join("Library/Application Support/Kiro"));
    }

    #[cfg(target_os = "windows")]
    {
        let appdata = std::env::var("APPDATA")
            .map_err(|_| anyhow::anyhow!("APPDATA environment variable not found"))?;
        return Ok(PathBuf::from(appdata).join("Kiro"));
    }

    #[cfg(target_os = "linux")]
    {
        let home = dirs::home_dir().context("Unable to locate user home directory")?;
        return Ok(home.join(".config/Kiro"));
    }

    #[allow(unreachable_code)]
    Err(anyhow::anyhow!(
        "Kiro data directory not supported on this platform"
    ))
}

fn kiro_global_storage_dir() -> Result<PathBuf> {
    Ok(kiro_data_dir()?
        .join("User")
        .join("globalStorage")
        .join("kiro.kiroagent"))
}

fn scan_sessions_in(global_dir: &Path) -> Result<Vec<ProviderSessionSummary>> {
    let mut sessions = Vec::new();

    for list_path in session_list_paths(global_dir)? {
        let Some(session_dir) = list_path.parent() else {
            continue;
        };
        for entry in read_session_list(&list_path)? {
            if entry.get("hidden").and_then(|v| v.as_bool()) == Some(true) {
                continue;
            }

            let Some(session_id) = entry.get("sessionId").and_then(|v| v.as_str()) else {
                continue;
            };

            let session_path = session_dir.join(format!("{}.json", session_id));
            let title = entry
                .get("title")
                .and_then(|v| v.as_str())
                .filter(|value| !value.trim().is_empty())
                .map(str::to_string);
            let project_dir = entry
                .get("workspaceDirectory")
                .and_then(|v| v.as_str())
                .filter(|value| !value.trim().is_empty())
                .map(str::to_string);
            let last_active_at = path_mtime_ms(&session_path)
                .or_else(|| entry.get("dateCreated").and_then(parse_ms));

            sessions.push(ProviderSessionSummary {
                session_id: session_id.to_string(),
                title,
                project_dir,
                last_active_at,
                source_path: Some(session_path.to_string_lossy().to_string()),
            });
        }
    }

    Ok(sessions)
}

fn import_canonical_session_from_path(path: &Path) -> Result<ImportedSession> {
    let raw = std::fs::read_to_string(path)
        .with_context(|| format!("Failed to read Kiro session: {}", path.display()))?;
    let value: Value = serde_json::from_str(&raw)
        .with_context(|| format!("Failed to parse Kiro session: {}", path.display()))?;
    import_canonical_session_from_value(path, value)
}

fn import_canonical_session_from_value(path: &Path, value: Value) -> Result<ImportedSession> {
    let mut report = MappingReport::new(PROVIDER_ID, MappingDirection::Import);
    let fallback_id = path
        .file_stem()
        .and_then(|value| value.to_str())
        .unwrap_or("")
        .to_string();
    let session_id = value
        .get("sessionId")
        .and_then(|v| v.as_str())
        .unwrap_or(&fallback_id)
        .to_string();
    let title = value
        .get("title")
        .and_then(|v| v.as_str())
        .filter(|value| !value.trim().is_empty())
        .map(str::to_string);
    let workspace_dir = value
        .get("workspaceDirectory")
        .and_then(|v| v.as_str())
        .filter(|value| !value.trim().is_empty())
        .map(str::to_string);
    let session_time = path_mtime_ms(path).unwrap_or_else(|| Utc::now().timestamp_millis());
    let session_dt = chrono::DateTime::from_timestamp_millis(session_time).unwrap_or_else(Utc::now);

    let mut events = Vec::new();
    if let Some(history) = value.get("history").and_then(|v| v.as_array()) {
        for (index, item) in history.iter().enumerate() {
            match canonical_event_from_kiro_history_item(index, item, session_dt, &mut report) {
                Some(event) => events.push(event),
                None => report.push_issue(MappingIssue {
                    level: MappingIssueLevel::Info,
                    disposition: MappingDisposition::Dropped,
                    code: "empty_history_item_dropped".to_string(),
                    message: "Dropped Kiro history item without message content".to_string(),
                    path: Some(format!("history:{}", index)),
                    raw: Some(item.clone()),
                }),
            }
        }
    }

    let mut extensions = BTreeMap::new();
    extensions.insert("kiro_session".to_string(), value);

    Ok(ImportedSession {
        session: CanonicalSession {
            schema: CanonicalSchema::default(),
            identity: SessionIdentity {
                canonical_id: session_id.clone(),
                source_title: title,
            },
            provenance: SessionProvenance {
                imported_at: Utc::now(),
                imported_by: Some("memorph-cli".to_string()),
                primary_source: ProviderSessionRef {
                    provider_id: PROVIDER_ID.to_string(),
                    session_id,
                    source_path: Some(path.to_string_lossy().to_string()),
                },
                aliases: Vec::new(),
            },
            context: SessionContext {
                workspace_dir,
                created_at: Some(session_dt),
                last_active_at: Some(session_dt),
                tags: Vec::new(),
            },
            events,
            artifacts: Vec::new(),
            extensions,
        },
        report,
    })
}

fn canonical_event_from_kiro_history_item(
    index: usize,
    item: &Value,
    timestamp: chrono::DateTime<Utc>,
    report: &mut MappingReport,
) -> Option<SessionEvent> {
    let message = item.get("message")?;
    let role_str = message
        .get("role")
        .and_then(|v| v.as_str())
        .unwrap_or("user");
    let role = match role_str {
        "user" => EventRole::User,
        "assistant" | "bot" => EventRole::Assistant,
        "tool" => EventRole::Tool,
        "system" => EventRole::System,
        "developer" => EventRole::Developer,
        other => {
            report.push_issue(MappingIssue {
                level: MappingIssueLevel::Info,
                disposition: MappingDisposition::Normalized,
                code: "unknown_role_normalized".to_string(),
                message: format!("Normalized unknown Kiro role '{}'", other),
                path: Some(format!("history:{}", index)),
                raw: Some(item.clone()),
            });
            EventRole::Unknown
        }
    };
    let blocks = kiro_event_blocks(message.get("content"), item, index, report);
    if blocks.is_empty() {
        return None;
    }
    let id = message
        .get("id")
        .and_then(|v| v.as_str())
        .map(str::to_string)
        .unwrap_or_else(|| format!("kiro:history:{}", index));

    Some(SessionEvent {
        id,
        kind: kiro_event_kind(&blocks),
        role,
        timestamp,
        links: EventLinks {
            parent_event_id: None,
            provider_parent_id: None,
            turn_index: Some(index as u32),
            related_event_ids: Vec::new(),
        },
        blocks,
        metadata: EventMetadata {
            source: EventSource {
                provider_id: PROVIDER_ID.to_string(),
                original_id: message
                    .get("id")
                    .and_then(|v| v.as_str())
                    .map(str::to_string),
                original_role: Some(role_str.to_string()),
                phase: None,
            },
            model: None,
            usage: None,
            fidelity: MappingDisposition::Preserved,
            provider_ext: {
                let mut ext = BTreeMap::new();
                ext.insert("kiro_history_item".to_string(), item.clone());
                ext
            },
        },
    })
}

fn kiro_event_blocks(
    content: Option<&Value>,
    raw_item: &Value,
    index: usize,
    report: &mut MappingReport,
) -> Vec<EventBlock> {
    match content {
        Some(Value::String(text)) => {
            if text.trim().is_empty() {
                Vec::new()
            } else {
                vec![EventBlock::Text { text: text.clone() }]
            }
        }
        Some(Value::Array(items)) => items
            .iter()
            .enumerate()
            .map(|(block_index, item)| {
                kiro_content_event_block(item, raw_item, index, block_index, report)
            })
            .collect(),
        Some(Value::Object(object)) => {
            if let Some(text) = object.get("text").and_then(|v| v.as_str()) {
                if text.trim().is_empty() {
                    Vec::new()
                } else {
                    vec![EventBlock::Text {
                        text: text.to_string(),
                    }]
                }
            } else {
                vec![EventBlock::ProviderPayload {
                    kind: "content".to_string(),
                    payload: content.cloned().unwrap_or(Value::Null),
                }]
            }
        }
        Some(other) => vec![EventBlock::Unknown { raw: other.clone() }],
        None => Vec::new(),
    }
}

fn kiro_content_event_block(
    value: &Value,
    raw_item: &Value,
    index: usize,
    block_index: usize,
    report: &mut MappingReport,
) -> EventBlock {
    if let Some(text) = value.get("text").and_then(|v| v.as_str()) {
        return EventBlock::Text {
            text: text.to_string(),
        };
    }
    if let Some(thinking) = value.get("thinking").and_then(|v| v.as_str()) {
        return EventBlock::Thinking {
            text: thinking.to_string(),
            signature: None,
        };
    }

    let kind = value
        .get("type")
        .and_then(|v| v.as_str())
        .unwrap_or("content");
    report.push_issue(MappingIssue {
        level: MappingIssueLevel::Info,
        disposition: MappingDisposition::Preserved,
        code: "provider_block_preserved".to_string(),
        message: format!("Preserved unsupported Kiro content block '{}'", kind),
        path: Some(format!("history:{}:block:{}", index, block_index)),
        raw: Some(raw_item.clone()),
    });
    EventBlock::ProviderPayload {
        kind: kind.to_string(),
        payload: value.clone(),
    }
}

fn kiro_event_kind(blocks: &[EventBlock]) -> SessionEventKind {
    if blocks
        .iter()
        .any(|block| matches!(block, EventBlock::ToolResult { .. }))
    {
        SessionEventKind::ToolResult
    } else if blocks
        .iter()
        .any(|block| matches!(block, EventBlock::ToolCall { .. }))
    {
        SessionEventKind::ToolCall
    } else if blocks.iter().all(|block| {
        matches!(
            block,
            EventBlock::ProviderPayload { .. } | EventBlock::Unknown { .. }
        )
    }) {
        SessionEventKind::Unknown
    } else {
        SessionEventKind::Message
    }
}

fn export_canonical_session_in(
    global_dir: &Path,
    session: &CanonicalSession,
    target_dir: &Path,
) -> Result<String> {
    let session_id = Uuid::new_v4().to_string();
    let target_dir_str = target_dir.to_string_lossy().to_string();
    let title = truncate_summary(&canonical_session_title(session), TITLE_MAX_CHARS);
    let now = Utc::now().timestamp_millis();
    let created = session
        .context
        .created_at
        .map(|dt| dt.timestamp_millis())
        .unwrap_or(now);

    let history: Vec<Value> = session
        .events
        .iter()
        .filter_map(canonical_event_to_kiro_history_item)
        .collect();

    let session_json = json!({
        "history": history,
        "sessionId": session_id,
        "title": title,
        "workspaceDirectory": target_dir_str,
        "sessionType": "vibe",
        "contextUsagePercentage": 0
    });

    let sessions_dir = sessions_folder(global_dir, Some(&target_dir_str));
    std::fs::create_dir_all(&sessions_dir)?;
    let session_path = sessions_dir.join(format!("{}.json", session_id));
    std::fs::write(&session_path, serde_json::to_string_pretty(&session_json)?)?;

    let list_path = sessions_dir.join("sessions.json");
    upsert_session_list_entry(
        &list_path,
        json!({
            "sessionId": session_id,
            "title": title,
            "dateCreated": created.to_string(),
            "workspaceDirectory": target_dir_str,
            "hidden": false
        }),
    )?;

    Ok(session_id)
}

fn canonical_event_to_kiro_history_item(event: &SessionEvent) -> Option<Value> {
    let content = canonical_event_kiro_content(event);
    if content.is_empty() {
        return None;
    }
    let role = match event.role {
        EventRole::Assistant => "assistant",
        _ => "user",
    };

    Some(json!({
        "message": {
            "id": event.id,
            "role": role,
            "content": content
        },
        "contextItems": [],
        "editorState": {}
    }))
}

fn canonical_event_kiro_content(event: &SessionEvent) -> Vec<Value> {
    event
        .blocks
        .iter()
        .filter_map(|block| match block {
            EventBlock::Text { text } => Some(json!({
                "type": "text",
                "text": text
            })),
            EventBlock::Thinking { text, .. } => Some(json!({
                "thinking": text
            })),
            _ => {
                let text = canonical_block_text(block);
                (!text.trim().is_empty()).then(|| {
                    json!({
                        "type": "text",
                        "text": text
                    })
                })
            }
        })
        .collect()
}

fn delete_session_in(global_dir: &Path, session_id: &str) -> Result<()> {
    let mut found = false;

    for list_path in session_list_paths(global_dir)? {
        let Some(session_dir) = list_path.parent() else {
            continue;
        };

        let mut entries = read_session_list(&list_path)?;
        let original_len = entries.len();
        entries.retain(|entry| {
            entry
                .get("sessionId")
                .and_then(|v| v.as_str())
                .map(|id| id != session_id)
                .unwrap_or(true)
        });
        if entries.len() != original_len {
            write_session_list(&list_path, &entries)?;
            found = true;
        }

        let session_path = session_dir.join(format!("{}.json", session_id));
        if session_path.exists() {
            std::fs::remove_file(session_path)?;
            found = true;
        }
    }

    if !found {
        anyhow::bail!("Kiro session not found: {}", session_id);
    }

    Ok(())
}

fn rename_session_in(global_dir: &Path, session_id: &str, new_title: &str) -> Result<()> {
    let mut found = false;

    for list_path in session_list_paths(global_dir)? {
        let Some(session_dir) = list_path.parent() else {
            continue;
        };

        let mut entries = read_session_list(&list_path)?;
        let mut list_changed = false;
        for entry in &mut entries {
            if entry.get("sessionId").and_then(|v| v.as_str()) == Some(session_id) {
                if let Some(object) = entry.as_object_mut() {
                    object.insert("title".to_string(), Value::String(new_title.to_string()));
                    list_changed = true;
                    found = true;
                }
            }
        }
        if list_changed {
            write_session_list(&list_path, &entries)?;
        }

        let session_path = session_dir.join(format!("{}.json", session_id));
        if session_path.exists() {
            let raw = std::fs::read_to_string(&session_path)?;
            let mut value: Value = serde_json::from_str(&raw)?;
            if let Some(object) = value.as_object_mut() {
                object.insert("title".to_string(), Value::String(new_title.to_string()));
                std::fs::write(&session_path, serde_json::to_string_pretty(&value)?)?;
                found = true;
            }
        }
    }

    if !found {
        anyhow::bail!("Kiro session not found: {}", session_id);
    }

    Ok(())
}

fn session_list_paths(global_dir: &Path) -> Result<Vec<PathBuf>> {
    let mut paths = Vec::new();
    let global_sessions = global_dir.join("sessions").join("sessions.json");
    if global_sessions.exists() {
        paths.push(global_sessions);
    }

    let workspace_root = global_dir.join("workspace-sessions");
    if workspace_root.exists() {
        for entry in std::fs::read_dir(&workspace_root)? {
            let entry = entry?;
            let path = entry.path();
            if path.is_dir() {
                let list_path = path.join("sessions.json");
                if list_path.exists() {
                    paths.push(list_path);
                }
            }
        }
    }

    Ok(paths)
}

fn sessions_folder(global_dir: &Path, workspace_dir: Option<&str>) -> PathBuf {
    match workspace_dir.filter(|value| !value.trim().is_empty()) {
        Some(workspace) => global_dir
            .join("workspace-sessions")
            .join(workspace_hash(workspace)),
        None => global_dir.join("sessions"),
    }
}

fn workspace_hash(path: &str) -> String {
    STANDARD.encode(path).replace(['/', '+', '='], "_")
}

fn read_session_list(path: &Path) -> Result<Vec<Value>> {
    let raw = std::fs::read_to_string(path)
        .with_context(|| format!("Failed to read Kiro session list: {}", path.display()))?;
    if raw.trim().is_empty() {
        return Ok(Vec::new());
    }
    let value: Value = serde_json::from_str(&raw)
        .with_context(|| format!("Failed to parse Kiro session list: {}", path.display()))?;
    Ok(value.as_array().cloned().unwrap_or_default())
}

fn write_session_list(path: &Path, entries: &[Value]) -> Result<()> {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }
    std::fs::write(path, serde_json::to_string_pretty(entries)?)?;
    Ok(())
}

fn upsert_session_list_entry(path: &Path, entry: Value) -> Result<()> {
    let mut entries = if path.exists() {
        read_session_list(path)?
    } else {
        Vec::new()
    };
    let session_id = entry.get("sessionId").and_then(|v| v.as_str());
    let mut replaced = false;

    if let Some(session_id) = session_id {
        for existing in &mut entries {
            if existing.get("sessionId").and_then(|v| v.as_str()) == Some(session_id) {
                *existing = entry.clone();
                replaced = true;
                break;
            }
        }
    }

    if !replaced {
        entries.push(entry);
    }
    write_session_list(path, &entries)
}

fn parse_ms(value: &Value) -> Option<i64> {
    if let Some(ms) = value.as_i64() {
        return Some(ms);
    }
    value.as_str()?.parse::<i64>().ok()
}

fn path_mtime_ms(path: &Path) -> Option<i64> {
    std::fs::metadata(path)
        .ok()?
        .modified()
        .ok()?
        .duration_since(std::time::UNIX_EPOCH)
        .ok()
        .map(|duration| duration.as_millis() as i64)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn scans_and_loads_workspace_session() -> Result<()> {
        let temp = tempfile::tempdir()?;
        let global_dir = temp.path().join("kiro.kiroagent");
        let workspace = "/tmp/kiro-project";
        let sessions_dir = sessions_folder(&global_dir, Some(workspace));
        std::fs::create_dir_all(&sessions_dir)?;

        let session_id = "kiro-session-1";
        write_session_list(
            &sessions_dir.join("sessions.json"),
            &[json!({
                "sessionId": session_id,
                "title": "Hello Kiro",
                "dateCreated": "1700000000000",
                "workspaceDirectory": workspace,
                "hidden": false
            })],
        )?;
        std::fs::write(
            sessions_dir.join(format!("{}.json", session_id)),
            serde_json::to_string_pretty(&json!({
                "history": [
                    {
                        "message": {
                            "id": "m1",
                            "role": "user",
                            "content": [{"type": "text", "text": "hi"}]
                        }
                    },
                    {
                        "message": {
                            "id": "m2",
                            "role": "assistant",
                            "content": [
                                {"thinking": "checking"},
                                {"type": "text", "text": "hello"},
                                {"type": "kiro_extra", "payload": {"ok": true}}
                            ]
                        }
                    }
                ],
                "sessionId": session_id,
                "title": "Hello Kiro",
                "workspaceDirectory": workspace,
                "sessionType": "vibe",
                "contextUsagePercentage": 0
            }))?,
        )?;

        let sessions = scan_sessions_in(&global_dir)?;
        assert_eq!(sessions.len(), 1);
        assert_eq!(sessions[0].session_id, session_id);
        assert_eq!(sessions[0].title.as_deref(), Some("Hello Kiro"));
        assert_eq!(sessions[0].project_dir.as_deref(), Some(workspace));

        let imported = import_canonical_session_from_path(Path::new(
            sessions[0].source_path.as_ref().unwrap(),
        ))?;
        assert_eq!(imported.session.identity.canonical_id, session_id);
        assert_eq!(
            imported.session.identity.source_title.as_deref(),
            Some("Hello Kiro")
        );
        assert_eq!(
            imported.session.context.workspace_dir.as_deref(),
            Some(workspace)
        );
        assert_eq!(imported.session.events.len(), 2);
        assert!(matches!(
            imported.session.events[0].blocks.first(),
            Some(EventBlock::Text { text }) if text == "hi"
        ));
        assert!(imported.session.events[1]
            .blocks
            .iter()
            .any(|block| matches!(
                block,
                EventBlock::Thinking { text, .. } if text == "checking"
            )));
        assert!(imported.session.events[1]
            .blocks
            .iter()
            .any(|block| matches!(
                block,
                EventBlock::ProviderPayload { kind, .. } if kind == "kiro_extra"
            )));

        Ok(())
    }

    #[test]
    fn write_rename_delete_roundtrip() -> Result<()> {
        let temp = tempfile::tempdir()?;
        let global_dir = temp.path().join("kiro.kiroagent");
        let target_dir = temp.path().join("project");
        std::fs::create_dir_all(&target_dir)?;

        let now = Utc::now();
        let source = CanonicalSession {
            schema: CanonicalSchema::default(),
            identity: SessionIdentity {
                canonical_id: "source-session".to_string(),
                source_title: Some("Imported Session".to_string()),
            },
            provenance: SessionProvenance {
                imported_at: now,
                imported_by: Some("memorph-test".to_string()),
                primary_source: ProviderSessionRef {
                    provider_id: PROVIDER_ID.to_string(),
                    session_id: "source-session".to_string(),
                    source_path: None,
                },
                aliases: Vec::new(),
            },
            context: SessionContext {
                workspace_dir: Some(target_dir.to_string_lossy().to_string()),
                created_at: Some(now),
                last_active_at: Some(now),
                tags: Vec::new(),
            },
            events: vec![
                SessionEvent {
                    id: "user-1".to_string(),
                    kind: SessionEventKind::Message,
                    role: EventRole::User,
                    timestamp: now,
                    links: EventLinks::default(),
                    blocks: vec![EventBlock::Text {
                        text: "Build this".to_string(),
                    }],
                    metadata: EventMetadata {
                        source: EventSource {
                            provider_id: PROVIDER_ID.to_string(),
                            original_id: None,
                            original_role: Some("user".to_string()),
                            phase: None,
                        },
                        model: None,
                        usage: None,
                        fidelity: MappingDisposition::Preserved,
                        provider_ext: BTreeMap::new(),
                    },
                },
                SessionEvent {
                    id: "assistant-1".to_string(),
                    kind: SessionEventKind::Message,
                    role: EventRole::Assistant,
                    timestamp: now,
                    links: EventLinks::default(),
                    blocks: vec![EventBlock::Text {
                        text: "Done".to_string(),
                    }],
                    metadata: EventMetadata {
                        source: EventSource {
                            provider_id: PROVIDER_ID.to_string(),
                            original_id: None,
                            original_role: Some("assistant".to_string()),
                            phase: None,
                        },
                        model: None,
                        usage: None,
                        fidelity: MappingDisposition::Preserved,
                        provider_ext: BTreeMap::new(),
                    },
                },
            ],
            artifacts: Vec::new(),
            extensions: BTreeMap::new(),
        };

        let new_id = export_canonical_session_in(&global_dir, &source, &target_dir)?;
        let sessions = scan_sessions_in(&global_dir)?;
        assert_eq!(sessions.len(), 1);
        assert_eq!(sessions[0].session_id, new_id);
        assert_eq!(sessions[0].title.as_deref(), Some("Imported Session"));

        let imported = import_canonical_session_from_path(Path::new(
            sessions[0].source_path.as_ref().unwrap(),
        ))?;
        let canonical_global_dir = temp.path().join("canonical-kiro.kiroagent");
        let canonical_id =
            export_canonical_session_in(&canonical_global_dir, &imported.session, &target_dir)?;
        let canonical_sessions = scan_sessions_in(&canonical_global_dir)?;
        assert_eq!(canonical_sessions.len(), 1);
        assert_eq!(canonical_sessions[0].session_id, canonical_id);
        assert_eq!(
            canonical_sessions[0].title.as_deref(),
            Some("Imported Session")
        );

        rename_session_in(&global_dir, &new_id, "Renamed")?;
        let renamed = scan_sessions_in(&global_dir)?;
        assert_eq!(renamed[0].title.as_deref(), Some("Renamed"));

        delete_session_in(&global_dir, &new_id)?;
        assert!(scan_sessions_in(&global_dir)?.is_empty());

        Ok(())
    }
}
