//! 単語テーブルの外部テスト
//!
//! Phase A: registry/word_table.rs のインラインテストから外部化

use pasta_core::registry::{MockRandomSelector, WordDefRegistry, WordTable};
use pasta_core::WordTableError;

fn create_test_registry() -> WordDefRegistry {
    let mut registry = WordDefRegistry::new();
    // Global words
    registry.register_global(
        "挨拶",
        vec!["こんにちは".to_string(), "おはよう".to_string()],
    );
    registry.register_global("場所", vec!["東京".to_string()]);
    registry.register_global("場所_日本", vec!["大阪".to_string(), "京都".to_string()]);
    // Local words
    registry.register_local("会話_1", "挨拶", vec!["やあ".to_string()]);
    registry.register_local("会話_1", "場所", vec!["ここ".to_string()]);
    registry
}

#[test]
fn test_from_word_def_registry() {
    let registry = create_test_registry();
    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let table = WordTable::from_word_def_registry(registry, selector);

    assert_eq!(table.entries().len(), 5);
}

#[test]
fn test_collect_word_candidates_global_exact() {
    let registry = create_test_registry();
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let table = WordTable::from_word_def_registry(registry, selector);

    // Exact match for global word
    let candidates = table.collect_word_candidates("", "挨拶").unwrap();
    assert_eq!(candidates.len(), 2);
    assert!(candidates.contains(&"こんにちは".to_string()));
    assert!(candidates.contains(&"おはよう".to_string()));
}

#[test]
fn test_collect_word_candidates_global_prefix() {
    let registry = create_test_registry();
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let table = WordTable::from_word_def_registry(registry, selector);

    // Prefix match: "場所" matches both "場所" and "場所_日本"
    let candidates = table.collect_word_candidates("", "場所").unwrap();
    // Should get all 3 words merged (東京, 大阪, 京都)
    assert_eq!(candidates.len(), 3);
    assert!(candidates.contains(&"東京".to_string()));
    assert!(candidates.contains(&"大阪".to_string()));
    assert!(candidates.contains(&"京都".to_string()));
}

#[test]
fn test_collect_word_candidates_local_only() {
    let registry = create_test_registry();
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let table = WordTable::from_word_def_registry(registry, selector);

    // Local search only - does NOT include global words
    let candidates = table.collect_word_candidates("会話_1", "挨拶").unwrap();
    // Should get ONLY local word (やあ)
    assert_eq!(candidates.len(), 1);
    assert!(candidates.contains(&"やあ".to_string()));
    // Global words NOT included
    assert!(!candidates.contains(&"こんにちは".to_string()));
    assert!(!candidates.contains(&"おはよう".to_string()));
}

#[test]
fn test_collect_word_candidates_local_not_found_no_fallback() {
    let registry = create_test_registry();
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let table = WordTable::from_word_def_registry(registry, selector);

    // No local word for "場所" in module "別のモジュール"
    // Should NOT fall back to global - should return error
    let result = table.collect_word_candidates("別のモジュール", "場所");
    assert!(result.is_err());
    match result {
        Err(WordTableError::WordNotFound { key }) => assert_eq!(key, "場所"),
        _ => panic!("Expected WordNotFound error"),
    }
}

#[test]
fn test_collect_word_candidates_global_only() {
    let registry = create_test_registry();
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let table = WordTable::from_word_def_registry(registry, selector);

    // Empty module_name means global only search
    let candidates = table.collect_word_candidates("", "挨拶").unwrap();
    assert_eq!(candidates.len(), 2);
    assert!(candidates.contains(&"こんにちは".to_string()));
    assert!(candidates.contains(&"おはよう".to_string()));
}

#[test]
fn test_collect_word_candidates_duplicate_entries_merge() {
    // Test that duplicate entries are merged in collect_word_candidates
    let mut registry = WordDefRegistry::new();
    registry.register_global(
        "挨拶",
        vec!["おはよう".to_string(), "こんにちわ".to_string()],
    );
    registry.register_global(
        "挨拶",
        vec!["はろー".to_string(), "ぐっもーにん".to_string()],
    );

    let selector = Box::new(MockRandomSelector::new(vec![]));
    let table = WordTable::from_word_def_registry(registry, selector);

    // Should collect all 4 words before shuffling
    let candidates = table.collect_word_candidates("", "挨拶").unwrap();
    assert_eq!(candidates.len(), 4);
    assert!(candidates.contains(&"おはよう".to_string()));
    assert!(candidates.contains(&"こんにちわ".to_string()));
    assert!(candidates.contains(&"はろー".to_string()));
    assert!(candidates.contains(&"ぐっもーにん".to_string()));
}

