"""Tests for AST → GBNF converter."""
import pytest
from bex.gbnf import to_gbnf, to_gbnf_with_rules
from bex.grammar import (
    Symbol, Concat, Alt, Plus, Optional, Star, Epsilon, Empty,
)


_a = Symbol('a')
_b = Symbol('b')
_c = Symbol('c')
_d = Symbol('d')


class TestToGBNF:
    def test_literal(self):
        assert to_gbnf(Symbol('mockk')) == 'root ::= "mockk"'

    def test_concat(self):
        assert to_gbnf(Concat([Symbol('raise'), Symbol('ValueError')])) == 'root ::= "raise" "ValueError"'

    def test_plus_group(self):
        assert to_gbnf(Plus(Symbol('append'))) == 'root ::= "append"+'

    def test_plus_concat(self):
        assert to_gbnf(Concat([Symbol('raise'), Plus(Symbol('ValueError'))])) == 'root ::= "raise" "ValueError"+'

    def test_nested_optional_plus(self):
        g = Concat([Symbol('assertEquals'), Plus(Concat([Symbol('of'), Optional(Symbol('assertFailsWith'))]))])
        assert to_gbnf(g) == 'root ::= "assertEquals" ("of" "assertFailsWith"?)+'

    def test_long_concat(self):
        g = Concat([Symbol('filesIn'), Symbol('filter'), Symbol('contains'),
                    Symbol('assertTrue'), Plus(Symbol('hasImport'))])
        assert to_gbnf(g) == 'root ::= "filesIn" "filter" "contains" "assertTrue" "hasImport"+'

    def test_simple_concat(self):
        g = Concat([Symbol('trim'), Symbol('lowercase'), Plus(Symbol('warn'))])
        assert to_gbnf(g) == 'root ::= "trim" "lowercase" "warn"+'

    def test_flat_concat(self):
        g = Concat([Symbol('DoclingConfig'), Symbol('assertThatThrownBy'),
                    Symbol('validateCriticalSettings'), Symbol('isInstanceOf'),
                    Symbol('hasMessageContaining')])
        assert to_gbnf(g) == 'root ::= "DoclingConfig" "assertThatThrownBy" "validateCriticalSettings" "isInstanceOf" "hasMessageContaining"'

    def test_simple_plus(self):
        assert to_gbnf(Plus(Symbol('abort'))) == 'root ::= "abort"+'

    def test_concat_with_plus(self):
        assert to_gbnf(Concat([Symbol('return'), Plus(Symbol('url_for'))])) == 'root ::= "return" "url_for"+'

    def test_star(self):
        assert to_gbnf(Star(Symbol('foo'))) == 'root ::= "foo"*'

    def test_optional(self):
        assert to_gbnf(Optional(Symbol('bar'))) == 'root ::= "bar"?'


class TestToGBNFWithRules:
    def test_named_rule(self):
        g = Concat([Symbol('raise'), Plus(Symbol('ValueError'))])
        result = to_gbnf_with_rules(g, name='my-pattern')
        assert result == 'my-pattern ::= "raise" "ValueError"+'

    def test_default_name(self):
        result = to_gbnf_with_rules(Symbol('mockk'))
        assert result == 'root ::= "mockk"'

    def test_nested(self):
        g = Concat([Symbol('assertEquals'), Plus(Concat([Symbol('of'), Optional(Symbol('assertFailsWith'))]))])
        result = to_gbnf_with_rules(g)
        assert result == 'root ::= "assertEquals" ("of" "assertFailsWith"?)+'


class TestGBNFDisjunction:
    """Test Alt nodes in GBNF output."""

    def test_simple_disjunction(self):
        assert to_gbnf(Alt([_a, _b])) == 'root ::= "a" | "b"'

    def test_disjunction_with_rep(self):
        assert to_gbnf(Plus(Alt([_a, _b]))) == 'root ::= ("a" | "b")+'

    def test_disjunction_optional(self):
        assert to_gbnf(Optional(Alt([_a, _b]))) == 'root ::= ("a" | "b")?'

    def test_disjunction_star(self):
        assert to_gbnf(Star(Alt([_a, _b]))) == 'root ::= ("a" | "b")*'

    def test_disjunction_in_concat(self):
        g = Concat([Plus(_a), Plus(Alt([Symbol('BAD_REQUEST'), Symbol('CONFLICT')]))])
        assert to_gbnf(g) == 'root ::= "a"+ ("BAD_REQUEST" | "CONFLICT")+'

    def test_four_way_disjunction(self):
        g = Plus(Alt([Symbol('assertEquals'), Symbol('authorize'), Symbol('coEvery'), Symbol('coVerify')]))
        assert to_gbnf(g) == 'root ::= ("assertEquals" | "authorize" | "coEvery" | "coVerify")+'

    def test_disjunction_parenthesized_in_concat(self):
        g = Concat([Plus(_a), Alt([_b, _c])])
        assert to_gbnf(g) == 'root ::= "a"+ ("b" | "c")'

    def test_disjunction_rep_then_disjunction(self):
        g = Concat([Plus(Alt([_a, _b])), Alt([_c, _d])])
        assert to_gbnf(g) == 'root ::= ("a" | "b")+ ("c" | "d")'


class TestGBNFCompoundRepetition:
    """Test compound repetition: nested quantifiers collapse correctly."""

    def test_plus_question(self):
        g = Plus(Optional(_a))
        result = to_gbnf(g)
        assert '"a"?+' in result or '"a"*' in result

    def test_plus_star(self):
        g = Plus(Star(_a))
        result = to_gbnf(g)
        assert '"a"*+' in result or '"a"*' in result

    def test_question_plus(self):
        g = Optional(Plus(_a))
        result = to_gbnf(g)
        assert '"a"?+' in result or '"a"+' in result

    def test_flask_pattern(self):
        g = Concat([
            Symbol('return'),
            Plus(Alt([Symbol('key'), Symbol('self')])),
            Plus(Alt([Symbol('Markup'), Symbol('UUID')])),
            Plus(Symbol('to_json')),
        ])
        result = to_gbnf(g)
        assert '"return"' in result
        assert '"key"' in result
        assert '"self"' in result
        assert '"Markup"' in result
        assert '"UUID"' in result
        assert '"to_json"' in result

    def test_double_plus(self):
        g = Concat([Plus(_a), _b])
        assert to_gbnf(g) == 'root ::= "a"+ "b"'
