//! Logseq format export with block support
//!
//! Exports KOGRAL nodes to Logseq-compatible markdown format.
//! Supports both flat content and structured blocks.

use std::fmt::Write;

use chrono::{DateTime, Utc};

use crate::block_parser::BlockParser;
use crate::error::Result;
use crate::models::{Node, NodeType};

/// Export a node to Logseq page format
///
/// # Format
///
/// ```markdown
/// ---
/// title: Page Title
/// tags: [[tag1]], [[tag2]]
/// created: 2026-01-17
/// ---
///
/// - Block content
///   - Nested block
/// - TODO Task block
/// ```
///
/// # Errors
///
/// Returns an error if node content cannot be serialized to Logseq format
pub fn export_to_logseq_page(node: &Node) -> Result<String> {
    let mut output = String::new();

    // Frontmatter (Logseq style with :: properties)
    let _ = writeln!(output, "title:: {}", node.title);

    if !node.tags.is_empty() {
        let tags = node
            .tags
            .iter()
            .map(|t| format!("[[{t}]]"))
            .collect::<Vec<_>>()
            .join(", ");
        let _ = writeln!(output, "tags:: {tags}");
    }

    let _ = writeln!(output, "created:: {}", format_logseq_date(node.created));
    let _ = writeln!(output, "modified:: {}", format_logseq_date(node.modified));
    let _ = writeln!(output, "type:: {}", node.node_type);
    let _ = writeln!(output, "status:: {}", node.status);

    // Add relationships as properties
    if !node.relates_to.is_empty() {
        let refs = node
            .relates_to
            .iter()
            .map(|id| format!("[[{id}]]"))
            .collect::<Vec<_>>()
            .join(", ");
        let _ = writeln!(output, "relates-to:: {refs}");
    }

    if !node.depends_on.is_empty() {
        let deps = node
            .depends_on
            .iter()
            .map(|id| format!("[[{id}]]"))
            .collect::<Vec<_>>()
            .join(", ");
        let _ = writeln!(output, "depends-on:: {deps}");
    }

    output.push('\n');

    // Content
    // If node has parsed blocks, serialize them directly
    // Otherwise, use the content as-is
    if let Some(blocks) = &node.blocks {
        if blocks.is_empty() {
            output.push_str(&node.content);
        } else {
            output.push_str(&BlockParser::serialize(blocks));
        }
    } else {
        output.push_str(&node.content);
    }

    Ok(output)
}

/// Export a node to Logseq journal format
///
/// Journal pages use date-based naming and simpler format
///
/// # Errors
///
/// Returns an error if node content cannot be serialized to Logseq journal
/// format
pub fn export_to_logseq_journal(node: &Node, _date: DateTime<Utc>) -> Result<String> {
    let mut output = String::new();

    // Journal pages typically don't have explicit frontmatter
    // Just content in block format

    // Add a header with the node title if not a journal type
    if node.node_type != NodeType::Journal {
        let _ = writeln!(output, "- ## {}", node.title);
    }

    // Add tags as first-level blocks
    for tag in &node.tags {
        let _ = writeln!(output, "- #{tag}");
    }

    // Content
    if let Some(blocks) = &node.blocks {
        if blocks.is_empty() {
            output.push_str(&node.content);
        } else {
            output.push_str(&BlockParser::serialize(blocks));
        }
    } else {
        output.push_str(&node.content);
    }

    Ok(output)
}

/// Format datetime for Logseq (supports both formats)
fn format_logseq_date(dt: DateTime<Utc>) -> String {
    // Logseq uses format: [[Jan 17th, 2026]]
    dt.format("%b %eth, %Y").to_string()
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::models::{Block, TaskStatus};

    #[test]
    fn test_export_simple_node() {
        let mut node = Node::new(NodeType::Note, "Test Note".to_string());
        node.content = "Simple content without blocks".to_string();
        node.add_tag("rust".to_string());

        let exported = export_to_logseq_page(&node).unwrap();

        assert!(exported.contains("title:: Test Note"));
        assert!(exported.contains("tags:: [[rust]]"));
        assert!(exported.contains("type:: note"));
        assert!(exported.contains("Simple content without blocks"));
    }

    #[test]
    fn test_export_node_with_blocks() {
        let mut node = Node::new(NodeType::Note, "Test".to_string());

        // Create blocks
        let mut block1 = Block::new("First block".to_string());
        block1.properties.add_tag("important".to_string());

        let mut block2 = Block::new("Second task".to_string());
        block2.properties.status = Some(TaskStatus::Todo);

        node.blocks = Some(vec![block1, block2]);

        let exported = export_to_logseq_page(&node).unwrap();

        assert!(exported.contains("title:: Test"));
        assert!(exported.contains("- First block #important"));
        assert!(exported.contains("- TODO Second task"));
    }

    #[test]
    fn test_export_with_relationships() {
        let mut node = Node::new(NodeType::Decision, "ADR-001".to_string());
        node.relates_to = vec!["node-123".to_string(), "node-456".to_string()];
        node.depends_on = vec!["node-789".to_string()];
        node.content = "Decision content".to_string();

        let exported = export_to_logseq_page(&node).unwrap();

        assert!(exported.contains("relates-to:: [[node-123]], [[node-456]]"));
        assert!(exported.contains("depends-on:: [[node-789]]"));
    }

    #[test]
    fn test_export_journal() {
        let mut node = Node::new(NodeType::Journal, "Daily Note".to_string());
        node.content = "- Morning reflection\n- TODO Review code".to_string();
        node.add_tag("daily".to_string());

        let date = Utc::now();
        let exported = export_to_logseq_journal(&node, date).unwrap();

        assert!(exported.contains("#daily"));
        assert!(exported.contains("- Morning reflection"));
        assert!(exported.contains("- TODO Review code"));
    }
}
