#![allow(
    clippy::cast_possible_truncation,  // u32/usize → u8 is safe (result is 0-255)
)]

//! Search functionality for finding items across tools

use serde::{Deserialize, Serialize};

/// Search result from query
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SearchResult {
    /// Tool that found this result
    pub tool: String,
    /// Item identifier
    pub id: String,
    /// Item display name
    pub name: String,
    /// Matched field (e.g., "title", "description", "content")
    pub field: String,
    /// Matching text snippet
    pub snippet: String,
    /// Relevance score (0-100)
    pub relevance: u8,
}

impl SearchResult {
    /// Create new search result
    #[must_use]
    pub const fn new(
        tool: String,
        id: String,
        name: String,
        field: String,
        snippet: String,
        relevance: u8,
    ) -> Self {
        Self {
            tool,
            id,
            name,
            field,
            snippet,
            relevance,
        }
    }
}

/// Search query builder
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SearchQuery {
    /// Search terms
    pub terms: String,
    /// Case sensitive search
    pub case_sensitive: bool,
    /// Only search in specific tool (None = all tools)
    pub tool_filter: Option<String>,
    /// Regex pattern instead of text search
    pub regex: bool,
    /// Maximum results to return
    pub limit: usize,
}

impl SearchQuery {
    /// Create new search query
    #[must_use]
    pub const fn new(terms: String) -> Self {
        Self {
            terms,
            case_sensitive: false,
            tool_filter: None,
            regex: false,
            limit: 100,
        }
    }

    /// Set case sensitivity
    #[must_use]
    pub const fn with_case_sensitive(mut self, case_sensitive: bool) -> Self {
        self.case_sensitive = case_sensitive;
        self
    }

    /// Filter by tool
    #[must_use]
    pub fn with_tool_filter(mut self, tool: String) -> Self {
        self.tool_filter = Some(tool);
        self
    }

    /// Enable regex mode
    #[must_use]
    pub const fn with_regex(mut self, regex: bool) -> Self {
        self.regex = regex;
        self
    }

    /// Set result limit
    #[must_use]
    pub const fn with_limit(mut self, limit: usize) -> Self {
        self.limit = limit;
        self
    }
}

/// Search manager for finding content across tools
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SearchManager {
    /// Recent search results
    results: Vec<SearchResult>,
    /// Current query
    query: Option<SearchQuery>,
    /// Selected result index
    selected: usize,
    /// Search history
    history: Vec<String>,
    /// Max history size
    max_history: usize,
}

impl SearchManager {
    /// Create new search manager
    #[must_use]
    pub const fn new() -> Self {
        Self {
            results: Vec::new(),
            query: None,
            selected: 0,
            history: Vec::new(),
            max_history: 50,
        }
    }

    /// Execute search query
    pub fn search(&mut self, query: SearchQuery) -> usize {
        // Add to history
        if !query.terms.is_empty() && !self.history.contains(&query.terms) {
            self.history.push(query.terms.clone());
            while self.history.len() > self.max_history {
                self.history.remove(0);
            }
        }

        self.query = Some(query);
        self.selected = 0;
        self.results.len()
    }

    /// Set search results
    pub fn set_results(&mut self, results: Vec<SearchResult>) {
        self.results = results;
        self.selected = 0;
    }

    /// Get current results
    #[must_use]
    pub fn results(&self) -> &[SearchResult] {
        &self.results
    }

    /// Get selected result
    #[must_use]
    pub fn selected_result(&self) -> Option<&SearchResult> {
        self.results.get(self.selected)
    }

    /// Get current query
    #[must_use]
    pub const fn query(&self) -> Option<&SearchQuery> {
        self.query.as_ref()
    }

    /// Select next result
    pub const fn select_next(&mut self) {
        if !self.results.is_empty() {
            self.selected = (self.selected + 1) % self.results.len();
        }
    }

    /// Select previous result
    pub const fn select_previous(&mut self) {
        if !self.results.is_empty() {
            self.selected = if self.selected == 0 {
                self.results.len() - 1
            } else {
                self.selected - 1
            };
        }
    }

    /// Clear results
    pub fn clear_results(&mut self) {
        self.results.clear();
        self.query = None;
        self.selected = 0;
    }

    /// Get search history
    #[must_use]
    pub fn history(&self) -> &[String] {
        &self.history
    }

    /// Clear search history
    pub fn clear_history(&mut self) {
        self.history.clear();
    }

    /// Get result count
    #[must_use]
    pub const fn result_count(&self) -> usize {
        self.results.len()
    }

    /// Get results by tool
    #[must_use]
    pub fn results_by_tool(&self, tool: &str) -> Vec<&SearchResult> {
        self.results.iter().filter(|r| r.tool == tool).collect()
    }

