//! Unified Content Item System
//!
//! This module provides the unified content item architecture with a base
//! `UnifiedContentItem` struct and specialized views for different contexts.
//!
//! Following project best practices:
//! - Configuration-driven: Works with any content type from content-kinds.toml
//! - Language agnostic: Supports dynamic language discovery
//! - No hardcoded values: All behavior driven by configuration

use std::collections::HashMap;

use serde::{Deserialize, Serialize};

use crate::content::traits::ContentItemTrait;

/// Unified content item structure containing all possible fields
///
/// This is the canonical content item that contains every field that might be
/// needed across different contexts. Specialized views can be created from
/// this.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UnifiedContentItem {
    // Core identification fields
    pub id: String,
    pub title: String,
    pub slug: String,
    pub language: String,
    pub content_type: String,

    // Content fields
    pub content: String,
    pub excerpt: Option<String>,
    pub subtitle: Option<String>,

    // Categorization
    pub categories: Vec<String>,
    pub tags: Vec<String>,
    pub emoji: Option<String>,

    // Status and publishing
    pub featured: bool,
    pub published: bool,
    pub draft: Option<bool>,

    // Metadata
    pub author: Option<String>,
    pub read_time: Option<String>,
    pub created_at: String,
    pub updated_at: Option<String>,

    // Translations and localization
    /// Available language codes for this content item.
    pub translations: Vec<String>,
    /// Map of language code → localized slug of the translation.
    /// Supports both legacy string format `{"es": "slug"}` and new object
    /// format `{"es": {"id": "post-id", "slug": "slug"}}`.
    #[serde(default)]
    pub translation_slugs: HashMap<String, String>,
    /// Map of language code → stable id of the translation.
    /// Only populated when source uses the new object format
    /// `{"es": {"id": "post-id", "slug": "slug"}}`.
    #[serde(default)]
    pub translation_ids: HashMap<String, String>,
    pub localized_slug: Option<String>,

    // File system paths
    pub source_file: String,

    // Content-type specific fields (stored as JSON for flexibility)
    pub metadata: serde_json::Value,

    // Dynamic fields for recipes, tutorials, etc.
    pub difficulty: Option<String>,
    pub prep_time: Option<String>,
    pub duration: Option<String>,
    pub prerequisites: Option<Vec<String>>,

    // Additional metadata fields
    pub view_count: Option<u32>,
    pub image_url: Option<String>,

    /// Grid card thumbnail (light theme). Falls back to `image_url` when
    /// absent.
    pub thumbnail: Option<String>,

    /// Grid card thumbnail for dark themes. When absent, `thumbnail` is used
    /// for all themes.
    pub thumbnail_dark: Option<String>,

    /// When set, all card links navigate here instead of the generated content
    /// URL.
    pub page_route: Option<String>,

    /// Manual sort position within content listings. Lower values appear first.
    /// 0 means no preference (falls back to date sorting).
    #[serde(default)]
    pub sort_order: u32,
}

impl ContentItemTrait for UnifiedContentItem {
    fn id(&self) -> &str {
        &self.id
    }

    fn title(&self) -> &str {
        &self.title
    }

    fn content_type(&self) -> &str {
        &self.content_type
    }

    fn language(&self) -> &str {
        &self.language
    }

    fn slug(&self) -> &str {
        &self.slug
    }

    fn content(&self) -> &str {
        &self.content
    }

    fn categories(&self) -> &[String] {
        &self.categories
    }

    fn tags(&self) -> &[String] {
        &self.tags
    }

    fn featured(&self) -> bool {
        self.featured
    }

    fn published(&self) -> bool {
        self.published
    }

    fn created_at(&self) -> &str {
        &self.created_at
    }
}

impl UnifiedContentItem {
    /// Create a new UnifiedContentItem with required fields
    pub fn new(
        id: String,
        title: String,
        slug: String,
        content_type: String,
        language: String,
        content: String,
        created_at: String,
    ) -> Self {
        Self {
            id,
            title,
            slug,
            language,
            content_type,
            content,
            excerpt: None,
            subtitle: None,
            categories: Vec::new(),
            tags: Vec::new(),
            emoji: None,
            featured: false,
            published: true,
            draft: None,
            author: None,
            read_time: None,
            created_at,
            updated_at: None,
            translations: Vec::new(),
            translation_slugs: HashMap::new(),
            translation_ids: HashMap::new(),
            localized_slug: None,
            source_file: String::new(),
            metadata: serde_json::Value::Null,
            difficulty: None,
            prep_time: None,
            duration: None,
            prerequisites: None,
            view_count: None,
            image_url: None,
            thumbnail: None,
            thumbnail_dark: None,
            page_route: None,
            sort_order: 0,
        }
    }

