//! Runtime Markdown Processor
//!
//! Processes markdown content on-demand with caching and frontmatter parsing.
//! Provides high-performance markdown to HTML conversion with metadata
//! extraction.

use std::collections::HashMap;

use anyhow::{Context, Result};
use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag};
use rustelo_core_lib::fluent::{ContentState, ContentType, PageContent};
use serde::{Deserialize, Serialize};
use tracing::{debug, warn};

/// Frontmatter extracted from markdown files
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrontMatter {
    pub title: String,
    pub slug: String,
    pub published: Option<bool>,
    pub draft: Option<bool>,
    pub featured: Option<bool>,
    pub tags: Vec<String>,
    pub category: Option<String>,
    pub excerpt: Option<String>,
    pub description: Option<String>,
    pub author: Option<String>,
    pub published_at: Option<chrono::DateTime<chrono::Utc>>,
    pub updated_at: Option<chrono::DateTime<chrono::Utc>>,
    pub content_type: Option<String>,
    pub language: Option<String>,
    pub translation: Option<String>,
    pub translations: Option<HashMap<String, String>>,
    pub custom_fields: HashMap<String, serde_yaml::Value>,
}

impl Default for FrontMatter {
    fn default() -> Self {
        Self {
            title: String::new(),
            slug: String::new(),
            published: Some(true),
            draft: None,
            featured: Some(false),
            tags: Vec::new(),
            category: None,
            excerpt: None,
            description: None,
            author: None,
            published_at: None,
            updated_at: None,
            content_type: None,
            language: None,
            translation: None,
            translations: None,
            custom_fields: HashMap::new(),
        }
    }
}

/// Processed markdown content with metadata
#[derive(Debug, Clone)]
pub struct ProcessedContent {
    pub frontmatter: FrontMatter,
    pub html: String,
    pub raw_markdown: String,
    pub word_count: usize,
    pub reading_time_minutes: usize,
    pub table_of_contents: Vec<TocEntry>,
}

/// Table of contents entry
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TocEntry {
    pub level: u32,
    pub title: String,
    pub id: String,
}

/// Runtime markdown processor with caching
pub struct MarkdownProcessor {
    highlight_code: bool,
    generate_toc: bool,
    options: Options,
}

impl Default for MarkdownProcessor {
    fn default() -> Self {
        Self::new()
    }
}

impl MarkdownProcessor {
    /// Create a new markdown processor
    pub fn new() -> Self {
        let mut options = Options::empty();
        options.insert(Options::ENABLE_STRIKETHROUGH);
        options.insert(Options::ENABLE_TABLES);
        options.insert(Options::ENABLE_FOOTNOTES);
        options.insert(Options::ENABLE_TASKLISTS);
        options.insert(Options::ENABLE_HEADING_ATTRIBUTES);

        Self {
            highlight_code: true,
            generate_toc: true,
            options,
        }
    }

    /// Process markdown content from raw text
    pub fn process(&self, content: &str) -> Result<ProcessedContent> {
        debug!("Processing markdown content ({} bytes)", content.len());

        // Split frontmatter and content
        let (frontmatter, markdown) = self.extract_frontmatter(content)?;

        // Generate HTML
        let (html, toc) = self.markdown_to_html(&markdown)?;

        // Calculate reading time (average 200 words per minute)
        let word_count = markdown.split_whitespace().count();
        let reading_time = (word_count as f64 / 200.0).ceil() as usize;

        Ok(ProcessedContent {
            frontmatter,
            html,
            raw_markdown: markdown.to_string(),
            word_count,
            reading_time_minutes: reading_time.max(1),
            table_of_contents: toc,
        })
    }