#[test]
fn test_collect_word_candidates_not_found() {
    let registry = create_test_registry();
    let selector = Box::new(MockRandomSelector::new(vec![]));
    let table = WordTable::from_word_def_registry(registry, selector);

    let result = table.collect_word_candidates("", "存在しない");
    assert!(result.is_err());
    match result {
        Err(WordTableError::WordNotFound { key }) => assert_eq!(key, "存在しない"),
        _ => panic!("Expected WordNotFound error"),
    }
}

#[test]
fn test_search_word_global_exact() {
    let registry = create_test_registry();
    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = WordTable::from_word_def_registry(registry, selector);
    table.set_shuffle_enabled(false);

    // Exact match for global word
    let result = table.search_word("", "挨拶", &[]);
    assert!(result.is_ok());
    assert_eq!(result.unwrap(), "こんにちは");
}

#[test]
fn test_search_word_global_prefix() {
    let registry = create_test_registry();
    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = WordTable::from_word_def_registry(registry, selector);
    table.set_shuffle_enabled(false);

    // Prefix match: "場所" matches both "場所" and "場所_日本"
    let result = table.search_word("", "場所", &[]);
    assert!(result.is_ok());
    // Should get word from merged list (東京, 大阪, 京都)
    let word = result.unwrap();
    assert!(word == "東京" || word == "大阪" || word == "京都");
}

#[test]
fn test_search_word_local() {
    let registry = create_test_registry();
    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = WordTable::from_word_def_registry(registry, selector);
    table.set_shuffle_enabled(false);

    // Fallback strategy: Local search found, global NOT included
    let result = table.search_word("会話_1", "挨拶", &[]);
    assert!(result.is_ok());
    // Should get ONLY from local (やあ) - fallback strategy
    let word = result.unwrap();
    assert_eq!(word, "やあ");
}

#[test]
fn test_search_word_not_found() {
    let registry = create_test_registry();
    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = WordTable::from_word_def_registry(registry, selector);

    let result = table.search_word("", "存在しない", &[]);
    assert!(result.is_err());
    match result {
        Err(WordTableError::WordNotFound { key }) => assert_eq!(key, "存在しない"),
        _ => panic!("Expected WordNotFound error"),
    }
}

#[test]
fn test_search_word_cache_sequential() {
    let mut registry = WordDefRegistry::new();
    registry.register_global(
        "test",
        vec!["a".to_string(), "b".to_string(), "c".to_string()],
    );

    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = WordTable::from_word_def_registry(registry, selector);
    table.set_shuffle_enabled(false);

    // Sequential calls should return different words until exhausted
    let r1 = table.search_word("", "test", &[]).unwrap();
    let r2 = table.search_word("", "test", &[]).unwrap();
    let r3 = table.search_word("", "test", &[]).unwrap();

    assert_eq!(r1, "a");
    assert_eq!(r2, "b");
    assert_eq!(r3, "c");
}

#[test]
fn test_search_word_cache_reshuffle() {
    let mut registry = WordDefRegistry::new();
    registry.register_global("test", vec!["a".to_string(), "b".to_string()]);

    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = WordTable::from_word_def_registry(registry, selector);
    table.set_shuffle_enabled(false);

    // Exhaust cache
    let _ = table.search_word("", "test", &[]).unwrap();
    let _ = table.search_word("", "test", &[]).unwrap();

    // Next call should reshuffle and start over
    let r3 = table.search_word("", "test", &[]).unwrap();
    assert_eq!(r3, "a"); // Back to first (no shuffle)
}

#[test]
fn test_cache_key_separation() {
    let mut registry = WordDefRegistry::new();
    registry.register_global("word", vec!["global".to_string()]);
    registry.register_local("mod1", "word", vec!["local1".to_string()]);
    registry.register_local("mod2", "word", vec!["local2".to_string()]);

    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = WordTable::from_word_def_registry(registry, selector);
    table.set_shuffle_enabled(false);

    // Different modules should have separate caches
    let r1 = table.search_word("mod1", "word", &[]).unwrap();
    let r2 = table.search_word("mod2", "word", &[]).unwrap();

    // Fallback strategy: local found, global NOT included
    // mod1 search: ONLY local1 (fallback)
    // mod2 search: ONLY local2 (fallback)
    assert_eq!(r1, "local1");
    assert_eq!(r2, "local2");
}

