/// List mode implementation.
///
/// This module provides the functionality to list stored items with customizable
/// formatting, filtering by tags, and support for different output formats
/// including table, JSON, and YAML.
use crate::config;
use crate::modes::common::ColumnType;
use crate::modes::common::{OutputFormat, apply_color, apply_table_attribute, format_size};
use crate::services::item_service::ItemService;
use crate::services::types::ItemWithMeta;
use anyhow::{Context, Result};
use comfy_table::CellAlignment;
use comfy_table::{Attribute, Cell, Color, Row};
use serde::{Deserialize, Serialize};
use serde_json;
use serde_yaml;

/// Structure representing a list item for structured output formats.
///
/// This struct holds all the information needed to serialize an item for JSON or
/// YAML output in list mode.
#[derive(Serialize, Deserialize)]
struct ListItem {
    /// Item ID.
    ///
    /// The unique identifier for the item.
    id: Option<i64>,
    /// Timestamp.
    ///
    /// The formatted timestamp string for the item.
    time: String,
    /// Size in bytes.
    ///
    /// The raw size of the item content.
    size: Option<u64>,
    /// Formatted size.
    ///
    /// Human-readable size string.
    size_formatted: String,
    /// Compression type.
    ///
    /// The compression algorithm used for the item.
    compression: String,
    /// File size in bytes.
    ///
    /// The size of the stored file on disk.
    file_size: Option<u64>,
    /// Formatted file size.
    ///
    /// Human-readable file size string.
    file_size_formatted: String,
    /// File path.
    ///
    /// The full path to the item's storage file.
    file_path: String,
    /// Tags.
    ///
    /// Vector of tag names associated with the item.
    tags: Vec<String>,
    /// Metadata.
    ///
    /// HashMap of metadata key-value pairs.
    meta: std::collections::HashMap<String, String>,
}

