use anyhow::{Context, Result, anyhow};
use chrono::{DateTime, Utc};
use clap::Command;
use log::debug;
use std::collections::HashMap;
use std::fs;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::str::FromStr;

use crate::common::PIPESIZE;
use crate::compression_engine::CompressionType;
use crate::config;
use crate::db;
use crate::modes::common::ImportMeta;

/// Import an item from a metadata file and optional data file.
///
/// If `import_data_file` is not provided, reads data from stdin.
pub fn mode_import(
    cmd: &mut Command,
    settings: &config::Settings,
    meta_file: &str,
    conn: &mut rusqlite::Connection,
    data_path: PathBuf,
) -> Result<()> {
    // Read 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!(
        "IMPORT: Parsed meta: ts={}, compression={}, tags={:?}",
        import_meta.ts, import_meta.compression, import_meta.tags
    );

    // Create item with original timestamp
    let item = db::insert_item_with_ts(conn, import_meta.ts, &import_meta.compression)?;
    let item_id = item.id.context("New item missing ID")?;

    debug!(
        "IMPORT: Created item {} with compression {}",
        item_id, import_meta.compression
    );

    // Set tags
    if !import_meta.tags.is_empty() {
        db::set_item_tags(conn, item.clone(), &import_meta.tags)?;
        debug!("IMPORT: Set {} tags", import_meta.tags.len());
    }

    // Write data to storage using streaming copy
    let mut item_path = data_path;
    item_path.push(item_id.to_string());

    let data_size: i64 = if let Some(ref data_file) = settings.import_data_file {
        // Stream from file to storage using fixed-size buffers
        let mut reader = fs::File::open(data_file)
            .with_context(|| format!("Cannot read data file: {}", data_file.display()))?;
        let mut writer = fs::File::create(&item_path)
            .with_context(|| format!("Cannot create item file: {}", item_path.display()))?;
        let mut buf = [0u8; PIPESIZE];
        let mut total = 0i64;
        loop {
            let n = reader.read(&mut buf)?;
            if n == 0 {
                break;
            }
            writer.write_all(&buf[..n])?;
            total += n as i64;
        }
        total
    } else {
        // Stream from stdin to storage
        let mut writer = fs::File::create(&item_path)
            .with_context(|| format!("Cannot create item file: {}", item_path.display()))?;
        let mut stdin = std::io::stdin().lock();
        let mut buf = [0u8; PIPESIZE];
        let mut total = 0i64;
        loop {
            let n = stdin.read(&mut buf)?;
            if n == 0 {
                break;
            }
            writer.write_all(&buf[..n])?;
            total += n as i64;
        }
        total
    };

    if data_size == 0 {
        cmd.error(
            clap::error::ErrorKind::InvalidValue,
            "No data provided (empty file or stdin)",
        )
        .exit();
    }

    debug!(
        "IMPORT: Wrote {} bytes to {}",
        data_size,
        item_path.display()
    );

    // Set metadata
    for (key, value) in &import_meta.metadata {
        db::query_upsert_meta(
            conn,
            db::Meta {
                id: item_id,
                name: key.clone(),
                value: value.clone(),
            },
        )?;
    }
    if !import_meta.metadata.is_empty() {
        debug!(
            "IMPORT: Set {} metadata entries",
            import_meta.metadata.len()
        );
    }

    // Update item size (use imported size if available, otherwise data length)
    let size_to_record = import_meta.size.unwrap_or(data_size);
    let mut updated_item = item;
    updated_item.size = Some(size_to_record);
    db::update_item(conn, updated_item)?;

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

    Ok(())
}
