use anyhow::{Result, bail};
use serde::{Deserialize, Serialize};
use tantivy::tokenizer::{TextAnalyzer, TokenizerManager};
use tici_grpc::{DEFAULT_PARSER_INFO, FullTextIndexInfo, FullTextParserType};

use crate::analysis::{token_filter::TokenFilterType, tokenizer::TokenizerType};

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct AnalyzerDef {
    name: String,
    tokenizer: TokenizerType,
    token_filters: Vec<TokenFilterType>,
}

// Analyzer names used in TiCI for MySQL compatibility
const MY_STANDARD_ANALYZER_NAME: &str = "tici_my_standard";
const MY_NGRAM_ANALYZER_NAME: &str = "tici_my_ngram";

impl AnalyzerDef {
    pub fn new(
        name: String,
        tokenizer: TokenizerType,
        token_filters: Vec<TokenFilterType>,
    ) -> Self {
        Self {
            name,
            tokenizer,
            token_filters,
        }
    }

    fn standard_v1_analyzer(parser_info: &tici_grpc::ParserInfo) -> Result<AnalyzerDef> {
        let mut token_filters = vec![
            TokenFilterType::Length {
                min: parser_info.get_innodb_ft_min_token_size()?,
                max: parser_info.get_innodb_ft_max_token_size()?,
            },
            TokenFilterType::LowerCaser,
        ];

        if parser_info.get_innodb_ft_enable_stopword()? {
            token_filters.push(TokenFilterType::StopWord {
                stop_words: parser_info
                    .get_stopwords()?
                    .map(|stopwords| stopwords.to_vec()),
            });
        }

        Ok(AnalyzerDef::new(
            MY_STANDARD_ANALYZER_NAME.to_string(),
            TokenizerType::PreserveUnderscore,
            token_filters,
        ))
    }

    fn ngram_v1_analyzer(parser_info: &tici_grpc::ParserInfo) -> Result<AnalyzerDef> {
        let ngram_token_size = parser_info.get_ngram_token_size()?;
        let token_filters = vec![
            TokenFilterType::Ngram {
                min: ngram_token_size,
                max: ngram_token_size,
            },
            TokenFilterType::LowerCaser,
        ];
        Ok(AnalyzerDef::new(
            MY_NGRAM_ANALYZER_NAME.to_string(),
            TokenizerType::PreserveUnderscore,
            token_filters,
        ))
    }

    fn raw_analyzer() -> Result<AnalyzerDef> {
        Ok(AnalyzerDef::new(
            RAW_ANALYZER_NAME.to_string(),
            TokenizerType::Keyword,
            vec![],
        ))
    }

    pub fn from_fulltext_index_info(
        ft_info: &FullTextIndexInfo,
        parser_info: &tici_grpc::ParserInfo,
    ) -> Result<AnalyzerDef> {
        match ft_info.parser_type {
            FullTextParserType::StandardV1 => Self::standard_v1_analyzer(parser_info),
            FullTextParserType::NgramV1 => Self::ngram_v1_analyzer(parser_info),
            _ => bail!("Unsupported parser type: {:?}", ft_info.parser_type),
        }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn has_ngram_filter(&self) -> bool {
        self.token_filters
            .iter()
            .any(|filter_type| matches!(filter_type, TokenFilterType::Ngram { .. }))
    }

    pub fn builtin_by_name(name: &str) -> Result<Option<AnalyzerDef>> {
        match name {
            DEFAULT_ANALYZER_NAME => Ok(Some(Self::standard_v1_analyzer(&DEFAULT_PARSER_INFO)?)),
            RAW_ANALYZER_NAME => Ok(Some(Self::raw_analyzer()?)),
            _ => Ok(None),
        }
    }

    pub fn get_source_tokens_with_positions(&self, text: &str) -> Result<Vec<(usize, String)>> {
        let mut tokenizer = self.tokenizer.build()?.build();
        let mut stream = tokenizer.token_stream(text);
        let mut tokens = Vec::new();
        stream.process(&mut |token| {
            tokens.push((token.position, token.text.clone()));
        });
        Ok(tokens)
    }

    pub fn build(&self) -> Result<TextAnalyzer> {
        let mut analyzer_builder = self.tokenizer.build()?;
        for filter_type in &self.token_filters {
            analyzer_builder = filter_type.build(analyzer_builder)?;
        }
        Ok(analyzer_builder.build())
    }

    pub fn build_prefix_query_analyzer(&self) -> Result<TextAnalyzer> {
        let mut analyzer_builder = self.tokenizer.build()?;
        for filter_type in &self.token_filters {
            analyzer_builder = filter_type.build_for_prefix_query(analyzer_builder)?;
        }
        Ok(analyzer_builder.build())
    }
}

// Required analyzer names by Tantivy
pub const DEFAULT_ANALYZER_NAME: &str = "default";
pub const RAW_ANALYZER_NAME: &str = "raw";
const RESERVED_ANALYZER_NAMES: [&str; 2] = [DEFAULT_ANALYZER_NAME, RAW_ANALYZER_NAME];

pub fn create_analyzer_manager(analyzer_defs: &[AnalyzerDef]) -> Result<TokenizerManager> {
    let manager = TokenizerManager::new();
    let mut analyzer_names = std::collections::HashSet::new();

    for analyzer_def in analyzer_defs {
        if RESERVED_ANALYZER_NAMES.contains(&analyzer_def.name.as_str()) {
            bail!(
                "Analyzer name '{}' is reserved by TiCI for internal use",
                analyzer_def.name
            );
        }
        if !analyzer_names.insert(analyzer_def.name.as_str()) {
            bail!("Duplicate analyzer name '{}'", analyzer_def.name);
        }
        let analyzer = analyzer_def.build()?;
        manager.register(&analyzer_def.name, analyzer.clone());
    }

    // Tantivy requires a "raw" analyzer for the STRING option.
    let raw_analyzer_def = AnalyzerDef::raw_analyzer()?;
    manager.register(&raw_analyzer_def.name, raw_analyzer_def.build()?);

    // Tantivy requires that there must be an analyzer named "default".
    // https://github.com/quickwit-oss/tantivy/blob/270ca5123c0dcb6bc362e20299d9cd3f82612feb/src/indexer/segment_writer.rs#L89-L98
    let default_analyzer_def = AnalyzerDef::standard_v1_analyzer(&DEFAULT_PARSER_INFO)?;

    manager.register(DEFAULT_ANALYZER_NAME, default_analyzer_def.build()?);

    Ok(manager)
}

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

