"""Tests for CRX with dotted symbols (e.g. 'foo.bar', 'request.body').

These tests expose the fundamental bug: CRX outputs grammars where '.'
means both concatenation AND is part of symbol names. The parser splits
on all dots, breaking matches.

Every test here MUST pass after the fix. They all fail before.
"""

from bex.crx import CRX
from bex.ensemble import _matches


class TestCRXDotInSymbol:
    """CRX must handle symbols containing dots."""

    def test_single_dotted_symbol(self):
        """One symbol with a dot: ['foo.bar']."""
        crx = CRX()
        g = crx.infer([['foo.bar']])
        assert g is not None
        assert _matches(g, ['foo.bar'])

    def test_two_dotted_symbols(self):
        """Two different dotted symbols: ['foo.bar', 'baz.qux']."""
        crx = CRX()
        g = crx.infer([['foo.bar', 'baz.qux']])
        assert g is not None
        assert _matches(g, ['foo.bar', 'baz.qux'])

    def test_dotted_symbol_repeated(self):
        """Dotted symbol appears multiple times: ['foo.bar', 'foo.bar', 'baz']."""
        crx = CRX()
        g = crx.infer([['foo.bar', 'foo.bar', 'baz']])
        assert g is not None
        assert _matches(g, ['foo.bar', 'foo.bar', 'baz'])

    def test_dotted_symbols_multiple_sequences(self):
        """Multiple sequences with dotted symbols."""
        crx = CRX()
        seqs = [
            ['return.capability', 'invoke.request'],
            ['return.capability', 'invoke'],
        ]
        g = crx.infer(seqs)
        assert g is not None
        for seq in seqs:
            assert _matches(g, seq), f"Grammar {g!r} should match {seq}"

    def test_dotted_symbol_alternation(self):
        """Dotted symbols in alternation: ['a.b', 'a.c']."""
        crx = CRX()
        g = crx.infer([['a.b'], ['a.c']])
        assert g is not None
        assert _matches(g, ['a.b'])
        assert _matches(g, ['a.c'])

    def test_dotted_symbol_optional(self):
        """Optional dotted symbol: some sequences have it, some don't."""
        crx = CRX()
        seqs = [
            ['a.b', 'c'],
            ['c'],
        ]
        g = crx.infer(seqs)
        assert g is not None
        for seq in seqs:
            assert _matches(g, seq), f"Grammar {g!r} should match {seq}"

    def test_dotted_symbol_plus(self):
        """Repeated dotted symbol: ['a.b', 'a.b', 'a.b', 'c']."""
        crx = CRX()
        seqs = [
            ['a.b', 'a.b', 'a.b', 'c'],
            ['a.b', 'a.b', 'c'],
            ['a.b', 'c'],
        ]
        g = crx.infer(seqs)
        assert g is not None
        for seq in seqs:
            assert _matches(g, seq), f"Grammar {g!r} should match {seq}"

    def test_realistic_method_chain(self):
        """Realistic: method call chains like Kotlin/Python."""
        crx = CRX()
        seqs = [
            ['request.body', 'validate', 'save'],
            ['request.body', 'validate', 'return'],
            ['request.body', 'save'],
        ]
        g = crx.infer(seqs)
        assert g is not None
        for seq in seqs:
            assert _matches(g, seq), f"Grammar {g!r} should match {seq}"

    def test_multiple_dots_in_symbol(self):
        """Symbol with multiple dots: 'a.b.c'."""
        crx = CRX()
        seqs = [
            ['a.b.c', 'd.e.f'],
            ['a.b.c', 'g'],
        ]
        g = crx.infer(seqs)
        assert g is not None
        for seq in seqs:
            assert _matches(g, seq), f"Grammar {g!r} should match {seq}"

    def test_mixed_dotted_and_plain(self):
        """Mix of dotted and plain symbols."""
        crx = CRX()
        seqs = [
            ['foo.bar', 'baz', 'qux.quux'],
            ['foo.bar', 'baz'],
            ['baz', 'qux.quux'],
        ]
        g = crx.infer(seqs)
        assert g is not None
        for seq in seqs:
            assert _matches(g, seq), f"Grammar {g!r} should match {seq}"


class TestEnsembleDotInSymbol:
    """Ensemble (CRX + scoring) must handle dotted symbols."""

    def test_ensemble_dotted_symbols(self):
        """infer_ensemble with dotted symbols."""
        from bex.ensemble import infer_ensemble
        seqs = [
            ['request.body', 'validate', 'save'],
            ['request.body', 'validate', 'return'],
            ['request.body', 'save'],
        ]
        result = infer_ensemble(seqs)
        assert result['best'] is not None
        grammar = result['best']['grammar']
        for seq in seqs:
            assert _matches(grammar, seq), \
                f"Grammar {grammar!r} should match {seq}"

    def test_scoring_dotted_symbols(self):
        """lang_size_score with dotted symbols should not crash."""
        from bex.ensemble import infer_ensemble
        seqs = [
            ['foo.bar', 'baz.qux'],
            ['foo.bar', 'baz'],
        ]
        result = infer_ensemble(seqs)
        assert result['best'] is not None
        # Just checking it doesn't crash — the score should be finite
        score = result['best']['mdl_score']
        assert isinstance(score, (int, float))
        assert score < float('inf')