#[test]
fn test_local_does_not_match_global_prefix() {
    let mut registry = WordDefRegistry::new();
    registry.register_global("abc", vec!["global_abc".to_string()]);
    registry.register_local("mod", "abc", vec!["local_abc".to_string()]);

    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = WordTable::from_word_def_registry(registry, selector);
    table.set_shuffle_enabled(false);

    // Global search should not include local entries
    let result = table.search_word("", "abc", &[]).unwrap();
    assert_eq!(result, "global_abc");
}

#[test]
fn test_search_word_merge_duplicate_entries() {
    // Test that multiple registrations of same key are merged at search time
    let mut registry = WordDefRegistry::new();

    // Same global word name registered twice
    registry.register_global(
        "挨拶",
        vec!["おはよう".to_string(), "こんにちわ".to_string()],
    );
    registry.register_global(
        "挨拶",
        vec!["はろー".to_string(), "ぐっもーにん".to_string()],
    );

    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = WordTable::from_word_def_registry(registry, selector);
    table.set_shuffle_enabled(false);

    // Search for "挨拶" should find both entries and merge all 4 words
    let result = table.search_word("", "挨拶", &[]);
    assert!(result.is_ok());

    // Verify that we get one of the 4 expected words
    let word = result.unwrap();
    assert!(
        word == "おはよう"
            || word == "こんにちわ"
            || word == "はろー"
            || word == "ぐっもーにん",
        "Unexpected word: {}",
        word
    );
}

#[test]
fn test_search_word_merge_duplicate_entries_all_words_reachable() {
    // Test that sequential calls reach all 4 words from duplicate entries
    let mut registry = WordDefRegistry::new();

    registry.register_global(
        "挨拶",
        vec!["おはよう".to_string(), "こんにちわ".to_string()],
    );
    registry.register_global(
        "挨拶",
        vec!["はろー".to_string(), "ぐっもーにん".to_string()],
    );

    let selector = Box::new(MockRandomSelector::new(vec![0, 1, 2, 3]));
    let mut table = WordTable::from_word_def_registry(registry, selector);
    table.set_shuffle_enabled(false);

    // Sequential calls should exhaust all 4 words
    let r1 = table.search_word("", "挨拶", &[]).unwrap();
    let r2 = table.search_word("", "挨拶", &[]).unwrap();
    let r3 = table.search_word("", "挨拶", &[]).unwrap();
    let r4 = table.search_word("", "挨拶", &[]).unwrap();

    // Collect results
    let results = vec![r1, r2, r3, r4];

    // All 4 words should be represented
    assert!(results.contains(&"おはよう".to_string()));
    assert!(results.contains(&"こんにちわ".to_string()));
    assert!(results.contains(&"はろー".to_string()));
    assert!(results.contains(&"ぐっもーにん".to_string()));
}

#[test]
fn test_search_word_merge_duplicate_local_entries() {
    // Test that duplicate local entries are merged
    let mut registry = WordDefRegistry::new();

    registry.register_local("会話", "挨拶", vec!["やあ".to_string(), "よう".to_string()]);
    registry.register_local(
        "会話",
        "挨拶",
        vec!["おす".to_string(), "ういっす".to_string()],
    );

    let selector = Box::new(MockRandomSelector::new(vec![0]));
    let mut table = WordTable::from_word_def_registry(registry, selector);
    table.set_shuffle_enabled(false);

    // Search for local word should find both entries
    let result = table.search_word("会話", "挨拶", &[]);
    assert!(result.is_ok());

    let word = result.unwrap();
    assert!(
        word == "やあ" || word == "よう" || word == "おす" || word == "ういっす",
        "Unexpected word: {}",
        word
    );
}

#[test]
fn test_search_word_merge_duplicate_with_global() {
    // Test that duplicate global + global entries are all merged
    // Pattern: search for global, should get all 5 words (2 + 3)
    let mut registry = WordDefRegistry::new();

    registry.register_global("word", vec!["a".to_string(), "b".to_string()]);
    registry.register_global(
        "word",
        vec!["c".to_string(), "d".to_string(), "e".to_string()],
    );

    let selector = Box::new(MockRandomSelector::new(vec![0, 1, 2, 3, 4]));
    let mut table = WordTable::from_word_def_registry(registry, selector);
    table.set_shuffle_enabled(false);

    // All 5 calls should succeed
    let results: Vec<_> = (0..5)
        .map(|_| table.search_word("", "word", &[]).unwrap())
        .collect();

    // Check all 5 words are reachable
    assert_eq!(results.len(), 5);
    assert!(results.contains(&"a".to_string()));
    assert!(results.contains(&"b".to_string()));
    assert!(results.contains(&"c".to_string()));
    assert!(results.contains(&"d".to_string()));
    assert!(results.contains(&"e".to_string()));
}
