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 crate::config;
use crate::filter_plugin::FilterChain;
use crate::modes::common::{ExportMeta, sanitize_tags};
use crate::services::item_service::ItemService;

/// Export an item to data and metadata files.
///
/// If no IDs or tags are specified, exports the latest item.
/// Writes `{basename}.data.{compression}` for raw data and `{basename}.meta.yml` for metadata.
pub fn mode_export(
    cmd: &mut Command,
    settings: &config::Settings,
    ids: &mut [i64],
    tags: &mut [String],
    conn: &mut rusqlite::Connection,
    data_path: PathBuf,
    filter_chain: Option<FilterChain>,
) -> Result<()> {
    if !ids.is_empty() && !tags.is_empty() {
        cmd.error(
            clap::error::ErrorKind::InvalidValue,
            "Both ID and tags given, you must supply either IDs or tags when using --export",
        )
        .exit();
    } else if ids.len() > 1 {
        cmd.error(
            clap::error::ErrorKind::InvalidValue,
            "More than one ID given, you must supply exactly one ID when using --export",
        )
        .exit();
    }

    let item_service = ItemService::new(data_path.clone());
    let meta_filter: HashMap<String, Option<String>> = settings
        .meta
        .iter()
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect();
    let item_with_meta = item_service
        .find_item(conn, ids, tags, &meta_filter)
        .map_err(|e| anyhow!("Unable to find matching item: {}", e))?;

    let item_id = item_with_meta.item.id.context("Item missing ID")?;
    let item_tags: Vec<String> = item_with_meta.tags.iter().map(|t| t.name.clone()).collect();
    let meta_map = item_with_meta.meta_as_map();

    // Build template variables
    let mut vars = HashMap::new();
    vars.insert("id".to_string(), item_id.to_string());
    vars.insert("tags".to_string(), sanitize_tags(&item_tags));
    vars.insert(
        "ts".to_string(),
        item_with_meta
            .item
            .ts
            .format("%Y-%m-%dT%H:%M:%SZ")
            .to_string(),
    );
    vars.insert(
        "compression".to_string(),
        item_with_meta.item.compression.clone(),
    );

    let basename = strfmt::strfmt(&settings.export_filename_format, &vars).map_err(|e| {
        anyhow!(
            "Invalid export filename format '{}': {}",
            settings.export_filename_format,
            e
        )
    })?;

    // Write data file
    let data_filename = format!("{}.data.{}", basename, item_with_meta.item.compression);

    let mut item_path = data_path.clone();
    item_path.push(item_id.to_string());

    if filter_chain.is_some() {
        // Apply filters: decompress, filter, write
        let (mut reader, _, _) = item_service.get_item_content_info_streaming_with_chain(
            conn,
            item_id,
            filter_chain.as_ref(),
        )?;
        let mut out_file = fs::File::create(&data_filename)
            .with_context(|| format!("Cannot create data file: {}", data_filename))?;
        let mut buf = [0u8; 8192];
        loop {
            let n = reader.read(&mut buf)?;
            if n == 0 {
                break;
            }
            out_file.write_all(&buf[..n])?;
        }
        debug!("EXPORT: Wrote filtered data to {}", data_filename);
    } else {
        // Raw copy of compressed file
        fs::copy(&item_path, &data_filename)
            .with_context(|| format!("Cannot copy {} to {}", item_path.display(), data_filename))?;
        debug!("EXPORT: Copied raw data to {}", data_filename);
    }

    // Write meta file
    let meta_filename = format!("{}.meta.yml", basename);
    let export_meta = ExportMeta {
        ts: item_with_meta.item.ts,
        compression: item_with_meta.item.compression.clone(),
        size: item_with_meta.item.size,
        tags: item_tags,
        metadata: meta_map,
    };
    let meta_yaml = serde_yaml::to_string(&export_meta)?;
    fs::write(&meta_filename, &meta_yaml)
        .with_context(|| format!("Cannot write meta file: {}", meta_filename))?;
    debug!("EXPORT: Wrote metadata to {}", meta_filename);

    eprintln!("{} {}", data_filename, meta_filename);

    Ok(())
}
