// src/cli/display.rs

use crate::domain::bookmark::Bookmark;
use crate::domain::search::SemanticSearchResult;
use crate::util::helper::{format_file_path, format_mtime};
use chrono::{DateTime, Utc};
use crossterm::style::Stylize;
use derive_builder::Builder;
use std::fmt;
use std::io::{self, IsTerminal, Write};

#[derive(Debug, Clone, PartialEq)]
pub enum DisplayField {
    Id,
    Url,
    Title,
    Description,
    Tags,
    AccessCount,
    LastUpdateTs,
    Similarity,
    Embedding,
    Embeddable,
}

impl fmt::Display for DisplayField {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            DisplayField::Id => write!(f, "ID"),
            DisplayField::Url => write!(f, "URL"),
            DisplayField::Title => write!(f, "Title"),
            DisplayField::Description => write!(f, "Description"),
            DisplayField::Tags => write!(f, "Tags"),
            DisplayField::AccessCount => write!(f, "Access Count"),
            DisplayField::LastUpdateTs => write!(f, "Last Updated"),
            DisplayField::Similarity => write!(f, "Similarity"),
            DisplayField::Embedding => write!(f, "Embedding"),
            DisplayField::Embeddable => write!(f, "Embeddable"),
        }
    }
}

pub const DEFAULT_FIELDS: &[DisplayField] = &[
    DisplayField::Id,
    DisplayField::Url,
    DisplayField::Title,
    DisplayField::Description,
    DisplayField::Tags,
    DisplayField::LastUpdateTs, // shows BOTH timestamps
    DisplayField::Similarity,
];

pub const ALL_FIELDS: &[DisplayField] = &[
    DisplayField::Id,
    DisplayField::Url,
    DisplayField::Title,
    DisplayField::Description,
    DisplayField::Tags,
    DisplayField::AccessCount,
    DisplayField::LastUpdateTs, // shows BOTH timestamps
    DisplayField::Similarity,
    DisplayField::Embedding,
    DisplayField::Embeddable,
];

#[derive(Debug, Clone, Builder)]
#[builder(setter(into))]
pub struct DisplayBookmark {
    #[builder(default = "0")]
    pub id: i32,

    #[builder(default)]
    pub url: String,

    #[builder(default)]
    pub title: String,

    #[builder(default)]
    pub description: String,

    #[builder(default)]
    pub tags: String,

    #[builder(default = "0")]
    pub access_count: i32,

    #[builder(default)]
    pub created_at: Option<DateTime<Utc>>,

    #[builder(default = "chrono::Utc::now()")]
    pub last_update_ts: DateTime<Utc>,

    #[builder(default)]
    pub similarity: Option<f64>,

    #[builder(default)]
    pub embedding: String,

    #[builder(default = "false")]
    pub embeddable: bool,

    #[builder(default)]
    pub file_path: Option<String>,

    #[builder(default)]
    pub file_mtime: Option<i32>,
}

impl DisplayBookmark {
    pub fn from_domain(bookmark: &Bookmark) -> Self {
        let url = bookmark.url.clone();

        DisplayBookmarkBuilder::default()
            .id(bookmark.id.unwrap_or(0))
            .url(url)
            .title(bookmark.title.to_string())
            .description(bookmark.description.to_string())
            .tags(bookmark.formatted_tags())
            .access_count(bookmark.access_count)
            .created_at(bookmark.created_at) // Pass through the Option<DateTime<Utc>>
            .last_update_ts(bookmark.updated_at)
            .embedding(
                bookmark
                    .embedding
                    .as_ref()
                    .map_or_else(String::new, |_| "yes".to_string()),
            )
            .embeddable(bookmark.embeddable)
            .file_path(bookmark.file_path.clone())
            .file_mtime(bookmark.file_mtime)
            .build()
            .unwrap()
    }

    pub fn get_value(&self, field: &DisplayField) -> String {
        match field {
            DisplayField::Embeddable => if self.embeddable { "yes" } else { "no" }.to_string(),
            DisplayField::Id => self.id.to_string(),
            DisplayField::Url => self.url.clone(),
            DisplayField::Title => self.title.clone(),
            DisplayField::Description => self.description.clone(),
            DisplayField::Tags => self.tags.clone(),
            DisplayField::AccessCount => self.access_count.to_string(),
            DisplayField::LastUpdateTs => self.last_update_ts.to_string(),
            DisplayField::Similarity => self.similarity.map_or_else(String::new, |s| s.to_string()),
            DisplayField::Embedding => self.embedding.clone(),
        }
    }
    pub fn from_semantic_result(result: &SemanticSearchResult) -> Self {
        let mut builder = DisplayBookmarkBuilder::default();

        // Start with the base bookmark fields
        let base = Self::from_domain(&result.bookmark);

        // Build with all the base fields plus similarity
        builder
            .id(base.id)
            .url(base.url)
            .title(base.title)
            .description(base.description)
            .tags(base.tags)
            .access_count(base.access_count)
            .created_at(base.created_at)
            .last_update_ts(base.last_update_ts)
            .embedding(base.embedding)
            .embeddable(base.embeddable)
            .similarity(Some(result.similarity))
            .build()
            .unwrap()
    }
}