/// Main list mode function.
///
/// This function handles the listing of items based on tags, applying formatting
/// and output options from settings. It supports table, JSON, and YAML output formats.
///
/// # Arguments
///
/// * `cmd` - Mutable reference to the Clap command for error handling.
/// * `settings` - Reference to application settings.
/// * `ids` - Mutable vector of item IDs (should be empty for list mode).
/// * `tags` - Reference to vector of tags for filtering.
/// * `conn` - Mutable reference to database connection.
/// * `data_path` - Path to the data directory.
///
/// # Returns
///
/// * `Result<()>` - Success or error if listing fails.
pub fn mode_list(
    cmd: &mut clap::Command,
    settings: &config::Settings,
    ids: &mut [i64],
    tags: &[String],
    conn: &mut rusqlite::Connection,
    data_path: std::path::PathBuf,
) -> Result<()> {
    if !ids.is_empty() {
        cmd.error(
            clap::error::ErrorKind::InvalidValue,
            "ID given, you can only supply tags when using --list",
        )
        .exit();
    }

    let item_service = ItemService::new(data_path.clone());
    let meta_filter: std::collections::HashMap<String, Option<String>> = settings
        .meta
        .iter()
        .map(|(k, v)| (k.clone(), v.clone()))
        .collect();
    let items_with_meta = item_service.list_items(conn, tags, &meta_filter)?;

    let output_format = crate::modes::common::settings_output_format(settings);

    if output_format != OutputFormat::Table {
        return show_list_structured(items_with_meta, data_path, settings, output_format);
    }

    let mut table = crate::modes::common::create_table_with_config(&settings.table_config);

    // Create header row
    let mut header_cells = Vec::new();
    for column in &settings.list_format {
        header_cells.push(Cell::new(&column.label).add_attribute(Attribute::Bold));
    }
    table.set_header(header_cells);

    for item_with_meta in items_with_meta {
        let tags: Vec<String> = item_with_meta.tags.iter().map(|t| t.name.clone()).collect();
        let meta = item_with_meta.meta_as_map();
        let item = item_with_meta.item;

        let mut item_path = data_path.clone();
        item_path.push(item.id.context("Item missing ID")?.to_string());

        let mut table_row = Row::new();

        for column in &settings.list_format {
            let column_type = column
                .name
                .parse::<ColumnType>()
                .with_context(|| format!("Unknown column type {:?} in list format", column.name))?;

            let mut meta_name: Option<&str> = None;

            if let ColumnType::Meta = column_type {
                let parts: Vec<&str> = column.name.split(':').collect();
                if parts.len() > 1 {
                    meta_name = Some(parts[1]);
                }
            }

            let cell_content = match column_type {
                ColumnType::Id => item.id.unwrap_or(0).to_string(),
                ColumnType::Time => item
                    .ts
                    .with_timezone(&chrono::Local)
                    .format("%F %T")
                    .to_string(),
                ColumnType::Size => match item.size {
                    Some(size) => format_size(size as u64, settings.human_readable),
                    None => match item_path.metadata() {
                        Ok(_) => "Unknown".to_string(),
                        Err(_) => "Missing".to_string(),
                    },
                },
                ColumnType::Compression => item.compression.to_string(),
                ColumnType::FileSize => match item_path.metadata() {
                    Ok(metadata) => format_size(metadata.len(), settings.human_readable),
                    Err(_) => "Missing".to_string(),
                },
                ColumnType::FilePath => item_path
                    .clone()
                    .into_os_string()
                    .into_string()
                    .unwrap_or_else(|os| os.to_string_lossy().into_owned()),
                ColumnType::Tags => tags.join(" "),
                ColumnType::Meta => match meta_name {
                    Some(meta_name) => match meta.get(meta_name) {
                        Some(meta_value) => meta_value.to_string(),
                        None => "".to_string(),
                    },
                    None => "".to_string(),
                },
            };

            // Truncate content to max 3 lines
            let mut cell_lines: Vec<String> =
                cell_content.split('\n').map(|s| s.to_string()).collect();
            if cell_lines.len() > 3 {
                cell_lines.truncate(3);
                // Add ellipsis to the last line if we truncated
                if let Some(last_line) = cell_lines.last_mut() {
                    if last_line.len() > 3 {
                        last_line.truncate(last_line.len() - 3);
                    }
                    last_line.push_str("...");
                }
            }
            let truncated_content = cell_lines.join("\n");

            let mut cell = Cell::new(truncated_content);

            // Apply column-specific styling
            if let Some(fg_color) = &column.fg_color {
                cell = apply_color(cell, fg_color, true);
            }

            if let Some(bg_color) = &column.bg_color {
                cell = apply_color(cell, bg_color, false);
            }

            for attribute in &column.attributes {
                cell = apply_table_attribute(cell, attribute);
            }

            // Apply padding if specified
            if let Some((_left_padding, _right_padding)) = column.padding {
                // Note: comfy-table doesn't directly support padding, so we'd need to handle this
                // by adding spaces to the content, or use a different approach
            }

            // Apply styling for specific cases
            match column_type {
                ColumnType::Size => {
                    if item.size.is_none() {
                        if item_path.metadata().is_ok() {
                            cell = cell
                                .fg(comfy_table::Color::Yellow)
                                .add_attribute(Attribute::Bold);
                        } else {
                            cell = cell
                                .fg(comfy_table::Color::Red)
                                .add_attribute(Attribute::Bold);
                        }
                    }
                }
                ColumnType::FileSize => {
                    if item_path.metadata().is_err() {
                        cell = cell
                            .fg(comfy_table::Color::Red)
                            .add_attribute(Attribute::Bold);
                    }
                }
                _ => {}
            }

            // Apply alignment
            cell = match column.align {
                crate::config::ColumnAlignment::Right => cell.set_alignment(CellAlignment::Right),
                crate::config::ColumnAlignment::Left => cell.set_alignment(CellAlignment::Left),
                crate::config::ColumnAlignment::Center => cell.set_alignment(CellAlignment::Center),
            };
            table_row.add_cell(cell);
        }
        table.add_row(table_row);
    }

    println!(
        "{}",
        crate::modes::common::trim_lines_end(&table.trim_fmt())
    );

    Ok(())
}

fn show_list_structured(
    items_with_meta: Vec<ItemWithMeta>,
    data_path: std::path::PathBuf,
    settings: &config::Settings,
    output_format: OutputFormat,
) -> Result<()> {
    let mut list_items = Vec::new();

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

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

        let file_size = item_path.metadata().map(|m| m.len()).ok();
        let file_size_formatted = match file_size {
            Some(size) => crate::modes::common::format_size(size, settings.human_readable),
            None => "Missing".to_string(),
        };

        let size_formatted = match item.size {
            Some(size) => crate::modes::common::format_size(size as u64, settings.human_readable),
            None => "Unknown".to_string(),
        };

        let list_item = ListItem {
            id: item.id,
            time: item
                .ts
                .with_timezone(&chrono::Local)
                .format("%F %T")
                .to_string(),
            size: item.size.map(|s| s as u64),
            size_formatted,
            compression: item.compression,
            file_size,
            file_size_formatted,
            file_path: item_path.into_os_string().into_string().unwrap_or_default(),
            tags,
            meta,
        };

        list_items.push(list_item);
    }

    crate::modes::common::print_serialized(&list_items, &output_format)?;

    Ok(())
}