    /// Load content items for a given content type and language from embedded
    /// resources
    pub fn load_for_type(content_type: &str, language: &str) -> Option<Vec<Self>> {
        // First try the new unified system
        if let Some(items) = Self::load_from_unified_system(content_type, language) {
            return Some(items);
        }

        // Fallback to legacy system with conversion
        Self::load_via_legacy(content_type, language)
    }

    /// Load content items using the new unified system
    fn load_from_unified_system(content_type: &str, language: &str) -> Option<Vec<Self>> {
        // Use configuration-driven resource key generation instead of hardcoded format
        let index_key = format!("{}_{}_index", content_type, language);

        tracing::debug!(
            "🔍 UNIFIED DEBUG: Attempting to load embedded resource key: '{}'",
            index_key
        );

        match crate::resources::load::content(&index_key) {
            Ok(index_content) => {
                tracing::debug!(
                    "🔍 UNIFIED DEBUG: Successfully loaded content, {} characters",
                    index_content.len()
                );

                // Parse the JSON index
                match serde_json::from_str::<serde_json::Value>(&index_content) {
                    Ok(json_value) => {
                        // Use configuration-driven key discovery instead of hardcoded patterns
                        let content_kind_config = crate::create_content_kind_registry()
                            .kinds
                            .get(content_type)
                            .cloned();

                        let dynamic_keys = if let Some(_config) = content_kind_config {
                            // Configuration-driven key generation based on content-kinds.toml
                            vec![
                                content_type.to_string(),
                                format!("{}_posts", content_type),
                                format!("{}s", content_type),
                            ]
                        } else {
                            // Minimal fallback when no configuration available
                            vec![content_type.to_string()]
                        };

                        for key in &dynamic_keys {
                            tracing::debug!(
                                "🔍 KEY DEBUG: Trying key '{}' for content_type='{}' language='{}'",
                                key,
                                content_type,
                                language
                            );

                            if let Some(posts_array) =
                                json_value.get(key).and_then(|p| p.as_array())
                            {
                                tracing::debug!(
                                    "🔍 KEY DEBUG: Found array with {} items for key '{}'",
                                    posts_array.len(),
                                    key
                                );

                                let mut items = Vec::new();
                                for post in posts_array {
                                    if let Some(item) =
                                        Self::from_json(post, content_type, language)
                                    {
                                        items.push(item);
                                    }
                                }

                                tracing::info!(
                                    "Loaded {} items from unified system key '{}' for \
                                     content_type='{}' language='{}'",
                                    items.len(),
                                    key,
                                    content_type,
                                    language
                                );
                                return Some(items);
                            } else {
                                tracing::debug!(
                                    "🔍 KEY DEBUG: Key '{}' not found or not an array",
                                    key
                                );
                            }
                        }

                        tracing::warn!(
                            "No recognized content array found in unified system for \
                             content_type='{}' language='{}'. Tried keys: {:?}",
                            content_type,
                            language,
                            dynamic_keys
                        );
                    }
                    Err(e) => {
                        tracing::error!(
                            "Failed to parse unified system JSON for content_type='{}' \
                             language='{}': {}",
                            content_type,
                            language,
                            e
                        );
                    }
                }
            }
            Err(e) => {
                tracing::warn!(
                    "🔍 UNIFIED DEBUG: Failed to load embedded resource key '{}': {}",
                    index_key,
                    e
                );
            }
        }

        None
    }

