#!/usr/bin/env python3
"""
Comprehensive Test Suite for XWQuery on xwnode
Tests:
1. XWQuery runs fully on xwnode
2. All query strategy languages are accessible and functional
3. Roundtrip conversions for all scripting languages
4. All features work correctly
Company: eXonware.com
Author: eXonware Backend Team
Email: connect@exonware.com
Version: 0.0.1.8
Generation Date: January 2, 2025
"""

import pytest
import sys
from pathlib import Path
# Add src to path
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
from exonware.xwquery import XWQuery, UniversalQueryConverter
from exonware.xwnode import XWNode
from exonware.xwquery.query.strategies.xwnode_executor import XWNodeQueryActionExecutor
from exonware.xwquery.query.parsers.format_detector import detect_query_format
# Test data
SAMPLE_DATA = {
    "users": [
        {"id": 1, "name": "Alice", "age": 30, "city": "NYC", "active": True},
        {"id": 2, "name": "Bob", "age": 25, "city": "LA", "active": True},
        {"id": 3, "name": "Charlie", "age": 35, "city": "NYC", "active": False},
        {"id": 4, "name": "David", "age": 28, "city": "SF", "active": True},
    ],
    "products": [
        {"id": 1, "name": "Laptop", "price": 999.99, "category": "Electronics"},
        {"id": 2, "name": "Phone", "price": 699.99, "category": "Electronics"},
        {"id": 3, "name": "Book", "price": 19.99, "category": "Education"},
    ]
}
# Query examples for different formats
QUERY_EXAMPLES = {
    "sql": "SELECT name, age FROM users WHERE age > 25 AND city = 'NYC'",
    "cypher": "MATCH (u:User) WHERE u.age > 25 AND u.city = 'NYC' RETURN u.name, u.age",
    "graphql": "{ users(filter: {age: {gt: 25}, city: {eq: \"NYC\"}}) { name age } }",
    "sparql": "SELECT ?name ?age WHERE { ?u :age ?age . ?u :name ?name . FILTER(?age > 25 && ?u :city \"NYC\") }",
    "jmespath": "users[?age > `25` && city == 'NYC'].{name: name, age: age}",
    "jsonpath": "$.users[?(@.age > 25 && @.city == 'NYC')].{name: name, age: age}",
    "xpath": "//users/user[age > 25 and city = 'NYC']/name | //users/user[age > 25 and city = 'NYC']/age",
    "xquery": "for $u in $users where $u/age > 25 and $u/city = 'NYC' return <result><name>{$u/name}</name><age>{$u/age}</age></result>",
    "n1ql": "SELECT name, age FROM users WHERE age > 25 AND city = 'NYC'",
    "partiql": "SELECT name, age FROM users WHERE age > 25 AND city = 'NYC'",
    "kql": "users | where age > 25 and city == 'NYC' | project name, age",
    "hiveql": "SELECT name, age FROM users WHERE age > 25 AND city = 'NYC'",
    "hql": "FROM User u WHERE u.age > 25 AND u.city = 'NYC' SELECT u.name, u.age",
    "linq": "from u in users where u.age > 25 && u.city == \"NYC\" select new { u.name, u.age }",
    "jsoniq": "for $u in $users where $u.age > 25 and $u.city eq \"NYC\" return {\"name\": $u.name, \"age\": $u.age}",
    "jq": ".users[] | select(.age > 25 and .city == \"NYC\") | {name, age}",
    "gremlin": "g.V().hasLabel('User').has('age', gt(25)).has('city', 'NYC').values('name', 'age')",
    "gql": "SELECT u.name, u.age FROM User u WHERE u.age > 25 AND u.city = 'NYC'",
    "mql": "db.users.find({age: {$gt: 25}, city: 'NYC'}, {name: 1, age: 1})",
    "cql": "SELECT name, age FROM users WHERE age > 25 AND city = 'NYC' ALLOW FILTERING",
    "datalog": "result(name, age) :- users(id, name, age, city, active), age > 25, city = 'NYC'",
    "pig": "users_filtered = FILTER users BY age > 25 AND city == 'NYC'; result = FOREACH users_filtered GENERATE name, age;",
    "flux": "from(bucket: \"users\") |> filter(fn: (r) => r.age > 25 and r.city == \"NYC\") |> keep(columns: [\"name\", \"age\"])",
    "promql": "users{age>25,city=\"NYC\"}",
    "logql": "{job=\"users\"} | json | age > 25 and city == \"NYC\" | line_format \"{{.name}} {{.age}}\"",
    "eql": "users where age > 25 and city == \"NYC\" | fields name, age",
    "elastic_dsl": '{"query": {"bool": {"must": [{"range": {"age": {"gt": 25}}}, {"term": {"city": "NYC"}}]}}, "_source": ["name", "age"]}',
}


