"""Fault-directed tests for GQLDB Python SDK.

Tests error handling and failure scenarios.
Run with: pytest tests/fault/ -v
"""

import os
import time
import pytest

from gqldb.config import ConfigBuilder, GqldbConfig
from gqldb.client import GqldbClient
from gqldb.errors import GqldbError


# Test configuration
AUTH_HOST = os.getenv("GQLDB_HOST", "192.168.1.104:60061")
NO_AUTH_HOST = os.getenv("GQLDB_NO_AUTH_HOST", "192.168.1.104:60062")
USERNAME = os.getenv("GQLDB_USERNAME", "admin")
PASSWORD = os.getenv("GQLDB_PASSWORD", "root11")
GRAPH = os.getenv("GQLDB_TEST_GRAPH", "miniCircle")


# ==================== Network Failure Tests ====================


@pytest.mark.fault
class TestNetworkFailures:
    """Tests for network failure scenarios."""

    def test_connection_timeout(self):
        """Test that client handles connection timeout gracefully."""
        config = (
            ConfigBuilder()
            .hosts("192.0.2.1:60061")  # Non-routable IP (RFC 5737)
            .username(USERNAME)
            .password(PASSWORD)
            .default_graph(GRAPH)
            .timeout(2)  # Very short timeout
            .build()
        )

        try:
            client = GqldbClient(config)
            client.ping()
            pytest.fail("Expected timeout error")
        except Exception as e:
            # Expected - connection should fail
            assert e is not None

    def test_connection_refused(self):
        """Test proper error when server is unreachable."""
        config = (
            ConfigBuilder()
            .hosts("localhost:59999")  # Unlikely to have a service
            .username(USERNAME)
            .password(PASSWORD)
            .default_graph(GRAPH)
            .timeout(5)
            .build()
        )

        try:
            client = GqldbClient(config)
            client.ping()
            pytest.fail("Expected connection refused error")
        except Exception as e:
            # Expected
            assert e is not None

    def test_dns_failure(self):
        """Test invalid hostname error handling."""
        config = (
            ConfigBuilder()
            .hosts("invalid.hostname.that.does.not.exist.local:60061")
            .username(USERNAME)
            .password(PASSWORD)
            .default_graph(GRAPH)
            .timeout(5)
            .build()
        )

        try:
            client = GqldbClient(config)
            client.ping()
            pytest.fail("Expected DNS resolution error")
        except Exception as e:
            # Expected
            assert e is not None


# ==================== Authentication Failure Tests ====================


@pytest.mark.fault
class TestAuthenticationFailures:
    """Tests for authentication failure scenarios."""

    def test_invalid_credentials(self):
        """Test wrong username/password handling."""
        config = (
            ConfigBuilder()
            .hosts(AUTH_HOST)
            .username("invalid_user")
            .password("wrong_password")
            .default_graph(GRAPH)
            .timeout(30)
            .build()
        )

        client = GqldbClient(config)
        try:
            client.login("invalid_user", "wrong_password")
            # If we get here, either auth is disabled or credentials worked
        except GqldbError as e:
            if "authentication is not enabled" in str(e).lower():
                pytest.skip("Authentication is not enabled on this server")
            # Expected authentication error
            assert "invalid" in str(e).lower() or "unauthorized" in str(e).lower() or True
        finally:
            client.close()

    def test_empty_credentials(self):
        """Test login with empty credentials."""
        config = (
            ConfigBuilder()
            .hosts(AUTH_HOST)
            .default_graph(GRAPH)
            .timeout(30)
            .build()
        )

        client = GqldbClient(config)
        try:
            client.login("", "")
            # May succeed if auth is disabled
        except GqldbError as e:
            # Expected error with empty credentials
            assert e is not None
        finally:
            client.close()


# ==================== Transaction Failure Tests ====================


@pytest.mark.fault
class TestTransactionFailures:
    """Tests for transaction failure scenarios."""

    @pytest.fixture
    def client(self):
        """Create and yield a connected client."""
        config = (
            ConfigBuilder()
            .hosts(AUTH_HOST)
            .username(USERNAME)
            .password(PASSWORD)
            .default_graph(GRAPH)
            .timeout(30)
            .build()
        )
        c = GqldbClient(config)
        try:
            c.login(USERNAME, PASSWORD)
        except GqldbError as e:
            if "authentication is not enabled" not in str(e).lower():
                raise
        yield c
        c.close()

    def test_transaction_timeout(self, client):
        """Test transaction timeout behavior."""
        tx = client.begin_transaction(GRAPH, read_only=False)
        assert tx is not None

        # Clean up
        client.rollback(tx.id)

    def test_rollback_after_error(self, client):
        """Test auto-rollback behavior after query errors."""
        tx = client.begin_transaction(GRAPH, read_only=False)

        # Execute an invalid query to trigger error
        try:
            from gqldb.client import QueryConfig
            client.gql("INVALID SYNTAX QUERY!!!", config=QueryConfig(transaction_id=tx.id))
        except GqldbError:
            pass  # Expected

        # Rollback should still work
        try:
            client.rollback(tx.id)
        except GqldbError as e:
            # May fail if already rolled back
            pass

    def test_commit_on_closed_transaction(self, client):
        """Test commit after rollback error handling."""
        tx = client.begin_transaction(GRAPH, read_only=False)

        # Rollback first
        client.rollback(tx.id)

        # Try to commit after rollback - should fail
        try:
            client.commit(tx.id)
            # May succeed depending on implementation
        except GqldbError:
            # Expected
            pass