    /// Convert a JSON post object to UnifiedContentItem
    pub fn from_json(post: &serde_json::Value, content_type: &str, language: &str) -> Option<Self> {
        let debug_value = crate::get_debug_value();
        if debug_value > 4 {
            tracing::debug!("🔍 CONVERT DEBUG: Converting post JSON: {:?}", post);
        }

        let id = post.get("id")?.as_str()?.to_string();
        let title = post.get("title")?.as_str()?.to_string();

        // Use id as slug if no explicit slug field exists
        let slug = post
            .get("slug")
            .and_then(|s| s.as_str())
            .or_else(|| post.get("id").and_then(|i| i.as_str()))
            .unwrap_or(&id)
            .to_string();

        // Handle both "categories" array and single "category" field
        let categories =
            if let Some(categories_array) = post.get("categories").and_then(|c| c.as_array()) {
                categories_array
                    .iter()
                    .filter_map(|v| v.as_str().map(|s| s.to_string()))
                    .collect()
            } else if let Some(single_category) = post.get("category").and_then(|c| c.as_str()) {
                vec![single_category.to_string()]
            } else {
                vec![]
            };

        let tags = post
            .get("tags")
            .and_then(|t| t.as_array())
            .map(|arr| {
                arr.iter()
                    .filter_map(|v| v.as_str().map(|s| s.to_string()))
                    .collect()
            })
            .unwrap_or_default();

        // Use subtitle or excerpt as content if no explicit content field
        let content = post
            .get("content")
            .and_then(|c| c.as_str())
            .or_else(|| post.get("subtitle").and_then(|s| s.as_str()))
            .or_else(|| post.get("excerpt").and_then(|e| e.as_str()))
            .unwrap_or("")
            .to_string();

        // Try multiple date field names
        let created_at = post
            .get("created_at")
            .or_else(|| post.get("date"))
            .and_then(|c| c.as_str())
            .unwrap_or("")
            .to_string();

        // Parse translations: accepts three formats:
        //   legacy array: ["es"]
        //   legacy object: {"es": "slug-es"}
        //   new object:    {"es": {"id": "post-id", "slug": "slug-es"}}
        let raw_translations = post.get("translations");
        let translations: Vec<String> = raw_translations
            .and_then(|t| {
                t.as_array()
                    .map(|arr| {
                        arr.iter()
                            .filter_map(|v| v.as_str().map(|s| s.to_string()))
                            .collect()
                    })
                    .or_else(|| t.as_object().map(|obj| obj.keys().cloned().collect()))
            })
            .unwrap_or_default();

        let mut translation_slugs: HashMap<String, String> = HashMap::new();
        let mut translation_ids: HashMap<String, String> = HashMap::new();
        if let Some(obj) = raw_translations.and_then(|t| t.as_object()) {
            for (lang, val) in obj {
                if let Some(slug) = val.as_str() {
                    // Legacy string format: {"es": "slug"}
                    translation_slugs.insert(lang.clone(), slug.to_string());
                } else if let Some(entry) = val.as_object() {
                    // New object format: {"es": {"id": "...", "slug": "..."}}
                    if let Some(slug) = entry.get("slug").and_then(|s| s.as_str()) {
                        translation_slugs.insert(lang.clone(), slug.to_string());
                    }
                    if let Some(id) = entry.get("id").and_then(|s| s.as_str()) {
                        translation_ids.insert(lang.clone(), id.to_string());
                    }
                }
            }
        }

        if debug_value > 4 {
            tracing::debug!(
                "Converting post: id={}, title={}, slug={}, categories={:?}",
                id,
                title,
                slug,
                categories
            );
        }

        Some(Self {
            id,
            title,
            slug,
            language: language.to_string(),
            content_type: content_type.to_string(),
            content,
            excerpt: post
                .get("excerpt")
                .and_then(|e| e.as_str().map(|s| s.to_string())),
            subtitle: post
                .get("subtitle")
                .and_then(|s| s.as_str().map(|s| s.to_string())),
            categories,
            tags,
            emoji: post
                .get("emoji")
                .and_then(|e| e.as_str().map(|s| s.to_string())),
            featured: post
                .get("featured")
                .and_then(|f| f.as_bool())
                .unwrap_or(false),
            published: post
                .get("published")
                .and_then(|p| p.as_bool())
                .unwrap_or(true),
            draft: post.get("draft").and_then(|d| d.as_bool()),
            author: post
                .get("author")
                .and_then(|a| a.as_str().map(|s| s.to_string())),
            read_time: post
                .get("read_time")
                .and_then(|r| r.as_str().map(|s| s.to_string())),
            created_at,
            updated_at: post
                .get("updated_at")
                .and_then(|u| u.as_str().map(|s| s.to_string())),
            translations,
            translation_slugs,
            translation_ids,
            localized_slug: post
                .get("localized_slug")
                .and_then(|l| l.as_str().map(|s| s.to_string())),
            source_file: post
                .get("source_file")
                .and_then(|s| s.as_str())
                .unwrap_or("")
                .to_string(),
            metadata: {
                let mut metadata = post
                    .get("metadata")
                    .cloned()
                    .unwrap_or_else(|| serde_json::Value::Object(serde_json::Map::new()));

                // Ensure metadata is an object, not a primitive value
                if let serde_json::Value::Object(ref mut map) = metadata {
                    // Include important fields in metadata for client-side access
                    if let Some(html_file) = post.get("html_file") {
                        map.insert("html_file".to_string(), html_file.clone());
                    }
                    if let Some(source_file) = post.get("source_file") {
                        map.insert("source_file".to_string(), source_file.clone());
                    }
                } else {
                    // If metadata wasn't an object, create new one with essential fields
                    let mut map = serde_json::Map::new();
                    if let Some(html_file) = post.get("html_file") {
                        map.insert("html_file".to_string(), html_file.clone());
                    }
                    if let Some(source_file) = post.get("source_file") {
                        map.insert("source_file".to_string(), source_file.clone());
                    }
                    metadata = serde_json::Value::Object(map);
                }

                metadata
            },
            difficulty: post
                .get("difficulty")
                .and_then(|d| d.as_str().map(|s| s.to_string())),
            prep_time: post
                .get("prep_time")
                .and_then(|p| p.as_str().map(|s| s.to_string())),
            duration: post
                .get("duration")
                .and_then(|d| d.as_str().map(|s| s.to_string())),
            prerequisites: post
                .get("prerequisites")
                .and_then(|p| p.as_array())
                .map(|arr| {
                    arr.iter()
                        .filter_map(|v| v.as_str().map(|s| s.to_string()))
                        .collect()
                }),
            view_count: post
                .get("view_count")
                .and_then(|v| v.as_u64().map(|n| n as u32)),
            image_url: post
                .get("image_url")
                .and_then(|u| u.as_str().map(|s| s.to_string())),
            thumbnail: post
                .get("thumbnail")
                .or_else(|| post.get("image_url"))
                .and_then(|u| u.as_str().map(|s| s.to_string())),
            thumbnail_dark: post
                .get("thumbnail_dark")
                .and_then(|u| u.as_str().map(|s| s.to_string())),
            page_route: post
                .get("page_route")
                .and_then(|u| u.as_str().map(|s| s.to_string())),
            sort_order: post
                .get("sort_order")
                .and_then(|v| v.as_u64().map(|n| n as u32))
                .unwrap_or(0),
        })
    }