    fn collect_tokens(analyzer: &mut TextAnalyzer, text: &str) -> Vec<String> {
        let mut token_stream = analyzer.token_stream(text);
        let mut tokens = Vec::new();
        while let Some(token) = token_stream.next() {
            tokens.push(token.text.clone());
        }
        tokens
    }

    #[test]
    fn test_analyzer_def_new() {
        let analyzer_def = AnalyzerDef::new(
            "test_analyzer".to_string(),
            TokenizerType::Simple,
            vec![TokenFilterType::LowerCaser],
        );
        assert_eq!(analyzer_def.name(), "test_analyzer");
    }

    #[test]
    fn test_analyzer_def_build_simple_with_lowercaser() -> Result<()> {
        let analyzer_def = AnalyzerDef::new(
            "test".to_string(),
            TokenizerType::Simple,
            vec![TokenFilterType::LowerCaser],
        );
        let mut analyzer = analyzer_def.build()?;
        let tokens = collect_tokens(&mut analyzer, "Hello WORLD");
        assert_eq!(tokens, vec!["hello", "world"]);
        Ok(())
    }

    #[test]
    fn test_analyzer_def_build_keyword() -> Result<()> {
        let analyzer_def = AnalyzerDef::new("test".to_string(), TokenizerType::Keyword, vec![]);
        let mut analyzer = analyzer_def.build()?;
        let tokens = collect_tokens(&mut analyzer, "Hello World Example");
        assert_eq!(tokens, vec!["Hello World Example"]);
        Ok(())
    }

    #[test]
    fn test_analyzer_def_build_with_length_filter() -> Result<()> {
        let analyzer_def = AnalyzerDef::new(
            "test".to_string(),
            TokenizerType::Simple,
            vec![
                TokenFilterType::Length { min: 3, max: 84 },
                TokenFilterType::LowerCaser,
            ],
        );
        let mut analyzer = analyzer_def.build()?;
        // "ab" (2 chars) should be filtered out, "hello" (5 chars) should pass
        let tokens = collect_tokens(&mut analyzer, "ab hello xy world");
        assert_eq!(tokens, vec!["hello", "world"]);
        Ok(())
    }

    #[test]
    fn test_analyzer_def_build_with_stopword() -> Result<()> {
        let analyzer_def = AnalyzerDef::new(
            "test".to_string(),
            TokenizerType::Simple,
            vec![
                TokenFilterType::LowerCaser,
                TokenFilterType::StopWord { stop_words: None },
            ],
        );
        let mut analyzer = analyzer_def.build()?;
        let tokens = collect_tokens(&mut analyzer, "This is a test");
        // "This" -> "this" -> stopword removed, "is" removed, "a" removed
        assert_eq!(tokens, vec!["test"]);
        Ok(())
    }