    /// Get average relevance
    #[must_use]
    pub fn average_relevance(&self) -> u8 {
        if self.results.is_empty() {
            return 0;
        }
        // SAFETY: u32 / u32 division result fits in u8 (relevance is u8)
        // usize to u32 is safe as search results list is unlikely to exceed u32::MAX
        (self
            .results
            .iter()
            .map(|r| u32::from(r.relevance))
            .sum::<u32>()
            / self.results.len() as u32) as u8
    }
}

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

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

    #[test]
    fn test_search_result_new() {
        let result = SearchResult::new(
            "tool1".to_string(),
            "id1".to_string(),
            "Item 1".to_string(),
            "title".to_string(),
            "Item 1 description".to_string(),
            75,
        );

        assert_eq!(result.tool, "tool1");
        assert_eq!(result.relevance, 75);
    }

    #[test]
    fn test_search_query_new() {
        let query = SearchQuery::new("test".to_string());
        assert_eq!(query.terms, "test");
        assert!(!query.case_sensitive);
        assert!(!query.regex);
        assert_eq!(query.limit, 100);
    }

    #[test]
    fn test_search_query_builder() {
        let query = SearchQuery::new("test".to_string())
            .with_case_sensitive(true)
            .with_tool_filter("tool1".to_string())
            .with_limit(50);

        assert!(query.case_sensitive);
        assert_eq!(query.tool_filter, Some("tool1".to_string()));
        assert_eq!(query.limit, 50);
    }

    #[test]
    fn test_search_manager_new() {
        let manager = SearchManager::new();
        assert!(manager.results.is_empty());
        assert!(manager.query.is_none());
    }

    #[test]
    fn test_set_results() {
        let mut manager = SearchManager::new();
        let results = vec![
            SearchResult::new(
                "tool1".to_string(),
                "id1".to_string(),
                "Item 1".to_string(),
                "title".to_string(),
                "Item 1".to_string(),
                80,
            ),
            SearchResult::new(
                "tool2".to_string(),
                "id2".to_string(),
                "Item 2".to_string(),
                "title".to_string(),
                "Item 2".to_string(),
                60,
            ),
        ];

        manager.set_results(results);

        assert_eq!(manager.result_count(), 2);
    }

    #[test]
    fn test_select_next() {
        let mut manager = SearchManager::new();
        manager.set_results(vec![
            SearchResult::new(
                "tool".to_string(),
                "1".to_string(),
                "Item 1".to_string(),
                "title".to_string(),
                "Item 1".to_string(),
                80,
            ),
            SearchResult::new(
                "tool".to_string(),
                "2".to_string(),
                "Item 2".to_string(),
                "title".to_string(),
                "Item 2".to_string(),
                60,
            ),
        ]);

        assert_eq!(manager.selected, 0);
        manager.select_next();
        assert_eq!(manager.selected, 1);
    }

    #[test]
    fn test_select_previous() {
        let mut manager = SearchManager::new();
        manager.set_results(vec![
            SearchResult::new(
                "tool".to_string(),
                "1".to_string(),
                "Item 1".to_string(),
                "title".to_string(),
                "Item 1".to_string(),
                80,
            ),
            SearchResult::new(
                "tool".to_string(),
                "2".to_string(),
                "Item 2".to_string(),
                "title".to_string(),
                "Item 2".to_string(),
                60,
            ),
        ]);

        manager.selected = 1;
        manager.select_previous();
        assert_eq!(manager.selected, 0);
    }

    #[test]
    fn test_search_history() {
        let mut manager = SearchManager::new();
        let query1 = SearchQuery::new("test1".to_string());
        let query2 = SearchQuery::new("test2".to_string());

        manager.search(query1);
        manager.search(query2);

        assert_eq!(manager.history().len(), 2);
        assert_eq!(manager.history[0], "test1");
        assert_eq!(manager.history[1], "test2");
    }

    #[test]
    fn test_search_history_limit() {
        let mut manager = SearchManager::new();
        manager.max_history = 3;

        for i in 0..5 {
            let query = SearchQuery::new(format!("test{i}"));
            manager.search(query);
        }

        assert_eq!(manager.history().len(), 3);
        assert_eq!(manager.history[0], "test2");
    }

    #[test]
    fn test_clear_results() {
        let mut manager = SearchManager::new();
        manager.set_results(vec![SearchResult::new(
            "tool".to_string(),
            "1".to_string(),
            "Item".to_string(),
            "title".to_string(),
            "Item".to_string(),
            80,
        )]);

        manager.clear_results();

        assert!(manager.results.is_empty());
        assert!(manager.query.is_none());
    }

    #[test]
    fn test_results_by_tool() {
        let mut manager = SearchManager::new();
        manager.set_results(vec![
            SearchResult::new(
                "tool1".to_string(),
                "1".to_string(),
                "Item 1".to_string(),
                "title".to_string(),
                "Item 1".to_string(),
                80,
            ),
            SearchResult::new(
                "tool2".to_string(),
                "2".to_string(),
                "Item 2".to_string(),
                "title".to_string(),
                "Item 2".to_string(),
                70,
            ),
            SearchResult::new(
                "tool1".to_string(),
                "3".to_string(),
                "Item 3".to_string(),
                "title".to_string(),
                "Item 3".to_string(),
                60,
            ),
        ]);

        let tool1_results = manager.results_by_tool("tool1");
        assert_eq!(tool1_results.len(), 2);
    }

    #[test]
    fn test_average_relevance() {
        let mut manager = SearchManager::new();
        manager.set_results(vec![
            SearchResult::new(
                "tool".to_string(),
                "1".to_string(),
                "Item 1".to_string(),
                "title".to_string(),
                "Item 1".to_string(),
                80,
            ),
            SearchResult::new(
                "tool".to_string(),
                "2".to_string(),
                "Item 2".to_string(),
                "title".to_string(),
                "Item 2".to_string(),
                60,
            ),
        ]);

        assert_eq!(manager.average_relevance(), 70);
    }
}