    /// Get the URL path for this content using language-agnostic route
    /// resolution. Returns `page_route` directly when set.
    pub fn url_path(&self) -> String {
        if let Some(ref route) = self.page_route {
            return route.clone();
        }
        let config = crate::routing::config::get_routing_config();
        let language_registry = crate::i18n::language_config::get_language_registry();
        let default_language = language_registry.get_default_language();

        // Look for route pattern for this content type and language
        let mut matching_routes: Vec<&crate::routing::RouteConfigToml> = config
            .routes
            .iter()
            .filter(|r| {
                let enabled = r.enabled;
                let lang_match = r.language == self.language;
                let has_slug = r.path.contains("{slug}");
                let content_type_match = r
                    .get_content_type()
                    .is_some_and(|ct| ct == self.content_type);
                enabled && lang_match && has_slug && content_type_match
            })
            .collect();

        // Sort by priority (higher priority first) and prefer routes with {category}
        // over hardcoded paths
        matching_routes.sort_by(|a, b| {
            // First, prioritize routes with {category} if this item has a category
            if !self.categories.is_empty() {
                let a_has_category = a.path.contains("{category}");
                let b_has_category = b.path.contains("{category}");
                if a_has_category && !b_has_category {
                    return std::cmp::Ordering::Less;
                } else if !a_has_category && b_has_category {
                    return std::cmp::Ordering::Greater;
                }
            }

            // Then sort by priority (higher priority first)
            b.priority
                .partial_cmp(&a.priority)
                .unwrap_or(std::cmp::Ordering::Equal)
        });

        if let Some(route) = matching_routes.first() {
            // Replace all placeholders and add .html extension
            let mut url = route.path.clone();

            // Replace {slug} with actual slug
            url = url.replace("{slug}", self.slug());

            // Replace {category} with main category if it exists
            if url.contains("{category}") {
                if !self.categories.is_empty() {
                    url = url.replace(
                        "{category}",
                        &self.categories[0].to_lowercase().replace(" ", "-"),
                    );
                } else {
                    // If no category, use "general" as fallback
                    url = url.replace("{category}", "general");
                }
            }

            url
        } else {
            // Fallback based on content type and language
            // For non-default language, should still follow the basic structure
            if self.language == default_language {
                // Default language: /recipes/slug
                if !self.categories.is_empty() {
                    format!(
                        "/{}/{}/{}",
                        self.content_type,
                        self.categories[0].to_lowercase().replace(" ", "-"),
                        self.slug()
                    )
                } else {
                    format!("/{}/{}", self.content_type, self.slug())
                }
            } else {
                // Non-default language: should still follow proper structure, not add -lang
                // suffix
                if !self.categories.is_empty() {
                    format!(
                        "/{}/{}/{}",
                        self.content_type,
                        self.categories[0].to_lowercase().replace(" ", "-"),
                        self.slug()
                    )
                } else {
                    format!("/{}/{}", self.content_type, self.slug())
                }
            }
        }
    }
}

