import pytest
from multiplayer_crosswords.dictionary import Word, Dictionary

def test_add_word_and_to_dict():
    d = Dictionary()
    w = Word("apple", ["a red or green fruit"], 0.2)
    d.add_word(w)
    result = d.to_dict()
    assert "words" in result
    assert len(result["words"]) == 1
    assert result["words"][0]["word"] == "apple"
    assert result["words"][0]["hints"] == ["a red or green fruit"]
    assert result["words"][0]["difficulty"] == 0.2

def test_from_dict_and_to_dict_roundtrip():
    data = {
        "words": [
            {"word": "banana", "hints": ["a yellow fruit", "something monkeys like to eat"], "difficulty": 0.1},
            {"word": "car", "hints": ["vehicle", "four wheels"], "difficulty": 0.3}
        ]
    }
    d = Dictionary.from_dict(data)
    assert len(d.words) == 2
    assert d.words[0].word == "banana"
    assert d.words[1].hints == ["vehicle", "four wheels"]
    assert d.words[1].difficulty == 0.3
    assert d.to_dict() == data

def test_to_json_and_from_json():
    d = Dictionary()
    d.add_word(Word("dog", ["animal", "barks"], 0.15))
    json_str = d.to_json()
    d2 = Dictionary.from_json(json_str)
    assert len(d2.words) == 1
    assert d2.words[0].word == "dog"
    assert d2.words[0].hints == ["animal", "barks"]
    assert d2.words[0].difficulty == 0.15

def test_empty_dictionary_to_dict_and_json():
    d = Dictionary()
    assert d.to_dict() == {"words": []}
    json_str = d.to_json()
    assert json_str.strip().startswith("{")
    d2 = Dictionary.from_json(json_str)
    assert isinstance(d2, Dictionary)
    assert d2.words == []

def test_word_to_dict_and_from_dict():
    w = Word("cat", ["animal", "meows"], 0.12)
    d = w.to_dict()
    assert d == {"word": "cat", "hints": ["animal", "meows"], "difficulty": 0.12}
    w2 = Word.from_dict(d)
    assert w2.word == "cat"
    assert w2.hints == ["animal", "meows"]
    assert w2.difficulty == 0.12

def test_find_by_pattern_exact_match():
    d = Dictionary()
    d.add_word(Word("apple", ["fruit"], 0.2))
    d.add_word(Word("apply", ["verb"], 0.3))
    d.add_word(Word("angle", ["geometry"], 0.4))
    result = d.find_by_pattern("apple")
    assert [r.word for r in result] == ["apple"]

def test_find_by_pattern_with_wildcards():
    d = Dictionary()
    d.add_word(Word("apple", ["fruit"], 0.2))
    d.add_word(Word("apply", ["verb"], 0.3))
    d.add_word(Word("angle", ["geometry"], 0.4))
    d.add_word(Word("ample", ["enough"], 0.5))
    # Pattern: a**le should match apple and angle and ample
    result = sorted([w.word for w in d.find_by_pattern("a**le")])
    assert set(result) == {"apple", "angle", "ample"}

def test_find_by_pattern_all_wildcards():
    d = Dictionary()
    d.add_word(Word("apple", ["fruit"], 0.2))
    d.add_word(Word("apply", ["verb"], 0.3))
    d.add_word(Word("angle", ["geometry"], 0.4))
    # Pattern: ***** should match all 5-letter words
    result = sorted([w.word for w in d.find_by_pattern("*****")])
    assert set(result) == {"apple", "apply", "angle"}

def test_find_by_pattern_no_match():
    d = Dictionary()
    d.add_word(Word("apple", ["fruit"], 0.2))
    d.add_word(Word("apply", ["verb"], 0.3))
    # Pattern: z**** should match nothing
    result = d.find_by_pattern("z****")
    assert list(result) == []

def test_find_by_pattern_duplicate_letters():
    d = Dictionary()
    d.add_word(Word("level", ["word"], 0.1))
    d.add_word(Word("lever", ["tool"], 0.2))
    # Pattern: l*vel should match "level" only
    result = d.find_by_pattern("l*vel")
    assert [r.word for r in result] == ["level"]

def test_find_by_pattern_length_not_in_index():
    d = Dictionary()
    d.add_word(Word("cat", ["animal"], 0.1))
    # Pattern: four letters, but only 3-letter word in dict
    result = d.find_by_pattern("****")
    assert [r.word for r in result] == []