    /// Convert processed content to PageContent
    pub fn to_page_content(
        &self,
        processed: ProcessedContent,
        content_type: &str,
        _language: &str,
    ) -> Result<PageContent> {
        let state = if processed.frontmatter.published.unwrap_or(true)
            && !processed.frontmatter.draft.unwrap_or(false)
        {
            ContentState::Published
        } else {
            ContentState::Draft
        };

        // Determine content type
        let content_type_enum = if let Some(ref ct) = processed.frontmatter.content_type {
            ContentType::from(ct.clone())
        } else {
            ContentType::from(content_type.to_string())
        };

        let now = chrono::Utc::now();
        let slug = processed.frontmatter.slug.clone();
        Ok(PageContent {
            id: uuid::Uuid::new_v4(),
            slug: slug.clone(),
            title: processed.frontmatter.title,
            name: slug, // Use slug as name
            author: processed.frontmatter.author,
            author_id: None, // Would be populated from user system
            content_type: content_type_enum,
            content_format: rustelo_core_lib::fluent::ContentFormat::Markdown,
            content: processed.html,
            container: format!("{}-container", content_type),
            state,
            require_login: false,
            date_init: now,
            date_end: None,
            created_at: now,
            updated_at: now,
            published_at: processed.frontmatter.published_at,
            metadata: serde_json::to_value(processed.frontmatter.custom_fields)
                .unwrap_or_default()
                .as_object()
                .map(|obj| {
                    obj.iter()
                        .map(|(k, v)| (k.clone(), v.to_string()))
                        .collect()
                })
                .unwrap_or_default(),
            tags: processed.frontmatter.tags,
            category: processed.frontmatter.category,
            featured_image: None,
            excerpt: processed.frontmatter.excerpt,
            seo_title: processed.frontmatter.description.clone(),
            seo_description: processed.frontmatter.description,
            allow_comments: true,
            view_count: 0,
            sort_order: 0,
        })
    }