/// Default implementation for UnifiedContentItem
impl Default for UnifiedContentItem {
    fn default() -> Self {
        Self {
            id: String::new(),
            title: String::new(),
            slug: String::new(),
            language: String::new(),
            content_type: String::new(),
            content: String::new(),
            excerpt: None,
            subtitle: None,
            categories: Vec::new(),
            tags: Vec::new(),
            emoji: None,
            featured: false,
            published: true,
            draft: None,
            author: None,
            read_time: None,
            created_at: String::new(),
            updated_at: None,
            translations: Vec::new(),
            translation_slugs: HashMap::new(),
            translation_ids: HashMap::new(),
            localized_slug: None,
            source_file: String::new(),
            metadata: serde_json::Value::Null,
            difficulty: None,
            prep_time: None,
            duration: None,
            prerequisites: None,
            view_count: None,
            image_url: None,
            thumbnail: None,
            thumbnail_dark: None,
            page_route: None,
            sort_order: 0,
        }
    }
}

/// Specialized content item for display contexts (pages, components)
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ContentItemView {
    pub id: String,
    pub title: String,
    pub slug: String,
    pub language: String,
    pub content_type: String,
    pub categories: Vec<String>,
    pub tags: Vec<String>,
    pub emoji: Option<String>,
    pub featured: bool,
    pub excerpt: Option<String>,
    pub content: String,
    pub created_at: String,
    pub updated_at: Option<String>,
    pub read_time: Option<String>,
}

impl ContentItemTrait for ContentItemView {
    fn id(&self) -> &str {
        &self.id
    }
    fn title(&self) -> &str {
        &self.title
    }
    fn content_type(&self) -> &str {
        &self.content_type
    }
    fn language(&self) -> &str {
        &self.language
    }
    fn slug(&self) -> &str {
        &self.slug
    }
    fn content(&self) -> &str {
        &self.content
    }
    fn categories(&self) -> &[String] {
        &self.categories
    }
    fn tags(&self) -> &[String] {
        &self.tags
    }
    fn featured(&self) -> bool {
        self.featured
    }
    fn published(&self) -> bool {
        true
    } // Views are always for published content
    fn created_at(&self) -> &str {
        &self.created_at
    }
}

impl From<UnifiedContentItem> for ContentItemView {
    fn from(item: UnifiedContentItem) -> Self {
        Self {
            id: item.id,
            title: item.title,
            slug: item.slug,
            language: item.language,
            content_type: item.content_type,
            categories: item.categories,
            tags: item.tags,
            emoji: item.emoji,
            featured: item.featured,
            excerpt: item.excerpt,
            content: item.content,
            created_at: item.created_at,
            updated_at: item.updated_at,
            read_time: item.read_time,
        }
    }
}

