//! Backup command implementations.

use std::fmt::Write;
use std::path::PathBuf;
use tokio::fs;
use uuid::Uuid;

use crate::config::Config;
use crate::output::OutputFormat;

use super::types::*;

pub async fn create_backup(
    memory: &do_memory_core::SelfLearningMemory,
    _config: &Config,
    format: OutputFormat,
    path: PathBuf,
    backup_format: BackupFormat,
    _compress: bool,
    _dry_run: bool,
) -> anyhow::Result<()> {
    let start_time = std::time::Instant::now();

    // Get all episodes - use max limit for backup
    let episodes = if let Some(turso) = memory.turso_storage() {
        turso
            .query_episodes_since(
                chrono::Utc::now() - chrono::Duration::days(365),
                Some(do_memory_core::MAX_QUERY_LIMIT),
            )
            .await?
    } else {
        Vec::new()
    };

    // Get all patterns
    let patterns: Vec<serde_json::Value> = Vec::new();

    // Generate backup ID
    let backup_id = Uuid::new_v4().to_string();

    // Create backup content based on format
    let content = match backup_format {
        BackupFormat::Json => serde_json::to_string_pretty(&serde_json::json!({
            "backup_id": backup_id,
            "timestamp": chrono::Utc::now().to_rfc3339(),
            "episodes": episodes,
            "patterns": patterns,
        }))?,
        BackupFormat::Jsonl => {
            let mut lines = Vec::new();
            for episode in &episodes {
                lines.push(serde_json::to_string(episode)?);
            }
            lines.join("\n")
        }
        BackupFormat::Sql => {
            let mut sql = String::new();
            sql.push_str("-- Backup generated by memory-cli\n");
            writeln!(sql, "-- Backup ID: {}", backup_id)?;
            writeln!(sql, "-- Timestamp: {}", chrono::Utc::now().to_rfc3339())?;
            sql
        }
    };

    // Ensure parent directory exists
    if let Some(parent) = path.parent() {
        if !parent.exists() {
            fs::create_dir_all(parent).await?;
        }
    }

    // If path is a directory, append a default filename
    let backup_path = if path.is_dir() {
        let filename = format!(
            "backup_{}.{}",
            backup_id,
            match backup_format {
                BackupFormat::Json => "json",
                BackupFormat::Jsonl => "jsonl",
                BackupFormat::Sql => "sql",
            }
        );
        path.join(filename)
    } else {
        path
    };

    // Write backup file
    fs::write(&backup_path, &content).await?;

    let result = BackupResult {
        backup_id,
        path: backup_path.to_string_lossy().to_string(),
        format: format!("{:?}", backup_format),
        compressed: false,
        episodes_count: episodes.len(),
        patterns_count: patterns.len(),
        size_bytes: content.len() as u64,
        #[allow(clippy::cast_possible_truncation)]
        duration_ms: start_time.elapsed().as_millis() as u64,
        timestamp: chrono::Utc::now().to_rfc3339(),
    };

    format.print_output(&result)?;
    Ok(())
}

pub async fn list_backups(
    _memory: &do_memory_core::SelfLearningMemory,
    _config: &Config,
    format: OutputFormat,
    path: PathBuf,
) -> anyhow::Result<()> {
    let mut backups = Vec::new();
    let mut total_size = 0u64;

    if path.exists() {
        let mut entries = fs::read_dir(&path).await?;
        while let Some(entry) = entries.next_entry().await? {
            if entry.file_type().await?.is_file() {
                let metadata = entry.metadata().await?;
                total_size += metadata.len();

                let filename = entry.file_name().to_string_lossy().to_string();
                let backup_id = filename.split('.').next().unwrap_or(&filename).to_string();

                backups.push(BackupInfo {
                    id: backup_id,
                    timestamp: chrono::Utc::now().format("%Y-%m-%d %H:%M").to_string(),
                    format: "json".to_string(),
                    compressed: false,
                    episodes_count: 0,
                    patterns_count: 0,
                    size_bytes: metadata.len(),
                    path: entry.path().to_string_lossy().to_string(),
                });
            }
        }
    }

    let list = BackupList {
        backups,
        total_size_bytes: total_size,
    };

    format.print_output(&list)?;
    Ok(())
}

pub async fn restore_backup(
    memory: &do_memory_core::SelfLearningMemory,
    _config: &Config,
    format: OutputFormat,
    path: PathBuf,
    backup_id: String,
    _force: bool,
    _dry_run: bool,
) -> anyhow::Result<()> {
    let start_time = std::time::Instant::now();

    let content = fs::read_to_string(&path).await?;

    let mut errors = Vec::new();
    let mut episodes_restored = 0usize;
    let mut patterns_restored = 0usize;

    // Parse backup content
    let data: Result<serde_json::Value, _> = serde_json::from_str(&content);
    let episodes = data
        .as_ref()
        .ok()
        .and_then(|e| e.get("episodes").and_then(|ep| ep.as_array()));

    for episode in episodes.into_iter().flatten() {
        match serde_json::from_value::<do_memory_core::Episode>(episode.clone()) {
            Ok(ep) => {
                // Store episode in available backends
                let mut stored = false;

                // Try Turso storage first (durable)
                if let Some(turso) = memory.turso_storage() {
                    if turso.store_episode(&ep).await.is_err() {
                        errors.push(format!(
                            "Failed to restore episode {} to Turso",
                            ep.episode_id
                        ));
                    } else {
                        stored = true;
                    }
                }

                // Try cache storage (fast access)
                if let Some(cache) = memory.cache_storage() {
                    if cache.store_episode(&ep).await.is_err() {
                        errors.push(format!(
                            "Failed to restore episode {} to cache",
                            ep.episode_id
                        ));
                    }
                }

                if stored {
                    episodes_restored += 1;
                }
            }
            Err(e) => {
                errors.push(format!("Failed to parse episode: {}", e));
            }
        }
    }

    let result = RestoreResult {
        backup_id,
        episodes_restored,
        patterns_restored,
        #[allow(clippy::cast_possible_truncation)]
        duration_ms: start_time.elapsed().as_millis() as u64,
        errors,
    };

    format.print_output(&result)?;
    Ok(())
}

pub async fn verify_backup(
    _memory: &do_memory_core::SelfLearningMemory,
    _config: &Config,
    format: OutputFormat,
    path: PathBuf,
    backup_id: String,
) -> anyhow::Result<()> {
    let mut issues = Vec::new();
    let mut episodes_count = 0usize;
    let mut patterns_count = 0usize;

    // Check if file exists and validate
    if path.exists() {
        // Try to read and parse
        match fs::read_to_string(&path).await {
            Ok(content) => {
                // Basic validation
                if content.is_empty() {
                    issues.push("Backup file is empty".to_string());
                }

                // Try to parse as JSON
                if let Ok(data) = serde_json::from_str::<serde_json::Value>(&content) {
                    if let Some(episodes) = data.get("episodes").and_then(|e| e.as_array()) {
                        episodes_count = episodes.len();
                    }
                    if let Some(patterns) = data.get("patterns").and_then(|p| p.as_array()) {
                        patterns_count = patterns.len();
                    }
                } else {
                    issues.push("Invalid JSON format".to_string());
                }
            }
            Err(e) => {
                issues.push(format!("Failed to read backup file: {}", e));
            }
        }
    } else {
        issues.push(format!("Backup file not found: {}", path.display()));
    }

    let result = VerifyResult {
        backup_id,
        is_valid: issues.is_empty(),
        issues,
        episodes_count,
        patterns_count,
    };

    format.print_output(&result)?;
    Ok(())
}