    #[test]
    fn test_analyzer_def_stopword_then_lower() -> Result<()> {
        let analyzer_def = AnalyzerDef::new(
            "test".to_string(),
            TokenizerType::Simple,
            vec![
                TokenFilterType::StopWord { stop_words: None },
                TokenFilterType::LowerCaser,
            ],
        );
        let mut analyzer = analyzer_def.build()?;
        let tokens = collect_tokens(&mut analyzer, "This is a test");
        // "This" not matched as stopword (case-sensitive) -> survives -> lowercased ->
        // "this" "is" is removed
        assert_eq!(tokens, vec!["this", "test"]);
        Ok(())
    }

    #[test]
    fn test_analyzer_def_build_with_ngram_filter() -> Result<()> {
        let analyzer_def = AnalyzerDef::new(
            "test".to_string(),
            TokenizerType::Simple,
            vec![TokenFilterType::Ngram { min: 2, max: 3 }],
        );
        let mut analyzer = analyzer_def.build()?;
        let tokens = collect_tokens(&mut analyzer, "abc");
        assert_eq!(tokens, vec!["ab", "abc", "bc"]);
        Ok(())
    }

    #[test]
    fn test_from_fulltext_index_info_standard_v1() -> Result<()> {
        let ft_info = FullTextIndexInfo {
            parser_type: FullTextParserType::StandardV1,
        };
        let analyzer_def = AnalyzerDef::from_fulltext_index_info(&ft_info, &DEFAULT_PARSER_INFO)?;
        assert_eq!(analyzer_def.name(), MY_STANDARD_ANALYZER_NAME);

        let mut analyzer = analyzer_def.build()?;
        // Test MySQL default behavior: min token length = 3
        let tokens = collect_tokens(&mut analyzer, "ab abc HELLO");
        // "ab" filtered (length < 3), "abc" and "hello" pass
        assert_eq!(tokens, vec!["abc", "hello"]);
        Ok(())
    }

    #[test]
    fn test_from_fulltext_index_info_ngram_v1() -> Result<()> {
        let ft_info = FullTextIndexInfo {
            parser_type: FullTextParserType::NgramV1,
        };
        let analyzer_def = AnalyzerDef::from_fulltext_index_info(&ft_info, &DEFAULT_PARSER_INFO)?;
        assert_eq!(analyzer_def.name(), MY_NGRAM_ANALYZER_NAME);

        let mut analyzer = analyzer_def.build()?;
        // Default ngram token size = 2
        let tokens = collect_tokens(&mut analyzer, "hello");
        assert_eq!(tokens, vec!["he", "el", "ll", "lo"]);
        Ok(())
    }

    #[test]
    fn test_from_fulltext_index_info_unsupported() {
        let ft_info = FullTextIndexInfo {
            parser_type: FullTextParserType::Invalid,
        };
        let result = AnalyzerDef::from_fulltext_index_info(&ft_info, &DEFAULT_PARSER_INFO);
        match result {
            Ok(_) => panic!("expected error for unsupported parser type"),
            Err(err) => assert!(err.to_string().contains("Unsupported parser type")),
        }
    }

    #[test]
    fn test_from_fulltext_index_info_multilingual_unsupported() {
        let ft_info = FullTextIndexInfo {
            parser_type: FullTextParserType::MultilingualV1,
        };
        let result = AnalyzerDef::from_fulltext_index_info(&ft_info, &DEFAULT_PARSER_INFO);
        assert!(result.is_err());
    }

    #[test]
    fn test_create_analyzer_manager_empty() -> Result<()> {
        let manager = create_analyzer_manager(&[])?;
        // Should have default and raw analyzers
        assert!(manager.get(DEFAULT_ANALYZER_NAME).is_some());
        assert!(manager.get(RAW_ANALYZER_NAME).is_some());
        Ok(())
    }

    #[test]
    fn test_create_analyzer_manager_with_custom_analyzer() -> Result<()> {
        let custom_analyzer = AnalyzerDef::new(
            "custom".to_string(),
            TokenizerType::Simple,
            vec![TokenFilterType::LowerCaser],
        );
        let manager = create_analyzer_manager(&[custom_analyzer])?;
        assert!(manager.get("custom").is_some());
        assert!(manager.get(DEFAULT_ANALYZER_NAME).is_some());
        assert!(manager.get(RAW_ANALYZER_NAME).is_some());
        Ok(())
    }

    #[test]
    fn test_create_analyzer_manager_reserved_name_default() {
        let reserved_analyzer = AnalyzerDef::new(
            DEFAULT_ANALYZER_NAME.to_string(),
            TokenizerType::Simple,
            vec![],
        );
        let result = create_analyzer_manager(&[reserved_analyzer]);
        match result {
            Ok(_) => panic!("expected error for reserved analyzer name"),
            Err(err) => assert!(err.to_string().contains("reserved")),
        }
    }

