use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use tauri::AppHandle;
use tauri_plugin_store::StoreExt;

const SNIPPETS_STORE_KEY: &str = "snippets";

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Snippet {
    pub id: String,
    pub name: String,
    pub content: String,
    pub category: String,
    pub is_default: bool,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

fn get_default_snippets() -> Vec<Snippet> {
    let now = Utc::now();
    vec![
        Snippet {
            id: "default-explain-code".to_string(),
            name: "Explain this code".to_string(),
            content: "Please explain what this code does, step by step:".to_string(),
            category: "Code Review".to_string(),
            is_default: true,
            created_at: now,
            updated_at: now,
        },
        Snippet {
            id: "default-fix-error".to_string(),
            name: "Fix this error".to_string(),
            content: "I'm getting the following error. Can you help me fix it?".to_string(),
            category: "Debugging".to_string(),
            is_default: true,
            created_at: now,
            updated_at: now,
        },
        Snippet {
            id: "default-write-tests".to_string(),
            name: "Write tests".to_string(),
            content: "Please write unit tests for this code with good coverage:".to_string(),
            category: "Testing".to_string(),
            is_default: true,
            created_at: now,
            updated_at: now,
        },
        Snippet {
            id: "default-refactor".to_string(),
            name: "Refactor for clarity".to_string(),
            content: "Please refactor this code to improve readability and maintainability:".to_string(),
            category: "Code Review".to_string(),
            is_default: true,
            created_at: now,
            updated_at: now,
        },
        Snippet {
            id: "default-optimize".to_string(),
            name: "Optimize performance".to_string(),
            content: "Please analyze this code for performance issues and suggest optimizations:".to_string(),
            category: "Performance".to_string(),
            is_default: true,
            created_at: now,
            updated_at: now,
        },
        Snippet {
            id: "default-review-pr".to_string(),
            name: "Review PR".to_string(),
            content: "Please review this pull request and provide feedback on code quality, potential issues, and suggestions for improvement.".to_string(),
            category: "Code Review".to_string(),
            is_default: true,
            created_at: now,
            updated_at: now,
        },
        Snippet {
            id: "default-add-comments".to_string(),
            name: "Add documentation".to_string(),
            content: "Please add clear documentation comments to this code explaining what it does:".to_string(),
            category: "Documentation".to_string(),
            is_default: true,
            created_at: now,
            updated_at: now,
        },
        Snippet {
            id: "default-security-review".to_string(),
            name: "Security review".to_string(),
            content: "Please review this code for security vulnerabilities and suggest fixes:".to_string(),
            category: "Security".to_string(),
            is_default: true,
            created_at: now,
            updated_at: now,
        },
    ]
}

fn load_all_snippets(app: &AppHandle) -> Result<Vec<Snippet>, String> {
    let store = app
        .store("hikari-snippets.json")
        .map_err(|e| e.to_string())?;

    match store.get(SNIPPETS_STORE_KEY) {
        Some(value) => {
            let mut snippets: Vec<Snippet> =
                serde_json::from_value(value.clone()).map_err(|e| e.to_string())?;

            // Ensure default snippets exist (in case new ones were added in an update)
            let defaults = get_default_snippets();
            for default in defaults {
                if !snippets.iter().any(|s| s.id == default.id) {
                    snippets.push(default);
                }
            }

            Ok(snippets)
        }
        None => Ok(get_default_snippets()),
    }
}

fn save_all_snippets(app: &AppHandle, snippets: &[Snippet]) -> Result<(), String> {
    let store = app
        .store("hikari-snippets.json")
        .map_err(|e| e.to_string())?;

    let value = serde_json::to_value(snippets).map_err(|e| e.to_string())?;
    store.set(SNIPPETS_STORE_KEY, value);
    store.save().map_err(|e| e.to_string())?;

    Ok(())
}

#[tauri::command]
pub async fn list_snippets(app: AppHandle) -> Result<Vec<Snippet>, String> {
    let mut snippets = load_all_snippets(&app)?;

    // Sort by category, then by name
    snippets.sort_by(|a, b| {
        let cat_cmp = a.category.cmp(&b.category);
        if cat_cmp == std::cmp::Ordering::Equal {
            a.name.cmp(&b.name)
        } else {
            cat_cmp
        }
    });

    Ok(snippets)
}

#[tauri::command]
pub async fn save_snippet(app: AppHandle, snippet: Snippet) -> Result<(), String> {
    let mut snippets = load_all_snippets(&app)?;

    // Update existing or add new
    if let Some(existing) = snippets.iter_mut().find(|s| s.id == snippet.id) {
        // Don't allow editing default snippets' is_default flag
        let mut updated = snippet;
        updated.is_default = existing.is_default;
        *existing = updated;
    } else {
        snippets.push(snippet);
    }

    save_all_snippets(&app, &snippets)
}

#[tauri::command]
pub async fn delete_snippet(app: AppHandle, snippet_id: String) -> Result<(), String> {
    let mut snippets = load_all_snippets(&app)?;

    // Don't allow deleting default snippets
    if snippets
        .iter()
        .any(|s| s.id == snippet_id && s.is_default)
    {
        return Err("Cannot delete default snippets".to_string());
    }

    snippets.retain(|s| s.id != snippet_id);
    save_all_snippets(&app, &snippets)
}

#[tauri::command]
pub async fn get_snippet_categories(app: AppHandle) -> Result<Vec<String>, String> {
    let snippets = load_all_snippets(&app)?;
    let mut categories: Vec<String> = snippets.iter().map(|s| s.category.clone()).collect();
    categories.sort();
    categories.dedup();
    Ok(categories)
}

#[tauri::command]
pub async fn reset_default_snippets(app: AppHandle) -> Result<(), String> {
    let mut snippets = load_all_snippets(&app)?;

    // Remove all default snippets
    snippets.retain(|s| !s.is_default);

    // Add fresh default snippets
    snippets.extend(get_default_snippets());

    save_all_snippets(&app, &snippets)
}

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

    fn create_test_snippet(id: &str, name: &str, category: &str, is_default: bool) -> Snippet {
        Snippet {
            id: id.to_string(),
            name: name.to_string(),
            content: "Test content".to_string(),
            category: category.to_string(),
            is_default,
            created_at: Utc::now(),
            updated_at: Utc::now(),
        }
    }

    #[test]
    fn test_default_snippets_exist() {
        let defaults = get_default_snippets();
        assert!(!defaults.is_empty());
        assert!(defaults.iter().all(|s| s.is_default));
    }

    #[test]
    fn test_default_snippets_have_required_fields() {
        let defaults = get_default_snippets();
        for snippet in defaults {
            assert!(!snippet.id.is_empty());
            assert!(!snippet.name.is_empty());
            assert!(!snippet.content.is_empty());
            assert!(!snippet.category.is_empty());
        }
    }

    #[test]
    fn test_default_snippets_count() {
        let defaults = get_default_snippets();
        // Should have 8 default snippets
        assert_eq!(defaults.len(), 8);
    }

    #[test]
    fn test_default_snippets_have_unique_ids() {
        let defaults = get_default_snippets();
        let ids: HashSet<&String> = defaults.iter().map(|s| &s.id).collect();
        assert_eq!(ids.len(), defaults.len());
    }

    #[test]
    fn test_default_snippets_ids_start_with_default() {
        let defaults = get_default_snippets();
        assert!(defaults.iter().all(|s| s.id.starts_with("default-")));
    }

    #[test]
    fn test_snippet_serialization() {
        let snippet = create_test_snippet("test-1", "Test Snippet", "Testing", false);
        let json = serde_json::to_string(&snippet).expect("Failed to serialize");
        let parsed: Snippet = serde_json::from_str(&json).expect("Failed to deserialize");

        assert_eq!(parsed.id, snippet.id);
        assert_eq!(parsed.name, snippet.name);
        assert_eq!(parsed.content, snippet.content);
        assert_eq!(parsed.category, snippet.category);
        assert_eq!(parsed.is_default, snippet.is_default);
    }

    #[test]
    fn test_snippet_clone() {
        let original = create_test_snippet("clone-test", "Clone Test", "Category", true);
        let cloned = original.clone();

        assert_eq!(original.id, cloned.id);
        assert_eq!(original.name, cloned.name);
        assert_eq!(original.is_default, cloned.is_default);
    }

    #[test]
    #[allow(clippy::useless_vec)]
    fn test_snippet_sorting_by_category_then_name() {
        let mut snippets = vec![
            create_test_snippet("s1", "Zebra", "B-Category", false),
            create_test_snippet("s2", "Apple", "A-Category", false),
            create_test_snippet("s3", "Banana", "B-Category", false),
            create_test_snippet("s4", "Alpha", "A-Category", false),
        ];

        // Sort by category, then by name (mimics list_snippets behavior)
        snippets.sort_by(|a, b| {
            let cat_cmp = a.category.cmp(&b.category);
            if cat_cmp == std::cmp::Ordering::Equal {
                a.name.cmp(&b.name)
            } else {
                cat_cmp
            }
        });

        // A-Category should come first
        assert_eq!(snippets[0].category, "A-Category");
        assert_eq!(snippets[1].category, "A-Category");
        assert_eq!(snippets[2].category, "B-Category");
        assert_eq!(snippets[3].category, "B-Category");

        // Within categories, alphabetically by name
        assert_eq!(snippets[0].name, "Alpha");
        assert_eq!(snippets[1].name, "Apple");
        assert_eq!(snippets[2].name, "Banana");
        assert_eq!(snippets[3].name, "Zebra");
    }

    #[test]
    fn test_known_default_snippets() {
        let defaults = get_default_snippets();
        let ids: Vec<&str> = defaults.iter().map(|s| s.id.as_str()).collect();

        assert!(ids.contains(&"default-explain-code"));
        assert!(ids.contains(&"default-fix-error"));
        assert!(ids.contains(&"default-write-tests"));
        assert!(ids.contains(&"default-refactor"));
        assert!(ids.contains(&"default-optimize"));
        assert!(ids.contains(&"default-review-pr"));
        assert!(ids.contains(&"default-add-comments"));
        assert!(ids.contains(&"default-security-review"));
    }

    #[test]
    fn test_default_snippet_categories() {
        let defaults = get_default_snippets();
        let categories: HashSet<&String> = defaults.iter().map(|s| &s.category).collect();

        assert!(categories.contains(&"Code Review".to_string()));
        assert!(categories.contains(&"Debugging".to_string()));
        assert!(categories.contains(&"Testing".to_string()));
        assert!(categories.contains(&"Performance".to_string()));
        assert!(categories.contains(&"Documentation".to_string()));
        assert!(categories.contains(&"Security".to_string()));
    }

    #[test]
    fn test_snippet_content_not_empty() {
        let defaults = get_default_snippets();
        for snippet in defaults {
            assert!(
                snippet.content.len() > 10,
                "Content should be meaningful: {}",
                snippet.name
            );
        }
    }

    #[test]
    fn test_snippet_timestamps() {
        let snippet = create_test_snippet("time-test", "Time Test", "Cat", false);
        assert!(snippet.created_at <= snippet.updated_at);
    }

    #[test]
    fn test_default_snippets_have_same_timestamps() {
        let defaults = get_default_snippets();
        // All defaults are created at the same instant
        let first_created = defaults[0].created_at;
        let first_updated = defaults[0].updated_at;

        for snippet in &defaults {
            assert_eq!(snippet.created_at, first_created);
            assert_eq!(snippet.updated_at, first_updated);
        }
    }

    #[test]
    fn test_snippet_retain_non_default() {
        let mut snippets = vec![
            create_test_snippet("default-1", "Default 1", "Cat", true),
            create_test_snippet("custom-1", "Custom 1", "Cat", false),
            create_test_snippet("default-2", "Default 2", "Cat", true),
            create_test_snippet("custom-2", "Custom 2", "Cat", false),
        ];

        // Mimics reset_default_snippets behavior (retain non-defaults)
        snippets.retain(|s| !s.is_default);

        assert_eq!(snippets.len(), 2);
        assert!(snippets.iter().all(|s| !s.is_default));
    }

    #[test]
    #[allow(clippy::useless_vec)]
    fn test_snippet_find_by_id() {
        let snippets = vec![
            create_test_snippet("snippet-1", "First", "Cat", false),
            create_test_snippet("snippet-2", "Second", "Cat", false),
            create_test_snippet("snippet-3", "Third", "Cat", false),
        ];

        let found = snippets.iter().find(|s| s.id == "snippet-2");
        assert!(found.is_some());
        assert_eq!(found.unwrap().name, "Second");

        let not_found = snippets.iter().find(|s| s.id == "snippet-999");
        assert!(not_found.is_none());
    }

    #[test]
    #[allow(clippy::useless_vec)]
    fn test_extract_categories_sorted_and_deduped() {
        let snippets = vec![
            create_test_snippet("s1", "S1", "Zebra", false),
            create_test_snippet("s2", "S2", "Alpha", false),
            create_test_snippet("s3", "S3", "Beta", false),
            create_test_snippet("s4", "S4", "Alpha", false), // Duplicate
        ];

        let mut categories: Vec<String> = snippets.iter().map(|s| s.category.clone()).collect();
        categories.sort();
        categories.dedup();

        assert_eq!(categories.len(), 3);
        assert_eq!(categories[0], "Alpha");
        assert_eq!(categories[1], "Beta");
        assert_eq!(categories[2], "Zebra");
    }

    #[test]
    fn test_snippet_category_code_review_count() {
        let defaults = get_default_snippets();
        let code_review_count = defaults
            .iter()
            .filter(|s| s.category == "Code Review")
            .count();

        // There should be multiple code review snippets
        assert!(code_review_count >= 2);
    }
}
