use anyhow::{Context, Result};
use std::io::Read;
use std::path::{Path, PathBuf};

use crate::common::PIPESIZE;
use crate::config;
use crate::db;
use crate::services::compression_service::CompressionService;
use crate::services::meta_service::MetaService;
use clap::Command;
use log::debug;
use rusqlite::Connection;

/// Handles the update mode: modifies tags and metadata for an existing item by ID.
///
/// This function processes a single item ID, updating its metadata based on `--meta`
/// arguments and optionally replacing its tags with positional arguments.
/// If the item's size is not set, it backfills it by streaming through the content file.
///
/// # Arguments
///
/// * `cmd` - Clap command for error handling.
/// * `settings` - Global settings containing metadata and meta plugin config.
/// * `ids` - List containing exactly one item ID.
/// * `conn` - Database connection.
/// * `data_path` - Path to data directory.
///
/// # Returns
///
/// `Result<()>` on success, or an error if the update fails.
pub fn mode_update(
    cmd: &mut Command,
    settings: &config::Settings,
    ids: &mut [i64],
    tags: &mut Vec<String>,
    conn: &mut Connection,
    data_path: PathBuf,
) -> Result<()> {
    if ids.len() != 1 {
        cmd.error(
            clap::error::ErrorKind::InvalidValue,
            "--update requires exactly one numeric ID",
        )
        .exit();
    }

    let item_id = ids[0];

    // Look up the item
    let item =
        db::get_item(conn, item_id)?.ok_or_else(|| anyhow::anyhow!("Item {item_id} not found"))?;

    debug!("UPDATE: Found item {item_id}: {item:?}");

    // Parse --meta arguments into set and delete lists
    let mut set_meta: Vec<(String, String)> = Vec::new();
    let mut delete_keys: Vec<String> = Vec::new();

    for (key, value) in &settings.meta {
        match value {
            Some(v) => set_meta.push((key.clone(), v.clone())),
            None => delete_keys.push(key.clone()),
        }
    }

    // Apply metadata changes
    for (key, value) in &set_meta {
        debug!("UPDATE: Setting meta {key}={value}");
        db::store_meta(
            conn,
            db::Meta {
                id: item_id,
                name: key.clone(),
                value: value.clone(),
            },
        )?;
    }

    for key in &delete_keys {
        debug!("UPDATE: Deleting meta {key}");
        db::query_delete_meta(
            conn,
            db::Meta {
                id: item_id,
                name: key.clone(),
                value: String::new(),
            },
        )?;
    }

    // Replace tags if provided
    if !tags.is_empty() {
        debug!("UPDATE: Replacing tags with {:?}", tags);
        db::set_item_tags(conn, item.clone(), tags)?;
    }

    // Run meta plugins if --meta-plugin flags are provided
    let plugin_names = settings.meta_plugins_names();
    if !plugin_names.is_empty() {
        debug!("UPDATE: Running meta plugins: {:?}", plugin_names);
        run_meta_plugins_on_item(conn, cmd, settings, &data_path, &item, item_id)?;
    }

    // Backfill size if not set
    let mut updated_item = item.clone();
    if item.size.is_none() {
        debug!("UPDATE: Size not set, backfilling from content file");
        if let Some(size) = compute_item_size(&data_path, &item) {
            debug!("UPDATE: Computed size: {size}");
            updated_item.size = Some(size);
            db::update_item(conn, updated_item.clone())?;
        }
    }

    // Print confirmation
    if !settings.quiet {
        let mut parts = Vec::new();
        if !set_meta.is_empty() {
            parts.push(format!("set {} metadata", set_meta.len()));
        }
        if !delete_keys.is_empty() {
            parts.push(format!("deleted {} metadata", delete_keys.len()));
        }
        if !tags.is_empty() {
            parts.push(format!("tags: {}", tags.join(" ")));
        }
        let action = if parts.is_empty() {
            "no changes".to_string()
        } else {
            parts.join(", ")
        };

        eprintln!("KEEP: Updated item {item_id} ({action})");
    }

    Ok(())
}

/// Computes the decompressed size of an item by streaming through its content file.
///
/// Reads the compressed file in PIPESIZE chunks and counts total decompressed bytes.
/// Returns None if the file doesn't exist or decompression fails.
fn compute_item_size(data_path: &Path, item: &db::Item) -> Option<i64> {
    let item_id = item.id?;
    let mut item_path = data_path.to_path_buf();
    item_path.push(item_id.to_string());

    if !item_path.exists() {
        debug!("UPDATE: Content file not found: {item_path:?}");
        return None;
    }

    let compression_service = CompressionService::new();
    let mut reader = match compression_service.stream_item_content(item_path, &item.compression) {
        Ok(r) => r,
        Err(e) => {
            debug!("UPDATE: Failed to open content stream: {e}");
            return None;
        }
    };

    let mut buffer = [0u8; PIPESIZE];
    let mut total_bytes: i64 = 0;

    loop {
        match reader.read(&mut buffer) {
            Ok(0) => break,
            Ok(n) => {
                total_bytes += n as i64;
            }
            Err(e) => {
                debug!("UPDATE: Error reading content: {e}");
                return None;
            }
        }
    }

    Some(total_bytes)
}

/// Runs meta plugins on an existing item's content and stores the results.
fn run_meta_plugins_on_item(
    conn: &mut Connection,
    cmd: &mut Command,
    settings: &config::Settings,
    data_path: &Path,
    item: &db::Item,
    item_id: i64,
) -> Result<()> {
    let mut item_path = data_path.to_path_buf();
    item_path.push(item_id.to_string());

    if !item_path.exists() {
        debug!("UPDATE: Content file not found: {item_path:?}");
        return Ok(());
    }

    // Collect metadata in memory
    let (meta_service, collected_meta) = MetaService::with_collector();
    let mut plugins = meta_service.get_plugins(cmd, settings);

    if plugins.is_empty() {
        return Ok(());
    }

    let compression_service = CompressionService::new();
    let mut reader = compression_service.stream_item_content(item_path, &item.compression)?;

    meta_service.initialize_plugins(&mut plugins);

    crate::common::stream_copy(&mut reader, |chunk| {
        meta_service.process_chunk(&mut plugins, chunk);
        Ok(())
    })?;

    meta_service.finalize_plugins(&mut plugins);

    // Write collected plugin metadata to DB
    if let Ok(entries) = collected_meta.lock() {
        for (name, value) in entries.iter() {
            db::add_meta(conn, item_id, name, value)?;
        }
    }

    Ok(())
}