    #[test]
    fn test_create_analyzer_manager_reserved_name_raw() {
        let reserved_analyzer =
            AnalyzerDef::new(RAW_ANALYZER_NAME.to_string(), TokenizerType::Simple, vec![]);
        let result = create_analyzer_manager(&[reserved_analyzer]);
        match result {
            Ok(_) => panic!("expected error for reserved analyzer name"),
            Err(err) => assert!(err.to_string().contains("reserved")),
        }
    }

    #[test]
    fn test_create_analyzer_manager_duplicate_name() {
        let analyzer_a = AnalyzerDef::new(
            "dup".to_string(),
            TokenizerType::Simple,
            vec![TokenFilterType::LowerCaser],
        );
        let analyzer_b = AnalyzerDef::new(
            "dup".to_string(),
            TokenizerType::Simple,
            vec![TokenFilterType::LowerCaser],
        );
        let result = create_analyzer_manager(&[analyzer_a, analyzer_b]);
        match result {
            Ok(_) => panic!("expected error for duplicate analyzer name"),
            Err(err) => assert!(err.to_string().contains("Duplicate analyzer name")),
        }
    }

    #[test]
    fn test_analyzer_def_serialization() -> Result<()> {
        let analyzer_def = AnalyzerDef::new(
            "test".to_string(),
            TokenizerType::Simple,
            vec![
                TokenFilterType::Length { min: 3, max: 84 },
                TokenFilterType::LowerCaser,
            ],
        );
        let json = serde_json::to_string(&analyzer_def)?;
        let deserialized: AnalyzerDef = serde_json::from_str(&json)?;
        assert_eq!(deserialized, analyzer_def);
        Ok(())
    }

    #[test]
    fn test_default_analyzer_behavior() -> Result<()> {
        let manager = create_analyzer_manager(&[])?;
        let mut default_analyzer = manager
            .get(DEFAULT_ANALYZER_NAME)
            .expect("default analyzer should exist");
        // Default analyzer should apply length filter (min=3), lowercaser, and stopword
        let tokens = collect_tokens(&mut default_analyzer, "This is AB test HELLO");
        // "This" -> lowercase -> "this" -> stopword removed
        // "is" -> stopword removed
        // "AB" -> length < 3 -> filtered
        // "test" -> passes
        // "HELLO" -> lowercase -> "hello" -> passes
        assert_eq!(tokens, vec!["test", "hello"]);
        Ok(())
    }

    #[test]
    fn test_raw_analyzer_behavior() -> Result<()> {
        let manager = create_analyzer_manager(&[])?;
        let mut raw_analyzer = manager
            .get(RAW_ANALYZER_NAME)
            .expect("raw analyzer should exist");
        // Raw analyzer should keep text as-is
        let tokens = collect_tokens(&mut raw_analyzer, "Hello World Example");
        assert_eq!(tokens, vec!["Hello World Example"]);
        Ok(())
    }

    #[test]
    fn test_mysql_standard_max_token_length() -> Result<()> {
        let ft_info = FullTextIndexInfo {
            parser_type: FullTextParserType::StandardV1,
        };
        let analyzer_def = AnalyzerDef::from_fulltext_index_info(&ft_info, &DEFAULT_PARSER_INFO)?;
        let mut analyzer = analyzer_def.build()?;

        // Create a token of exactly 84 chars (max) - should pass
        let token_84 = "a".repeat(84);
        let tokens = collect_tokens(&mut analyzer, &token_84);
        assert_eq!(tokens.len(), 1);
        assert_eq!(tokens[0].len(), 84);

        // Create a token of 85 chars - should be filtered
        let token_85 = "b".repeat(85);
        let tokens = collect_tokens(&mut analyzer, &token_85);
        assert!(tokens.is_empty());
        Ok(())
    }

    #[test]
    fn test_mysql_standard_min_token_length() -> Result<()> {
        let ft_info = FullTextIndexInfo {
            parser_type: FullTextParserType::StandardV1,
        };
        let analyzer_def = AnalyzerDef::from_fulltext_index_info(&ft_info, &DEFAULT_PARSER_INFO)?;
        let mut analyzer = analyzer_def.build()?;

        // Token of 2 chars - should be filtered (min is 3)
        let tokens = collect_tokens(&mut analyzer, "ab");
        assert!(tokens.is_empty());

        // Token of 3 chars - should pass
        let tokens = collect_tokens(&mut analyzer, "abc");
        assert_eq!(tokens, vec!["abc"]);
        Ok(())
    }
}