// Implement Default directly instead of deriving it,
// as we already provide defaults in the builder
impl Default for DisplayBookmark {
    fn default() -> Self {
        Self {
            id: 0,
            url: String::new(),
            title: String::new(),
            description: String::new(),
            tags: String::new(),
            access_count: 0,
            created_at: None,
            last_update_ts: Utc::now(),
            similarity: None,
            embedding: String::new(),
            embeddable: false,
            file_path: None,
            file_mtime: None,
        }
    }
}

/// Display bookmarks with color formatting
pub fn show_bookmarks(
    bookmarks: &[DisplayBookmark],
    fields: &[DisplayField],
    settings: &crate::config::Settings,
) {
    if bookmarks.is_empty() {
        eprintln!("No bookmarks to display");
        return;
    }

    let use_color = io::stderr().is_terminal();
    let mut stderr = io::stderr().lock();
    let first_col_width = bookmarks.len().to_string().len();

    for (i, bm) in bookmarks.iter().enumerate() {
        // Title/Metadata (green)
        if fields.contains(&DisplayField::Title) {
            let title_line = format!("{:first_col_width$}. {}", i + 1, bm.title);
            let _ = write!(&mut stderr, "{}", if use_color { title_line.green().to_string() } else { title_line });
        }

        // Similarity score if available
        if let Some(similarity) = bm.similarity {
            if fields.contains(&DisplayField::Similarity) {
                let _ = write!(&mut stderr, " [{:.3}]", similarity);
            }
        }

        // ID
        if fields.contains(&DisplayField::Id) {
            let _ = writeln!(&mut stderr, " [{}]", bm.id);
        } else {
            let _ = writeln!(&mut stderr);
        }

        // URL (yellow)
        if fields.contains(&DisplayField::Url) {
            let formatted_url = if bm.url.contains('\n') {
                bm.url.replace('\n', "\n    ")
            } else {
                bm.url.clone()
            };
            let url_line = format!("{:first_col_width$}  {}", "", formatted_url);
            let _ = writeln!(&mut stderr, "{}", if use_color { url_line.yellow().to_string() } else { url_line });
        }

        // Description
        if fields.contains(&DisplayField::Description) && !bm.description.is_empty() {
            let _ = writeln!(&mut stderr, "{:first_col_width$}  {}", "", bm.description);
        }

        // Tags (blue)
        if fields.contains(&DisplayField::Tags) {
            let tags = bm.tags.replace(',', " ");
            if tags.find(|c: char| !c.is_whitespace()).is_some() {
                let tag_line = format!("{:first_col_width$}  {}", "", tags.trim());
                let _ = writeln!(&mut stderr, "{}", if use_color { tag_line.blue().to_string() } else { tag_line });
            }
        }

        // Access count and embedding status
        let mut flags_and_embedding_line = String::new();

        if fields.contains(&DisplayField::AccessCount) {
            flags_and_embedding_line.push_str(&format!("Count: {}", bm.access_count));
        }

        if fields.contains(&DisplayField::Embedding) {
            let embed_status = if bm.embedding.is_empty() { "null" } else { "yes" };
            if !flags_and_embedding_line.is_empty() {
                flags_and_embedding_line.push_str(" | ");
            }
            flags_and_embedding_line.push_str(&format!("embed: {}", embed_status));
        }

        // Embeddable status
        if fields.contains(&DisplayField::Embeddable) {
            let _ = writeln!(
                &mut stderr,
                "{:first_col_width$}  Embeddable: {}",
                "",
                if bm.embeddable { "yes" } else { "no" }
            );
        }

        // Print access count and embedding info if any exist
        if !flags_and_embedding_line.is_empty() {
            let _ = writeln!(
                &mut stderr,
                "{:first_col_width$}  {}",
                "", flags_and_embedding_line
            );
        }

        // Created and Last update timestamps (magenta)
        if fields.contains(&DisplayField::LastUpdateTs) {
            let created_str = match bm.created_at {
                Some(created) => created.to_string(),
                None => "null".to_string(),
            };
            let ts_line = format!(
                "{:first_col_width$}  Created: {} | Updated: {}",
                "", created_str, bm.last_update_ts
            );
            let _ = writeln!(&mut stderr, "{}", if use_color { ts_line.magenta().to_string() } else { ts_line });
        }

        // File info (dark_grey) - show if present and enabled
        if settings.fzf_opts.show_file_info {
            if let (Some(file_path), Some(file_mtime)) = (&bm.file_path, bm.file_mtime) {
                let formatted_path = format_file_path(file_path, 120);
                let formatted_time = format_mtime(file_mtime);
                let file_line = format!(
                    "{:first_col_width$}  📁 {} ({})",
                    "", formatted_path, formatted_time
                );
                let _ = writeln!(&mut stderr, "{}", if use_color { file_line.dark_grey().to_string() } else { file_line });
            }
        }

        let _ = writeln!(&mut stderr);
    }
}

