"""Property-based tests for AST nodes."""

import pytest
from hypothesis import given, strategies as st

from ink.ast_nodes import (
    Proposition,
    SVOClause,
    ClassificationEdge,
    Rule,
    Query,
    VerbDeclaration,
)


# Generators for AST nodes
@st.composite
def identifiers(draw):
    """Generate valid identifiers (Chinese characters or alphanumeric)"""
    # Generate either Chinese characters or simple alphanumeric identifiers
    return draw(st.one_of(
        st.text(min_size=1, max_size=10).filter(lambda x: x.strip() and not x.startswith('其')),
        st.from_regex(r'[A-Za-z][A-Za-z0-9]*', fullmatch=True)
    ))


@st.composite
def variable_identifiers(draw):
    """Generate variable identifiers (starting with 其)"""
    base = draw(st.one_of(
        st.text(min_size=1, max_size=10).filter(lambda x: x.strip()),
        st.from_regex(r'[A-Za-z][A-Za-z0-9]*', fullmatch=True)
    ))
    return f'其{base}'


@st.composite
def propositions(draw):
    """Generate Proposition nodes"""
    is_var = draw(st.booleans())
    if is_var:
        identifier = draw(variable_identifiers())
    else:
        identifier = draw(identifiers())
    return Proposition(identifier, is_var)


@st.composite
def svo_clauses(draw):
    """Generate SVOClause nodes"""
    subject_is_var = draw(st.booleans())
    verb_is_var = draw(st.booleans())
    object_is_var = draw(st.booleans())
    
    subject = draw(variable_identifiers() if subject_is_var else identifiers())
    verb = draw(variable_identifiers() if verb_is_var else identifiers())
    obj = draw(variable_identifiers() if object_is_var else identifiers())
    
    return SVOClause(subject, subject_is_var, verb, verb_is_var, obj, object_is_var)


@st.composite
def classification_edges(draw):
    """Generate ClassificationEdge nodes"""
    subtype_is_var = draw(st.booleans())
    supertype_is_var = draw(st.booleans())
    
    subtype = draw(variable_identifiers() if subtype_is_var else identifiers())
    supertype = draw(variable_identifiers() if supertype_is_var else identifiers())
    
    return ClassificationEdge(subtype, subtype_is_var, supertype, supertype_is_var)


# Feature: ink-language-mvp, Property 49: Variable flag correctness
@given(variable_identifiers())
def test_proposition_variable_flag_correctness(var_identifier):
    """
    Property 49: Variable flag correctness
    Validates: Requirements 18.2
    
    For any identifier beginning with 其, the is_variable flag should be True
    """
    prop = Proposition(var_identifier, True)
    assert prop.is_variable is True
    assert prop.identifier.startswith('其')


@given(identifiers())
def test_proposition_non_variable_flag_correctness(identifier):
    """
    Property 49: Variable flag correctness (non-variable case)
    Validates: Requirements 18.2
    
    For any identifier not beginning with 其, the is_variable flag should be False
    """
    prop = Proposition(identifier, False)
    assert prop.is_variable is False
    assert not prop.identifier.startswith('其')


@given(variable_identifiers(), variable_identifiers(), variable_identifiers())
def test_svo_clause_variable_flags(subj_var, verb_var, obj_var):
    """
    Property 49: Variable flag correctness for SVO clauses
    Validates: Requirements 18.2
    
    For any SVO clause with variable identifiers, the is_variable flags should be True
    """
    clause = SVOClause(subj_var, True, verb_var, True, obj_var, True)
    assert clause.subject_is_var is True
    assert clause.verb_is_var is True
    assert clause.object_is_var is True
    assert clause.subject.startswith('其')
    assert clause.verb.startswith('其')
    assert clause.object.startswith('其')


@given(variable_identifiers(), variable_identifiers())
def test_classification_edge_variable_flags(subtype_var, supertype_var):
    """
    Property 49: Variable flag correctness for classification edges
    Validates: Requirements 18.2
    
    For any classification edge with variable identifiers, the is_variable flags should be True
    """
    edge = ClassificationEdge(subtype_var, True, supertype_var, True)
    assert edge.subtype_is_var is True
    assert edge.supertype_is_var is True
    assert edge.subtype.startswith('其')
    assert edge.supertype.startswith('其')


# Feature: ink-language-mvp, Property 50: AST node equality
@given(propositions())
def test_proposition_equality(prop):
    """
    Property 50: AST node equality
    Validates: Requirements 18.4
    
    For any two AST nodes with identical structure and values, they should be equal
    """
    prop_copy = Proposition(prop.identifier, prop.is_variable)
    assert prop == prop_copy
    assert hash(prop) == hash(prop_copy)


@given(svo_clauses())
def test_svo_clause_equality(clause):
    """
    Property 50: AST node equality for SVO clauses
    Validates: Requirements 18.4
    
    For any two SVO clauses with identical values, they should be equal
    """
    clause_copy = SVOClause(
        clause.subject, clause.subject_is_var,
        clause.verb, clause.verb_is_var,
        clause.object, clause.object_is_var
    )
    assert clause == clause_copy
    assert hash(clause) == hash(clause_copy)


@given(classification_edges())
def test_classification_edge_equality(edge):
    """
    Property 50: AST node equality for classification edges
    Validates: Requirements 18.4
    
    For any two classification edges with identical values, they should be equal
    """
    edge_copy = ClassificationEdge(
        edge.subtype, edge.subtype_is_var,
        edge.supertype, edge.supertype_is_var
    )
    assert edge == edge_copy
    assert hash(edge) == hash(edge_copy)


@given(
    st.lists(st.one_of(propositions(), svo_clauses(), classification_edges()), min_size=1, max_size=3),
    st.one_of(propositions(), svo_clauses(), classification_edges())
)
def test_rule_equality(premises, conclusion):
    """
    Property 50: AST node equality for rules
    Validates: Requirements 18.4
    
    For any two rules with identical premises and conclusion, they should be equal
    """
    rule1 = Rule(premises, conclusion)
    rule2 = Rule(list(premises), conclusion)  # Create new list with same elements
    assert rule1 == rule2
    assert hash(rule1) == hash(rule2)


@given(st.one_of(propositions(), svo_clauses(), classification_edges()))
def test_query_equality(expression):
    """
    Property 50: AST node equality for queries
    Validates: Requirements 18.4
    
    For any two queries with identical expressions, they should be equal
    """
    query1 = Query(expression)
    query2 = Query(expression)
    assert query1 == query2
    assert hash(query1) == hash(query2)


@given(identifiers())
def test_verb_declaration_equality(verb):
    """
    Property 50: AST node equality for verb declarations
    Validates: Requirements 18.4
    
    For any two verb declarations with identical verbs, they should be equal
    """
    decl1 = VerbDeclaration(verb)
    decl2 = VerbDeclaration(verb)
    assert decl1 == decl2
    assert hash(decl1) == hash(decl2)


@given(propositions())
def test_ast_nodes_are_immutable(prop):
    """
    Test that AST nodes are immutable (frozen dataclasses)
    """
    with pytest.raises(AttributeError):
        prop.identifier = "new_value"
