use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use shared::content::{ContentFormat, ContentState, ContentType, PageContent};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
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>>,
}

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,
        }
    }
}

#[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)
        }
    }

    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())
    }

    #[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 super::*;
    use std::fs;
    use tempfile::TempDir;

    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()));
    }
}