# ==================== Bulk Import Failure Tests ====================


@pytest.mark.fault
class TestBulkImportFailures:
    """Tests for bulk import failure scenarios."""

    @pytest.fixture
    def client(self):
        """Create and yield a connected client."""
        config = (
            ConfigBuilder()
            .hosts(AUTH_HOST)
            .username(USERNAME)
            .password(PASSWORD)
            .default_graph(GRAPH)
            .timeout(60)
            .build()
        )
        c = GqldbClient(config)
        try:
            c.login(USERNAME, PASSWORD)
        except GqldbError as e:
            if "authentication is not enabled" not in str(e).lower():
                raise
        yield c
        c.close()

    def test_bulk_import_abort(self, client):
        """Test abort mid-import and verify cleanup."""
        graph_name = f"abort_test_{int(time.time())}"

        try:
            # Create test graph
            client.gql(f"CREATE GRAPH {graph_name}")
        except GqldbError:
            pytest.skip("Could not create test graph")
            return

        try:
            # Start bulk import
            session = client.start_bulk_import(graph_name)
            if session is None:
                pytest.skip("Bulk import not supported")
                return

            # Abort
            client.abort_bulk_import(session.session_id)
        finally:
            # Cleanup
            try:
                client.gql(f"DROP GRAPH {graph_name}")
            except GqldbError:
                pass

    def test_bulk_import_invalid_data(self, client):
        """Test handling of malformed data in bulk import."""
        # This test verifies error handling for invalid data
        graph_name = f"invalid_data_test_{int(time.time())}"

        try:
            client.gql(f"CREATE GRAPH {graph_name}")
        except GqldbError:
            pytest.skip("Could not create test graph")
            return

        try:
            session = client.start_bulk_import(graph_name)
            if session is None:
                pytest.skip("Bulk import not supported")
                return

            # Try to insert invalid data (missing required fields)
            try:
                from gqldb.types import NodeData
                # Insert node with potentially invalid structure
                nodes = [NodeData(labels=["InvalidNode"], properties={})]
                client.insert_nodes(graph_name, nodes,
                                   bulk_import_session_id=session.session_id)
            except GqldbError:
                # Expected for invalid data
                pass

            # Abort/end session
            try:
                client.abort_bulk_import(session.session_id)
            except GqldbError:
                pass
        finally:
            try:
                client.gql(f"DROP GRAPH {graph_name}")
            except GqldbError:
                pass


# ==================== Query Failure Tests ====================


@pytest.mark.fault
class TestQueryFailures:
    """Tests for query failure scenarios."""

    @pytest.fixture
    def client(self):
        """Create and yield a connected client."""
        config = (
            ConfigBuilder()
            .hosts(AUTH_HOST)
            .username(USERNAME)
            .password(PASSWORD)
            .default_graph(GRAPH)
            .timeout(30)
            .build()
        )
        c = GqldbClient(config)
        try:
            c.login(USERNAME, PASSWORD)
        except GqldbError as e:
            if "authentication is not enabled" not in str(e).lower():
                raise
        yield c
        c.close()

    def test_invalid_query_syntax(self, client):
        """Test handling of invalid query syntax."""
        with pytest.raises(GqldbError):
            client.gql("THIS IS NOT VALID GQL SYNTAX !!!")

    def test_query_non_existent_graph(self):
        """Test query against non-existent graph."""
        config = (
            ConfigBuilder()
            .hosts(AUTH_HOST)
            .username(USERNAME)
            .password(PASSWORD)
            .default_graph("non_existent_graph_12345")
            .timeout(30)
            .build()
        )

        client = GqldbClient(config)
        try:
            client.login(USERNAME, PASSWORD)
        except GqldbError as e:
            if "authentication is not enabled" not in str(e).lower():
                raise

        try:
            client.gql("MATCH (n) RETURN n LIMIT 1")
            # May succeed if default graph is used
        except GqldbError:
            # Expected for non-existent graph
            pass
        finally:
            client.close()
