use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};

use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use glob::glob;
use rustelo_core_lib::fluent::{ContentFormat, ContentState, ContentType, PageContent};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FrontMatter {
    pub title: String,
    pub slug: Option<String>,
    pub name: Option<String>,
    pub author: Option<String>,
    pub author_id: Option<String>,
    pub content_type: Option<String>,
    pub content_format: Option<String>,
    pub container: Option<String>,
    pub state: Option<String>,
    pub require_login: Option<bool>,
    pub date_init: Option<DateTime<Utc>>,
    pub date_end: Option<DateTime<Utc>>,
    pub published_at: Option<DateTime<Utc>>,
    pub tags: Option<Vec<String>>,
    pub category: Option<String>,
    pub featured_image: Option<String>,
    pub excerpt: Option<String>,
    pub seo_title: Option<String>,
    pub seo_description: Option<String>,
    pub allow_comments: Option<bool>,
    pub sort_order: Option<i32>,
    pub metadata: Option<HashMap<String, String>>,
    // Blog-specific fields
    pub date: Option<String>,
    pub read_time: Option<String>,
    // Recipe-specific fields
    pub difficulty: Option<String>,
    pub prep_time: Option<String>,
    pub duration: Option<String>,
    pub prerequisites: Option<Vec<String>>,
    // CSS styling fields
    pub css_class: Option<String>,
    pub css_style: Option<String>,
    // Translation fields
    pub translations: Option<HashMap<String, String>>,
    // Version control fields
    pub version: Option<String>,
    pub version_notes: Option<String>,
    pub parent_version: Option<String>,
    pub version_created_at: Option<DateTime<Utc>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CategoryMeta {
    pub slug: String,
    pub title: String,
    pub published: bool,
    pub sort_order: i32,
    pub description: String,
    pub css_class: String,
    pub css_style: String,
    pub translations: Option<HashMap<String, String>>,
}

impl Default for CategoryMeta {
    fn default() -> Self {
        Self {
            slug: String::new(),
            title: String::new(),
            published: true,
            sort_order: 1,
            description: String::new(),
            css_class: String::new(),
            css_style: String::new(),
            translations: None,
        }
    }
}

impl Default for FrontMatter {
    fn default() -> Self {
        Self {
            title: "Untitled".to_string(),
            slug: None,
            name: None,
            author: None,
            author_id: None,
            content_type: None,
            content_format: None,
            container: None,
            state: None,
            require_login: None,
            date_init: None,
            date_end: None,
            published_at: None,
            tags: None,
            category: None,
            featured_image: None,
            excerpt: None,
            seo_title: None,
            seo_description: None,
            allow_comments: None,
            sort_order: None,
            metadata: None,
            date: None,
            read_time: None,
            difficulty: None,
            prep_time: None,
            duration: None,
            prerequisites: None,
            css_class: None,
            css_style: None,
            translations: None,
            version: None,
            version_notes: None,
            parent_version: None,
            version_created_at: None,
        }
    }
}

#[derive(Debug, Clone)]
pub struct FileContent {
    pub front_matter: FrontMatter,
    pub content: String,
    pub file_path: PathBuf,
    #[allow(dead_code)]
    pub file_name: String,
}

impl FileContent {
    pub fn into_page_content(self) -> PageContent {
        let now = Utc::now();
        let file_stem = self
            .file_path
            .file_stem()
            .and_then(|s| s.to_str())
            .unwrap_or("untitled");

        let slug = self
            .front_matter
            .slug
            .unwrap_or_else(|| slugify(&self.front_matter.title));

        let name = self
            .front_matter
            .name
            .unwrap_or_else(|| file_stem.to_string());

        let author_id = self
            .front_matter
            .author_id
            .and_then(|id| Uuid::parse_str(&id).ok());

        let content_type = self
            .front_matter
            .content_type
            .map(ContentType::from)
            .unwrap_or(ContentType::Page);

        let content_format = self
            .front_matter
            .content_format
            .map(ContentFormat::from)
            .unwrap_or_else(|| detect_content_format(&self.file_path));

        let container = self
            .front_matter
            .container
            .unwrap_or_else(|| format!("{}-container", content_type.as_str()));

        let state = self
            .front_matter
            .state
            .map(ContentState::from)
            .unwrap_or(ContentState::Draft);

        PageContent {
            id: Uuid::new_v4(),
            slug,
            title: self.front_matter.title,
            name,
            author: self.front_matter.author,
            author_id,
            content_type,
            content_format,
            content: self.content,
            container,
            state,
            require_login: self.front_matter.require_login.unwrap_or(false),
            date_init: self.front_matter.date_init.unwrap_or(now),
            date_end: self.front_matter.date_end,
            created_at: now,
            updated_at: now,
            published_at: self.front_matter.published_at,
            metadata: self.front_matter.metadata.unwrap_or_default(),
            tags: self.front_matter.tags.unwrap_or_default(),
            category: self.front_matter.category,
            featured_image: self.front_matter.featured_image,
            excerpt: self.front_matter.excerpt,
            seo_title: self.front_matter.seo_title,
            seo_description: self.front_matter.seo_description,
            allow_comments: self.front_matter.allow_comments.unwrap_or(true),
            view_count: 0,
            sort_order: self.front_matter.sort_order.unwrap_or(0),
        }
    }
}

pub struct FileContentLoader {
    content_dir: PathBuf,
    supported_extensions: Vec<String>,
}

impl FileContentLoader {
    #[allow(dead_code)]
    pub fn new<P: AsRef<Path>>(content_dir: P) -> Self {
        Self {
            content_dir: content_dir.as_ref().to_path_buf(),
            supported_extensions: vec![
                "md".to_string(),
                "markdown".to_string(),
                "txt".to_string(),
                "html".to_string(),
            ],
        }
    }

    #[allow(dead_code)]
    pub fn with_extensions(mut self, extensions: Vec<String>) -> Self {
        self.supported_extensions = extensions;
        self
    }

    pub fn load_all_content(&self) -> Result<Vec<PageContent>> {
        let mut contents = Vec::new();
        self.load_content_from_dir(&self.content_dir, &mut contents)?;
        Ok(contents)
    }

    fn load_content_from_dir(&self, dir: &Path, contents: &mut Vec<PageContent>) -> Result<()> {
        if !dir.exists() {
            return Ok(());
        }

        let entries = fs::read_dir(dir)
            .with_context(|| format!("Failed to read directory: {}", dir.display()))?;

        for entry in entries {
            let entry = entry.with_context(|| "Failed to read directory entry")?;
            let path = entry.path();

            if path.is_dir() {
                // Recursively load from subdirectories
                self.load_content_from_dir(&path, contents)?;
            } else if path.is_file() {
                if let Some(extension) = path.extension() {
                    if let Some(ext_str) = extension.to_str() {
                        if self.supported_extensions.contains(&ext_str.to_lowercase()) {
                            match self.load_file(&path) {
                                Ok(content) => contents.push(content.into_page_content()),
                                Err(e) => {
                                    eprintln!(
                                        "Warning: Failed to load file {}: {}",
                                        path.display(),
                                        e
                                    );
                                }
                            }
                        }
                    }
                }
            }
        }

        Ok(())
    }

    pub fn load_file(&self, file_path: &Path) -> Result<FileContent> {
        let content = fs::read_to_string(file_path)
            .with_context(|| format!("Failed to read file: {}", file_path.display()))?;

        let file_name = file_path
            .file_name()
            .and_then(|name| name.to_str())
            .unwrap_or("unknown")
            .to_string();

        if content.starts_with("---") {
            // File has front matter
            self.parse_with_front_matter(&content, file_path.to_path_buf(), file_name)
        } else {
            // File has no front matter, use defaults
            self.parse_without_front_matter(&content, file_path.to_path_buf(), file_name)
        }
    }

    pub fn parse_with_front_matter(
        &self,
        content: &str,
        file_path: PathBuf,
        file_name: String,
    ) -> Result<FileContent> {
        let parts: Vec<&str> = content.splitn(3, "---").collect();

        if parts.len() < 3 {
            return Err(anyhow::anyhow!("Invalid front matter format"));
        }

        let front_matter_str = parts[1].trim();
        let content_str = parts[2].trim();

        let front_matter: FrontMatter = if front_matter_str.starts_with('{') {
            // JSON front matter
            serde_json::from_str(front_matter_str)
                .with_context(|| "Failed to parse JSON front matter")?
        } else {
            // YAML front matter
            serde_yaml::from_str(front_matter_str)
                .with_context(|| "Failed to parse YAML front matter")?
        };

        Ok(FileContent {
            front_matter,
            content: content_str.to_string(),
            file_path,
            file_name,
        })
    }

    fn parse_without_front_matter(
        &self,
        content: &str,
        file_path: PathBuf,
        file_name: String,
    ) -> Result<FileContent> {
        let title = extract_title_from_content(content).unwrap_or_else(|| {
            file_path
                .file_stem()
                .and_then(|s| s.to_str())
                .unwrap_or("Untitled")
                .to_string()
        });

        let front_matter = FrontMatter {
            title,
            ..Default::default()
        };

        Ok(FileContent {
            front_matter,
            content: content.to_string(),
            file_path,
            file_name,
        })
    }

    pub fn load_by_slug(&self, slug: &str) -> Result<Option<PageContent>> {
        let contents = self.load_all_content()?;
        Ok(contents.into_iter().find(|c| c.slug == slug))
    }

    #[allow(dead_code)]
    pub fn load_by_type(&self, content_type: ContentType) -> Result<Vec<PageContent>> {
        let contents = self.load_all_content()?;
        Ok(contents
            .into_iter()
            .filter(|c| c.content_type == content_type)
            .collect())
    }

    pub fn load_published(&self) -> Result<Vec<PageContent>> {
        let contents = self.load_all_content()?;
        Ok(contents.into_iter().filter(|c| c.is_published()).collect())
    }

    /// Load content from a specific category
    pub fn load_category_content(
        &self,
        content_type: &str,
        lang: &str,
        category: &str,
    ) -> Result<Vec<PageContent>> {
        let category_path = self
            .content_dir
            .join(content_type)
            .join(lang)
            .join(category);

        if !category_path.exists() {
            return Ok(Vec::new());
        }

        let mut contents = Vec::new();
        let entries = fs::read_dir(&category_path).with_context(|| {
            format!(
                "Failed to read category directory: {}",
                category_path.display()
            )
        })?;

        for entry in entries {
            let entry = entry.with_context(|| "Failed to read directory entry")?;
            let path = entry.path();

            if path.is_file() {
                if let Some(extension) = path.extension() {
                    if let Some(ext_str) = extension.to_str() {
                        if self.supported_extensions.contains(&ext_str.to_lowercase()) {
                            match self.load_file(&path) {
                                Ok(content) => contents.push(content.into_page_content()),
                                Err(e) => {
                                    eprintln!(
                                        "Warning: Failed to load file {}: {}",
                                        path.display(),
                                        e
                                    );
                                }
                            }
                        }
                    }
                }
            }
        }

        Ok(contents)
    }

    /// Get category metadata from meta.toml file
    pub fn get_category_metadata(
        &self,
        content_type: &str,
        lang: &str,
        category: &str,
    ) -> Result<CategoryMeta> {
        let meta_path = self
            .content_dir
            .join(content_type)
            .join(lang)
            .join(category)
            .join("meta.toml");

        if !meta_path.exists() {
            return Ok(CategoryMeta {
                slug: category.to_string(),
                title: category
                    .replace('-', " ")
                    .split(' ')
                    .map(|word| {
                        let mut chars = word.chars();
                        match chars.next() {
                            None => String::new(),
                            Some(first) => {
                                first.to_uppercase().collect::<String>() + chars.as_str()
                            }
                        }
                    })
                    .collect::<Vec<_>>()
                    .join(" "),
                css_class: format!("category-{}", category),
                ..Default::default()
            });
        }

        let content = fs::read_to_string(&meta_path)
            .with_context(|| format!("Failed to read meta file: {}", meta_path.display()))?;

        let meta: CategoryMeta = toml::from_str(&content)
            .with_context(|| format!("Failed to parse meta file: {}", meta_path.display()))?;

        Ok(meta)
    }

    /// Get available languages by scanning content type directories
    /// (language-agnostic)
    pub fn get_available_languages(&self, content_type: &str) -> Vec<String> {
        let mut languages = Vec::new();
        let content_type_path = self.content_dir.join(content_type);

        if !content_type_path.exists() {
            return languages;
        }

        if let Ok(entries) = fs::read_dir(&content_type_path) {
            for entry in entries.flatten() {
                let entry_path = entry.path();
                let entry_name = entry_path
                    .file_name()
                    .and_then(|n| n.to_str())
                    .unwrap_or_default();

                // Include only directories that look like language codes and aren't special
                // directories
                if entry_path.is_dir()
                    && !entry_name.starts_with('.')
                    && !entry_name.starts_with('_')
                    && entry_name != "meta.toml"
                    && entry_name.len() >= 2
                // Language codes are at least 2 characters
                {
                    languages.push(entry_name.to_string());
                }
            }
        }

        // Sort for consistent ordering
        languages.sort();
        languages
    }

    /// Discover translations for a specific post ID across all languages
    /// (language-agnostic)
    pub fn discover_translations(
        &self,
        content_type: &str,
        post_id: &str,
    ) -> HashMap<String, String> {
        let mut translations = HashMap::new();

        // Dynamically discover available languages instead of hardcoding
        let available_languages = self.get_available_languages(content_type);

        for lang_code in available_languages {
            // Search for post with matching ID in all categories within this language
            let content_type_path = self.content_dir.join(content_type);
            let pattern = format!(
                "{}/{}/*/{}.md",
                content_type_path.display(),
                lang_code,
                post_id
            );

            if let Ok(matches) = glob(&pattern) {
                for match_path in matches.flatten() {
                    if let Some(parent) = match_path.parent() {
                        if let Some(category) = parent.file_name().and_then(|n| n.to_str()) {
                            let slug = format!("{}/{}", category, post_id);
                            translations.insert(lang_code.clone(), slug);
                            break; // Found it in this language, move to next
                                   // language
                        }
                    }
                }
            }
        }

        translations
    }

    /// Update the slug finding logic to work with category structure
    pub fn load_by_slug_in_category(
        &self,
        content_type: &str,
        lang: &str,
        category: &str,
        slug: &str,
    ) -> Result<Option<PageContent>> {
        let file_path = self
            .content_dir
            .join(content_type)
            .join(lang)
            .join(category)
            .join(format!("{}.md", slug));

        if !file_path.exists() {
            return Ok(None);
        }

        match self.load_file(&file_path) {
            Ok(content) => Ok(Some(content.into_page_content())),
            Err(e) => {
                eprintln!(
                    "Warning: Failed to load file {}: {}",
                    file_path.display(),
                    e
                );
                Ok(None)
            }
        }
    }

    /// Enhanced load_by_slug that searches across categories
    pub fn load_by_slug_enhanced(
        &self,
        content_type: &str,
        lang: &str,
        slug: &str,
    ) -> Result<Option<PageContent>> {
        let type_lang_path = self.content_dir.join(content_type).join(lang);

        if !type_lang_path.exists() {
            return Ok(None);
        }

        // Search in all categories
        if let Ok(category_entries) = fs::read_dir(&type_lang_path) {
            for category_entry in category_entries.flatten() {
                let category_path = category_entry.path();

                if category_path.is_dir() {
                    let file_path = category_path.join(format!("{}.md", slug));
                    if file_path.exists() {
                        match self.load_file(&file_path) {
                            Ok(content) => return Ok(Some(content.into_page_content())),
                            Err(e) => {
                                eprintln!(
                                    "Warning: Failed to load file {}: {}",
                                    file_path.display(),
                                    e
                                );
                            }
                        }
                    }
                }
            }
        }

        Ok(None)
    }

    /// Load content by content type and language (for runtime processing)
    pub fn load_by_type_and_language(
        &self,
        content_type: &str,
        language: &str,
    ) -> Result<Vec<PageContent>> {
        let type_lang_path = self.content_dir.join(content_type).join(language);

        if !type_lang_path.exists() {
            return Ok(Vec::new());
        }

        let mut contents = Vec::new();
        self.load_content_from_dir(&type_lang_path, &mut contents)?;
        Ok(contents)
    }

    #[allow(dead_code)]
    pub fn watch_for_changes(&self) -> Result<()> {
        // This would implement file watching for hot reloading
        // For now, it's a placeholder
        tracing::info!("File watching not yet implemented");
        Ok(())
    }
}

fn slugify(text: &str) -> String {
    text.to_lowercase()
        .chars()
        .map(|c| {
            if c.is_alphanumeric() {
                c
            } else if c.is_whitespace() || c == '-' || c == '_' {
                '-'
            } else {
                '\0'
            }
        })
        .filter(|&c| c != '\0')
        .collect::<String>()
        .split('-')
        .filter(|s| !s.is_empty())
        .collect::<Vec<_>>()
        .join("-")
}

fn detect_content_format(path: &Path) -> ContentFormat {
    match path.extension().and_then(|ext| ext.to_str()) {
        Some("md") | Some("markdown") => ContentFormat::Markdown,
        Some("html") | Some("htm") => ContentFormat::Html,
        Some("txt") => ContentFormat::PlainText,
        _ => ContentFormat::Markdown,
    }
}

fn extract_title_from_content(content: &str) -> Option<String> {
    // Try to extract title from markdown heading
    if let Some(line) = content.lines().next() {
        if line.starts_with('#') {
            return Some(line.trim_start_matches('#').trim().to_string());
        }
    }

    // Try to extract title from HTML
    if content.contains("<title>") {
        if let Some(start) = content.find("<title>") {
            if let Some(end) = content[start..].find("</title>") {
                let title = &content[start + 7..start + end];
                return Some(title.to_string());
            }
        }
    }

    // Try to extract title from HTML h1
    if content.contains("<h1>") {
        if let Some(start) = content.find("<h1>") {
            if let Some(end) = content[start..].find("</h1>") {
                let title = &content[start + 4..start + end];
                return Some(title.to_string());
            }
        }
    }

    None
}

#[cfg(test)]
mod tests {
    use std::fs;

    use tempfile::TempDir;

    use super::*;

    fn create_test_file(dir: &Path, name: &str, content: &str) -> PathBuf {
        let file_path = dir.join(name);
        fs::write(&file_path, content).expect("Failed to write test file");
        file_path
    }

    #[test]
    fn test_slugify() {
        assert_eq!(slugify("Hello World"), "hello-world");
        assert_eq!(slugify("Hello  World!"), "hello-world");
        assert_eq!(slugify("Test_Title-123"), "test-title-123");
        assert_eq!(slugify("Special@#$%Characters"), "specialcharacters");
    }

    #[test]
    fn test_detect_content_format() {
        assert_eq!(
            detect_content_format(Path::new("test.md")),
            ContentFormat::Markdown
        );
        assert_eq!(
            detect_content_format(Path::new("test.html")),
            ContentFormat::Html
        );
        assert_eq!(
            detect_content_format(Path::new("test.txt")),
            ContentFormat::PlainText
        );
        assert_eq!(
            detect_content_format(Path::new("test.unknown")),
            ContentFormat::Markdown
        );
    }

    #[test]
    fn test_extract_title_from_content() {
        assert_eq!(
            extract_title_from_content("# Hello World\n\nContent here"),
            Some("Hello World".to_string())
        );
        assert_eq!(
            extract_title_from_content("## Secondary Title\n\nContent"),
            Some("Secondary Title".to_string())
        );
        assert_eq!(
            extract_title_from_content("<title>HTML Title</title>"),
            Some("HTML Title".to_string())
        );
        assert_eq!(
            extract_title_from_content("<h1>Header Title</h1>"),
            Some("Header Title".to_string())
        );
        assert_eq!(extract_title_from_content("Just plain text"), None);
    }

    #[test]
    fn test_file_loading() {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let content_dir = temp_dir.path();

        let markdown_content = r#"---
title: "Test Post"
slug: "test-post"
content_type: "blog"
state: "published"
tags: ["test", "markdown"]
---

# Test Post

This is a test post content.
"#;

        create_test_file(content_dir, "test.md", markdown_content);

        let loader = FileContentLoader::new(content_dir);
        let contents = loader.load_all_content().expect("Failed to load content");

        assert_eq!(contents.len(), 1);
        assert_eq!(contents[0].title, "Test Post");
        assert_eq!(contents[0].slug, "test-post");
        assert_eq!(contents[0].content_type, ContentType::Blog);
        assert_eq!(contents[0].state, ContentState::Published);
        assert_eq!(contents[0].tags, vec!["test", "markdown"]);
    }

    #[test]
    fn test_file_without_front_matter() {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let content_dir = temp_dir.path();

        let plain_content = r#"# Simple Title

This is plain markdown content without front matter.
"#;

        create_test_file(content_dir, "simple.md", plain_content);

        let loader = FileContentLoader::new(content_dir);
        let contents = loader.load_all_content().expect("Failed to load content");

        assert_eq!(contents.len(), 1);
        assert_eq!(contents[0].title, "Simple Title");
        assert_eq!(contents[0].slug, "simple-title");
        assert_eq!(contents[0].content_type, ContentType::Page);
        assert_eq!(contents[0].state, ContentState::Draft);
    }

    #[test]
    fn test_load_by_slug() {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let content_dir = temp_dir.path();

        let content = r#"---
title: "Specific Post"
slug: "specific-post"
---

Content here.
"#;

        create_test_file(content_dir, "specific.md", content);

        let loader = FileContentLoader::new(content_dir);
        let found_content = loader
            .load_by_slug("specific-post")
            .expect("Failed to load by slug");

        assert!(found_content.is_some());
        assert_eq!(found_content.unwrap().title, "Specific Post");

        let not_found = loader
            .load_by_slug("nonexistent")
            .expect("Failed to load by slug");
        assert!(not_found.is_none());
    }

    #[test]
    fn test_recursive_loading() {
        let temp_dir = TempDir::new().expect("Failed to create temp dir");
        let content_dir = temp_dir.path();
        let sub_dir = content_dir.join("subdir");
        fs::create_dir_all(&sub_dir).expect("Failed to create subdirectory");

        create_test_file(content_dir, "root.md", "# Root Content");
        create_test_file(&sub_dir, "sub.md", "# Sub Content");

        let loader = FileContentLoader::new(content_dir);
        let contents = loader.load_all_content().expect("Failed to load content");

        assert_eq!(contents.len(), 2);
        let titles: Vec<_> = contents.iter().map(|c| &c.title).collect();
        assert!(titles.contains(&&"Root Content".to_string()));
        assert!(titles.contains(&&"Sub Content".to_string()));
    }
}
