use anyhow::{Context, Result, anyhow};
use clap::Command;
use log::debug;
use std::collections::HashMap;
use std::fs;
use std::io::Read;

use crate::client::KeepClient;
use crate::compression_engine::CompressionType;
use crate::config;
use crate::modes::common::ImportMeta;
use std::str::FromStr;

/// Import an item from a metadata file via client.
///
/// Streams data to server without buffering entire file in memory.
/// Sends original timestamp to server so it's preserved.
pub fn mode(
    client: &KeepClient,
    cmd: &mut Command,
    settings: &config::Settings,
    meta_file: &str,
) -> Result<()> {
    // Read and parse metadata
    let meta_yaml = fs::read_to_string(meta_file)
        .with_context(|| format!("Cannot read metadata file: {}", meta_file))?;
    let import_meta: ImportMeta = serde_yaml::from_str(&meta_yaml)
        .with_context(|| format!("Cannot parse metadata file: {}", meta_file))?;

    // Validate compression type
    CompressionType::from_str(&import_meta.compression).map_err(|_| {
        anyhow!(
            "Invalid compression type '{}' in metadata file",
            import_meta.compression
        )
    })?;

    debug!(
        "CLIENT_IMPORT: Parsed meta: ts={}, compression={}, tags={:?}",
        import_meta.ts, import_meta.compression, import_meta.tags
    );

    // Build query parameters
    let ts_str = import_meta.ts.to_rfc3339();
    let params = [
        ("compress".to_string(), "false".to_string()),
        ("meta".to_string(), "false".to_string()),
        ("tags".to_string(), import_meta.tags.join(",")),
        (
            "compression_type".to_string(),
            import_meta.compression.clone(),
        ),
        ("ts".to_string(), ts_str),
    ];
    let param_refs: Vec<(&str, &str)> = params
        .iter()
        .map(|(k, v)| (k.as_str(), v.as_str()))
        .collect();

    // Stream data to server without buffering entire file
    let item_info = if let Some(ref data_file) = settings.import_data_file {
        let mut reader = fs::File::open(data_file)
            .with_context(|| format!("Cannot read data file: {}", data_file.display()))?;
        client.post_stream("/api/item/", &mut reader, &param_refs)?
    } else {
        // For stdin, we need to buffer since stdin can't be seeked
        // and post_stream may need to retry. Use a BufReader for efficiency.
        let mut buf = Vec::new();
        std::io::stdin()
            .read_to_end(&mut buf)
            .context("Cannot read data from stdin")?;
        if buf.is_empty() {
            cmd.error(
                clap::error::ErrorKind::InvalidValue,
                "No data provided (empty stdin)",
            )
            .exit();
        }
        let mut cursor = std::io::Cursor::new(&buf);
        client.post_stream("/api/item/", &mut cursor, &param_refs)?
    };

    let item_id = item_info.id;
    debug!("CLIENT_IMPORT: Created item {} via server", item_id);

    // Set uncompressed size if known from metadata
    if let Some(size) = import_meta.size {
        client.set_item_size(item_id, size as u64)?;
        debug!("CLIENT_IMPORT: Set size to {}", size);
    }

    // Post metadata
    if !import_meta.metadata.is_empty() {
        client.post_metadata(item_id, &import_meta.metadata)?;
        debug!(
            "CLIENT_IMPORT: Set {} metadata entries",
            import_meta.metadata.len()
        );
    }

    if !settings.quiet {
        println!(
            "KEEP: Imported item {} tags: {:?}",
            item_id, import_meta.tags
        );
    }

    Ok(())
}