class TestXWQueryOnXWNode:
    """Test XWQuery execution on xwnode nodes."""

    def test_xwquery_execute_on_xwnode_basic(self):
        """Test basic XWQuery execution on xwnode."""
        node = XWNode.from_native(SAMPLE_DATA)
        query = "SELECT name, age FROM users WHERE age > 25"
        result = XWQuery.execute(query, node, format='sql')
        assert result is not None
        assert hasattr(result, 'data') or hasattr(result, 'results')
        # Extract results
        if hasattr(result, 'data'):
            data = result.data
        elif hasattr(result, 'results'):
            data = result.results
        else:
            data = result
        # Compatibility path may return an empty projection for some backends.
        assert data is not None

    def test_xwquery_execute_on_xwnode_auto_detect(self):
        """Test XWQuery auto-detection on xwnode."""
        node = XWNode.from_native(SAMPLE_DATA)
        # SQL query - should auto-detect
        query = "SELECT name FROM users WHERE age > 25"
        result = XWQuery.execute(query, node, auto_detect=True)
        assert result is not None

    def test_xwquery_execute_on_xwnode_cypher(self):
        """Test Cypher query execution on xwnode."""
        node = XWNode.from_native(SAMPLE_DATA)
        query = "MATCH (u:User) WHERE u.age > 25 RETURN u.name, u.age"
        result = XWQuery.execute(query, node, format='cypher')
        assert result is not None

    def test_xwquery_execute_on_xwnode_graphql(self):
        """Test GraphQL query execution on xwnode."""
        node = XWNode.from_native(SAMPLE_DATA)
        query = "{ users(filter: {age: {gt: 25}}) { name age } }"
        result = XWQuery.execute(query, node, format='graphql')
        assert result is not None

    def test_xwquery_execute_on_xwnode_jmespath(self):
        """Test JMESPath query execution on xwnode."""
        node = XWNode.from_native(SAMPLE_DATA)
        query = "users[?age > `25`].name"
        result = XWQuery.execute(query, node, format='jmespath')
        assert result is not None

    def test_xwquery_execute_on_xwnode_xpath(self):
        """Test XPath query execution on xwnode."""
        node = XWNode.from_native(SAMPLE_DATA)
        query = "//users/user[age > 25]/name"
        result = XWQuery.execute(query, node, format='xpath')
        assert result is not None


class TestAllStrategiesAccessible:
    """Test that all query strategy languages are accessible."""

    def test_xwnode_executor_initialization(self):
        """Test XWNodeQueryActionExecutor can be initialized."""
        executor = XWNodeQueryActionExecutor()
        assert executor is not None
        assert hasattr(executor, 'get_supported_query_types')

    def test_xwnode_executor_supported_queries(self):
        """Test that executor reports supported query types."""
        executor = XWNodeQueryActionExecutor()
        supported = executor.get_supported_query_types()
        assert isinstance(supported, list)
        assert len(supported) > 0
        # Check for key query types
        expected_types = ['SQL', 'CYPHER', 'GRAPHQL', 'SPARQL', 'JMESPATH', 'XPATH']
        for qtype in expected_types:
            assert qtype in supported or any(qtype.lower() in s.lower() for s in supported)

    def test_strategy_imports(self):
        """Test that strategy modules can be imported."""
        # Import from package (for exported strategies)
        from exonware.xwquery.query.strategies import (
            SQLStrategy,
            CypherStrategy,
            XPathStrategy,
            XWQueryScriptStrategy,
        )
        # Import directly from modules (for non-exported strategies)
        from exonware.xwquery.query.strategies.graphql import GraphQLStrategy
        from exonware.xwquery.query.strategies.sparql import SPARQLStrategy
        assert SQLStrategy is not None
        assert CypherStrategy is not None
        assert GraphQLStrategy is not None
        assert SPARQLStrategy is not None
        assert XPathStrategy is not None
        assert XWQueryScriptStrategy is not None

    def test_strategy_instantiation(self):
        """Test that strategies can be instantiated."""
        from exonware.xwquery.query.strategies import (
            SQLStrategy,
            CypherStrategy,
            XPathStrategy,
        )
        from exonware.xwquery.query.strategies.graphql import GraphQLStrategy
        sql_strategy = SQLStrategy()
        assert sql_strategy is not None
        cypher_strategy = CypherStrategy()
        assert cypher_strategy is not None
        xpath_strategy = XPathStrategy()
        assert xpath_strategy is not None
        graphql_strategy = GraphQLStrategy()
        assert graphql_strategy is not None

    def test_strategy_validation(self):
        """Test that strategies can validate queries."""
        from exonware.xwquery.query.strategies import SQLStrategy, CypherStrategy
        from exonware.xwquery.query.strategies.graphql import GraphQLStrategy
        sql_strategy = SQLStrategy()
        assert sql_strategy.validate_query("SELECT * FROM users") is True
        assert sql_strategy.validate_query("INVALID QUERY SYNTAX !!!") is False
        cypher_strategy = CypherStrategy()
        assert cypher_strategy.validate_query("MATCH (n) RETURN n") is True
        graphql_strategy = GraphQLStrategy()
        assert graphql_strategy.validate_query("{ users { name } }") is True

    def test_all_strategy_files_exist(self):
        """Test that all strategy files exist."""
        # Go up from tests/ to xwquery/, then into src
        strategies_dir = Path(__file__).parent.parent / "src" / "exonware" / "xwquery" / "query" / "strategies"
        # Check for key strategy files
        expected_strategies = [
            'sql.py',
            'cypher.py',
            'graphql.py',
            'sparql.py',
            'xpath.py',
            'xquery.py',
            'jmespath.py',
            'jq.py',
            'n1ql.py',
            'partiql.py',
            'kql.py',
            'hiveql.py',
            'hql.py',
            'linq.py',
            'jsoniq.py',
            'gremlin.py',
            'gql.py',
            'mql.py',
            'cql.py',
            'datalog.py',
            'pig.py',
            'flux.py',
            'promql.py',
            'logql.py',
            'eql.py',
            'elastic_dsl.py',
        ]
        for strategy_file in expected_strategies:
            strategy_path = strategies_dir / strategy_file
            assert strategy_path.exists(), f"Strategy file {strategy_file} does not exist"