    /// Extract frontmatter from markdown content
    fn extract_frontmatter<'a>(&self, content: &'a str) -> Result<(FrontMatter, &'a str)> {
        let content = content.trim_start();

        if !content.starts_with("---") {
            // No frontmatter, use defaults
            return Ok((FrontMatter::default(), content));
        }

        // Find end of frontmatter
        if let Some(end) = content[3..].find("\n---") {
            let yaml_content = &content[4..end + 3];
            let markdown_content = &content[end + 7..].trim_start();

            let mut frontmatter: FrontMatter =
                serde_yaml::from_str(yaml_content).context("Failed to parse frontmatter YAML")?;

            // Handle additional fields not in the struct
            if let Ok(yaml_value) = serde_yaml::from_str::<serde_yaml::Value>(yaml_content) {
                if let Some(mapping) = yaml_value.as_mapping() {
                    for (key, value) in mapping {
                        if let Some(key_str) = key.as_str() {
                            // Store custom fields that aren't part of the main struct
                            match key_str {
                                "title" | "slug" | "published" | "draft" | "featured" | "tags"
                                | "category" | "excerpt" | "description" | "author"
                                | "published_at" | "updated_at" | "content_type" | "language"
                                | "translation" | "translations" => {
                                    // Already handled by serde
                                }
                                _ => {
                                    frontmatter
                                        .custom_fields
                                        .insert(key_str.to_string(), value.clone());
                                }
                            }
                        }
                    }
                }
            }

            Ok((frontmatter, markdown_content))
        } else {
            warn!("Malformed frontmatter: no closing ---");
            Ok((FrontMatter::default(), content))
        }
    }

    /// Convert markdown to HTML with table of contents
    fn markdown_to_html(&self, markdown: &str) -> Result<(String, Vec<TocEntry>)> {
        let parser = Parser::new_ext(markdown, self.options);
        let mut html_output = String::new();
        let mut toc = Vec::new();
        let mut heading_counter = 0;

        let events: Vec<_> = parser.collect();
        let enhanced_events = self.enhance_events(events, &mut toc, &mut heading_counter)?;

        pulldown_cmark::html::push_html(&mut html_output, enhanced_events.into_iter());

        Ok((html_output, toc))
    }

    /// Enhance markdown events with syntax highlighting and ToC
    fn enhance_events<'a>(
        &self,
        events: Vec<Event<'a>>,
        toc: &mut Vec<TocEntry>,
        heading_counter: &mut usize,
    ) -> Result<Vec<Event<'a>>> {
        let mut enhanced = Vec::new();
        let mut in_heading = false;
        let mut _heading_level = 0;
        let mut heading_text = String::new();

        for event in events {
            match event {
                Event::Start(Tag::Heading {
                    level,
                    id,
                    classes,
                    attrs,
                }) => {
                    in_heading = true;
                    _heading_level = level as u32;
                    heading_text.clear();
                    enhanced.push(Event::Start(Tag::Heading {
                        level,
                        id: id.clone(),
                        classes: classes.clone(),
                        attrs: attrs.clone(),
                    }));
                }
                Event::End(pulldown_cmark::TagEnd::Heading(level)) => {
                    if in_heading && self.generate_toc {
                        *heading_counter += 1;
                        let heading_id = format!("heading-{}", heading_counter);

                        toc.push(TocEntry {
                            level: level as u32,
                            title: heading_text.trim().to_string(),
                            id: heading_id.clone(),
                        });

                        // Add id to heading for ToC linking
                        enhanced.push(Event::End(pulldown_cmark::TagEnd::Heading(level)));
                    } else {
                        enhanced.push(Event::End(pulldown_cmark::TagEnd::Heading(level)));
                    }
                    in_heading = false;
                }
                Event::Text(ref text) if in_heading => {
                    heading_text.push_str(text);
                    enhanced.push(event);
                }
                Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(lang)))
                    if self.highlight_code =>
                {
                    // For now, just pass through. In the future, we could add syntax highlighting
                    // here
                    enhanced.push(Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(lang))));
                }
                _ => {
                    enhanced.push(event);
                }
            }
        }

        Ok(enhanced)
    }

    /// Set whether to enable code highlighting
    pub fn with_code_highlighting(mut self, enable: bool) -> Self {
        self.highlight_code = enable;
        self
    }

    /// Set whether to generate table of contents
    pub fn with_toc_generation(mut self, enable: bool) -> Self {
        self.generate_toc = enable;
        self
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_frontmatter_extraction() {
        let content = r#"---
title: Test Post
slug: test-post
tags: [rust, web]
published: true
custom_field: custom_value
---
# Hello World

This is a test post."#;

        let processor = MarkdownProcessor::new();
        let (frontmatter, markdown) = processor.extract_frontmatter(content).unwrap();

        assert_eq!(frontmatter.title, "Test Post");
        assert_eq!(frontmatter.slug, "test-post");
        assert_eq!(frontmatter.tags, vec!["rust", "web"]);
        assert_eq!(frontmatter.published, Some(true));
        assert!(frontmatter.custom_fields.contains_key("custom_field"));
        assert!(markdown.starts_with("# Hello World"));
    }

    #[test]
    fn test_markdown_processing() {
        let content = r#"---
title: Test Post
slug: test-post
---
# Introduction

This is a **bold** statement with some `code`.

## Section One

- Item 1
- Item 2

## Section Two

```rust
fn main() {
    println!("Hello, world!");
}
```"#;

        let processor = MarkdownProcessor::new();
        let processed = processor.process(content).unwrap();

        assert_eq!(processed.frontmatter.title, "Test Post");
        assert!(processed.html.contains("<h1"));
        assert!(processed.html.contains("<strong>bold</strong>"));
        assert!(processed.html.contains("<code>code</code>"));
        assert!(processed.html.contains("<ul>"));
        assert_eq!(processed.table_of_contents.len(), 3); // Introduction, Section One, Section Two
        assert!(processed.word_count > 0);
        assert!(processed.reading_time_minutes > 0);
    }

    #[test]
    fn test_no_frontmatter() {
        let content = "# Just a heading\n\nSome content.";

        let processor = MarkdownProcessor::new();
        let processed = processor.process(content).unwrap();

        assert_eq!(processed.frontmatter.title, "");
        assert!(processed.html.contains("<h1"));
    }
}