#[cfg(test)]
mod display_tests {
    use super::*;
    use chrono::Utc;
    use std::{fs, path::Path};

    fn create_test_bookmarks() -> Vec<DisplayBookmark> {
        // Create a common timestamp for testing
        let now = Utc::now();
        let earlier = now - chrono::Duration::days(30);

        vec![
            DisplayBookmark {
                id: 1,
                url: "https://www.rust-lang.org".to_string(),
                title: "The Rust Programming Language".to_string(),
                description:
                    "A language empowering everyone to build reliable and efficient software."
                        .to_string(),
                tags: ",rust,programming,systems,".to_string(),
                access_count: 42,
                created_at: Some(earlier), // Created 30 days ago
                last_update_ts: now,       // Updated now
                similarity: Some(0.85),
                embedding: "yes".to_string(),
                embeddable: true,
                file_path: None,
                file_mtime: None,
            },
            DisplayBookmark {
                id: 2,
                url: "https://doc.rust-lang.org/book/".to_string(),
                title: "The Rust Book".to_string(),
                description: "The Rust Programming Language Book".to_string(),
                tags: ",book,documentation,rust,learning,".to_string(),
                access_count: 24,
                created_at: None,    // No creation date
                last_update_ts: now, // Updated now
                similarity: None,
                embedding: "".to_string(),
                embeddable: false,
                file_path: None,
                file_mtime: None,
            },
            DisplayBookmark {
                id: 3,
                url: "https://crates.io".to_string(),
                title: "Rust Package Registry".to_string(),
                description: "".to_string(), // Empty description
                tags: ",crates,registry,".to_string(),
                access_count: 12,
                created_at: Some(now), // Created now (same as updated)
                last_update_ts: now,   // Updated now
                similarity: Some(0.62),
                embedding: "yes".to_string(),
                embeddable: true,
                file_path: None,
                file_mtime: None,
            },
        ]
    }

    #[test]
    fn given_bookmarks_when_show_with_default_fields_then_displays_colored_output() {
        println!("\n\nTEST: Colored Bookmark Display - Default Fields\n");
        let bookmarks = create_test_bookmarks();
        let settings = crate::config::Settings::default();
        show_bookmarks(&bookmarks, DEFAULT_FIELDS, &settings);
    }

    #[test]
    fn given_bookmarks_when_show_with_all_fields_then_displays_extended_output() {
        println!("\n\nTEST: Colored Bookmark Display - All Fields\n");
        let bookmarks = create_test_bookmarks();

        // Create a version of ALL_FIELDS that includes Similarity and Embedding
        let extended_fields = &[
            DisplayField::Id,
            DisplayField::Title,
            DisplayField::Url,
            DisplayField::Description,
            DisplayField::Tags,
            DisplayField::AccessCount,
            DisplayField::LastUpdateTs,
            DisplayField::Similarity,
            DisplayField::Embedding,
        ];

        let settings = crate::config::Settings::default();
        show_bookmarks(&bookmarks, extended_fields, &settings);
    }

    #[test]
    fn given_empty_bookmark_list_when_show_then_displays_nothing() {
        println!("\n\nTEST: Empty Bookmark List\n");
        let empty_bookmarks: Vec<DisplayBookmark> = Vec::new();
        let settings = crate::config::Settings::default();
        show_bookmarks(&empty_bookmarks, DEFAULT_FIELDS, &settings);
    }

    #[test]
    fn given_bookmarks_when_output_to_file_then_creates_file_successfully() -> io::Result<()> {
        use std::io::Write;

        // Create output directory if it doesn't exist
        let output_dir = Path::new("target").join("display_test_output");
        if !output_dir.exists() {
            fs::create_dir_all(&output_dir)?;
        }

        // Redirect stdout to a file temporarily
        let mut output_file = fs::File::create(output_dir.join("bookmarks_display_test.txt"))?;

        // We can't redirect stderr where the colored output goes, but we can still save
        // the table output for inspection
        let bookmarks = create_test_bookmarks();

        // Write to the file
        writeln!(output_file, "=== BOOKMARK TABLE FORMAT ===")?;

        // We can't easily redirect the show_bookmarks output since it uses stderr
        // but we can create similar output manually for the table version
        for field in DEFAULT_FIELDS.iter() {
            write!(output_file, "{} ", field)?;
        }
        writeln!(output_file)?;

        for bm in &bookmarks {
            for field in DEFAULT_FIELDS {
                let value = bm.get_value(field);
                write!(output_file, "{} ", value)?;
            }
            writeln!(output_file)?;
        }

        println!(
            "Display test output saved to: {}",
            output_dir.join("bookmarks_display_test.txt").display()
        );

        Ok(())
    }
}