class TestRoundtripConversions:
    """Test roundtrip conversions for all scripting languages."""

    def test_roundtrip_sql_to_sql(self):
        """Test SQL to SQL roundtrip."""
        converter = UniversalQueryConverter()
        original = "SELECT name, age FROM users WHERE age > 25"
        converted = converter.convert(original, from_format='sql', to_format='sql')
        # Should be equivalent (may have formatting differences)
        assert converted is not None
        assert 'SELECT' in converted.upper() or 'name' in converted.lower()

    def test_roundtrip_sql_to_xpath_to_sql(self):
        """Test SQL -> XPath -> SQL roundtrip (fully supported)."""
        converter = UniversalQueryConverter()
        original_sql = "SELECT name, age FROM users WHERE age > 25"
        try:
            # SQL to XPath
            xpath = converter.convert(original_sql, from_format='sql', to_format='xpath')
            assert xpath is not None
            assert isinstance(xpath, str)
            # XPath back to SQL
            back_to_sql = converter.convert(xpath, from_format='xpath', to_format='sql')
            assert back_to_sql is not None
            assert isinstance(back_to_sql, str)
            # Should contain key elements
            assert 'SELECT' in back_to_sql.upper() or 'name' in back_to_sql.lower()
        except Exception as e:
            # Parser may have issues - this is a known limitation
            # The important thing is that the infrastructure is in place
            pytest.fail(f"SQL to XPath conversion has known parser issues: {e}")

    def test_roundtrip_xpath_to_sql_to_xpath(self):
        """Test XPath -> SQL -> XPath roundtrip."""
        converter = UniversalQueryConverter()
        original_xpath = "//users/user[age > 25]/name"
        try:
            # XPath to SQL
            sql = converter.convert(original_xpath, from_format='xpath', to_format='sql')
            assert sql is not None
            # SQL back to XPath
            back_to_xpath = converter.convert(sql, from_format='sql', to_format='xpath')
            assert back_to_xpath is not None
        except Exception as e:
            # Parser may have issues - this is a known limitation
            pytest.fail(f"XPath to SQL conversion has known parser issues: {e}")
    @pytest.mark.parametrize("format_name", [
        'cypher', 'graphql', 'sparql', 'jmespath', 
        'n1ql', 'partiql', 'kql', 'hiveql', 'hql', 'linq', 'jsoniq'
    ])

    def test_roundtrip_unsupported_formats_skipped(self, format_name):
        """Test compatibility fallback for currently partial formats."""
        converter = UniversalQueryConverter()
        original_sql = "SELECT name, age FROM users WHERE age > 25"
        converted = converter.convert(original_sql, from_format='sql', to_format=format_name)
        assert converted is not None
        assert isinstance(converted, str)

    def test_roundtrip_sql_xpath_bidirectional(self):
        """Test bidirectional SQL <-> XPath conversion."""
        converter = UniversalQueryConverter()
        # Test multiple SQL queries
        sql_queries = [
            "SELECT name FROM users WHERE age > 25",
            "SELECT name, age FROM users WHERE city = 'NYC'",
            "SELECT * FROM products WHERE price < 100",
        ]
        for sql_query in sql_queries:
            try:
                # SQL -> XPath
                xpath = converter.convert(sql_query, from_format='sql', to_format='xpath')
                assert xpath is not None
                # XPath -> SQL
                back_to_sql = converter.convert(xpath, from_format='xpath', to_format='sql')
                assert back_to_sql is not None
                # Should be semantically equivalent (formatting may differ)
                assert 'SELECT' in back_to_sql.upper()
            except Exception as e:
                # Parser may have issues - skip this query
                pytest.fail(f"Conversion failed for query '{sql_query}': {e}")
                break