/// Specialized content item for management UI
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContentItemManager {
    pub id: String,
    pub title: String,
    pub slug: String,
    pub content: String,
    pub content_type: String,
    pub language: String,
    pub published: bool,
    pub tags: Vec<String>,
    pub category: String, // Single primary category for management
    pub excerpt: String,
    pub updated_at: String,
}

impl From<UnifiedContentItem> for ContentItemManager {
    fn from(item: UnifiedContentItem) -> Self {
        Self {
            id: item.id,
            title: item.title,
            slug: item.slug,
            content: item.content,
            content_type: item.content_type,
            language: item.language,
            published: item.published,
            tags: item.tags,
            category: item.categories.first().cloned().unwrap_or_default(),
            excerpt: item.excerpt.unwrap_or_default(),
            updated_at: item.updated_at.unwrap_or(item.created_at),
        }
    }
}

// Additional implementations for UnifiedContentItem
impl UnifiedContentItem {
    /// Load content using the legacy system and convert to UnifiedContentItem
    pub fn load_via_legacy(content_type: &str, language: &str) -> Option<Vec<Self>> {
        use crate::fluent::load_content_index;

        match load_content_index(content_type, language) {
            Ok(content_index) => Some(content_index.items),
            Err(e) => {
                tracing::error!("Failed to load content via legacy system: {}", e);
                None
            }
        }
    }

    /// Find translated content by ID in a different language
    /// Returns a tuple (slug, category) of the translated content if found
    pub fn find_translated_content_by_id(
        content_id: &str,
        content_type: &str,
        target_language: &str,
    ) -> Option<(String, Option<String>)> {
        // Load content for the target language
        let target_items = Self::load_for_type(content_type, target_language)?;

        // Find the item with the same ID
        let translated_item = target_items.iter().find(|item| item.id == content_id)?;

        let category = if !translated_item.categories.is_empty() {
            Some(translated_item.categories[0].clone())
        } else {
            None
        };

        tracing::debug!(
            "Found translated content: id='{}' in language='{}' with slug='{}' and category='{:?}'",
            content_id,
            target_language,
            translated_item.slug,
            category
        );

        Some((translated_item.slug.clone(), category))
    }

    /// Get content ID from slug in a specific language and content type
    /// This is useful for finding the common identifier across languages
    pub fn get_content_id_from_slug(
        slug: &str,
        content_type: &str,
        language: &str,
    ) -> Option<String> {
        // Load content for the specified language
        let items = Self::load_for_type(content_type, language)?;

        // Find the item by slug
        let item = items.iter().find(|item| item.slug == slug)?;

        tracing::debug!(
            "Found content ID '{}' for slug '{}' in language '{}' content_type '{}'",
            item.id,
            slug,
            language,
            content_type
        );

        Some(item.id.clone())
    }
}

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

    #[test]
    fn test_unified_content_item_creation() {
        let item = UnifiedContentItem::new(
            "test-id".to_string(),
            "Test Title".to_string(),
            "test-slug".to_string(),
            "blog".to_string(),
            "en".to_string(),
            "Test content".to_string(),
            "2024-01-01".to_string(),
        );

        assert_eq!(item.id(), "test-id");
        assert_eq!(item.title(), "Test Title");
        assert_eq!(item.content_type(), "blog");
        assert_eq!(item.language(), "en");
    }

    #[test]
    fn test_content_item_view_conversion() {
        let unified = UnifiedContentItem::new(
            "test-id".to_string(),
            "Test Title".to_string(),
            "test-slug".to_string(),
            "blog".to_string(),
            "en".to_string(),
            "Test content".to_string(),
            "2024-01-01".to_string(),
        );

        let view: ContentItemView = unified.into();
        assert_eq!(view.id(), "test-id");
        assert_eq!(view.title(), "Test Title");
    }

    #[test]
    fn test_legacy_conversion() {
        // This test would require mocking the fluent system
        // For now, we'll just test the conversion method exists
        assert!(UnifiedContentItem::load_via_legacy("test", "en").is_none());
    }
}