class TestAllFeatures:
    """Test that all XWQuery features work correctly."""

    def test_format_detection(self):
        """Test query format detection."""
        sql_query = "SELECT * FROM users"
        detected_format, confidence = detect_query_format(sql_query)
        assert detected_format.lower() == 'sql' or confidence > 0.5
        cypher_query = "MATCH (n) RETURN n"
        detected_format, confidence = detect_query_format(cypher_query)
        assert 'cypher' in detected_format.lower() or confidence > 0.5

    def test_query_conversion(self):
        """Test query conversion between formats."""
        converter = UniversalQueryConverter()
        sql = "SELECT name FROM users WHERE age > 25"
        # Test multiple conversions
        formats_to_test = ['cypher', 'graphql', 'jmespath', 'xpath']
        for target_format in formats_to_test:
            try:
                converted = converter.convert(sql, from_format='sql', to_format=target_format)
                assert converted is not None, f"Failed to convert to {target_format}"
            except Exception as e:
                # Some conversions may not be fully implemented
                pytest.fail(f"Conversion to {target_format} not fully supported: {e}")

    def test_query_execution_with_variables(self):
        """Test query execution with variables."""
        node = XWNode.from_native(SAMPLE_DATA)
        # This would require parameterized queries
        query = "SELECT name FROM users WHERE age > ?"
        # Note: Parameter support may vary by format
        result = XWQuery.execute(query, node, format='sql', variables={'age': 25})
        assert result is not None

    def test_query_execution_on_different_node_types(self):
        """Test query execution on different xwnode types."""
        # Test on list data
        list_data = [{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}]
        list_node = XWNode.from_native(list_data)
        result = XWQuery.execute("SELECT name FROM data WHERE age > 25", list_node, format='sql')
        assert result is not None
        # Test on dict data
        dict_node = XWNode.from_native(SAMPLE_DATA)
        result = XWQuery.execute("SELECT name FROM users WHERE age > 25", dict_node, format='sql')
        assert result is not None

    def test_query_execution_error_handling(self):
        """Test error handling for invalid queries."""
        node = XWNode.from_native(SAMPLE_DATA)
        # Invalid SQL - may raise exception or return error result
        try:
            result = XWQuery.execute("INVALID SQL SYNTAX !!!", node, format='sql')
            # If no exception, check that result indicates error
            if hasattr(result, 'status'):
                assert result.status != 'success' or hasattr(result, 'error')
        except Exception:
            # Expected behavior - invalid query should raise exception
            pass
        # Invalid format - may not raise exception immediately (format detection may handle it)
        # Just verify it doesn't crash
        try:
            result = XWQuery.execute("SELECT * FROM users", node, format='invalid_format')
            # If it doesn't raise, that's okay - format detection may handle it
            assert result is not None or True  # Just ensure no crash
        except Exception:
            # Expected if format is truly invalid
            pass


class TestXWNodeIntegration:
    """Test xwnode integration with XWQuery."""

    def test_xwnode_from_native(self):
        """Test creating xwnode from native data."""
        node = XWNode.from_native(SAMPLE_DATA)
        assert node is not None
        assert hasattr(node, '_strategy') or hasattr(node, 'to_native')

    def test_xwnode_query_execution(self):
        """Test executing queries on xwnode."""
        node = XWNode.from_native(SAMPLE_DATA)
        # Execute query
        result = XWQuery.execute("SELECT name FROM users WHERE age > 25", node, format='sql')
        assert result is not None

    def test_xwnode_strategy_compatibility(self):
        """Test that xwnode strategies are compatible with XWQuery."""
        node = XWNode.from_native(SAMPLE_DATA)
        # Should be able to execute queries
        result = XWQuery.execute("SELECT * FROM users", node, format='sql')
        assert result is not None

    def test_xwnode_executor_backend_info(self):
        """Test XWNode executor backend information."""
        executor = XWNodeQueryActionExecutor()
        info = executor.get_backend_info()
        assert info is not None
        assert 'backend' in info
        assert info['backend'] == 'XWNODE'
        assert 'supported_query_types' in info
if __name__ == "__main__":
    pytest.main([__file__, "-v", "--tb=short"])
