"""End-to-end integration tests for GQLDB Python SDK.

Complete workflow tests for real-world scenarios.
Run with: pytest tests/e2e/ -v
"""

import os
import time
import threading
import pytest

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


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


def create_client() -> GqldbClient:
    """Create a new client instance."""
    config = (
        ConfigBuilder()
        .hosts(HOST)
        .username(USERNAME)
        .password(PASSWORD)
        .default_graph(GRAPH)
        .timeout(60)
        .build()
    )
    return GqldbClient(config)


def unique_graph_name(prefix: str) -> str:
    """Generate a unique graph name."""
    return f"{prefix}_{int(time.time() * 1000)}"


# ==================== Complete CRUD Lifecycle ====================


@pytest.mark.e2e
class TestCompleteCRUDLifecycle:
    """Tests for complete CRUD workflow."""

    def test_crud_lifecycle(self):
        """Test the full create-read-update-delete workflow."""
        client = create_client()

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

        graph_name = unique_graph_name("e2e_crud")

        try:
            # 1. Create graph
            client.gql(f"CREATE GRAPH {graph_name}")

            # 2. Create schema
            try:
                client.gql(f"USE GRAPH {graph_name} CREATE NODE TYPE Person (name STRING, age INT64)")
            except GqldbError:
                pass  # Schema may already exist

            # 3. Insert nodes
            session = client.start_bulk_import(graph_name)
            nodes = [
                NodeData(labels=["Person"], properties={"name": "Alice", "age": 30}),
                NodeData(labels=["Person"], properties={"name": "Bob", "age": 25}),
                NodeData(labels=["Person"], properties={"name": "Charlie", "age": 35}),
            ]
            try:
                client.insert_nodes(graph_name, nodes,
                                   bulk_import_session_id=session.session_id)
            finally:
                client.end_bulk_import(session.session_id)

            # 4. Query nodes
            response = client.gql(f"USE GRAPH {graph_name} MATCH (p:Person) RETURN p.name, p.age ORDER BY p.name")
            assert not response.is_empty()

            count = 0
            for row in response:
                count += 1
            assert count == 3

            # 5. Create edges
            try:
                client.gql(f"USE GRAPH {graph_name} CREATE EDGE TYPE KNOWS")
            except GqldbError:
                pass

            client.gql(f"""
                USE GRAPH {graph_name}
                INSERT (:Person {{name: 'Alice'}})-[:KNOWS]->(:Person {{name: 'Bob'}})
            """)

            # 6. Query paths
            path_response = client.gql(f"USE GRAPH {graph_name} MATCH p = (a:Person)-[e:KNOWS]->(b:Person) RETURN p LIMIT 10")
            paths = path_response.alias("p").as_paths()

            # 7. Delete node
            client.gql(f"USE GRAPH {graph_name} MATCH (p:Person WHERE p.name = 'Charlie') DELETE p")

            # 8. Verify
            verify_response = client.gql(f"USE GRAPH {graph_name} MATCH (p:Person) RETURN count(p) AS cnt")
            assert not verify_response.is_empty()

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


# ==================== Transaction Isolation ====================


@pytest.mark.e2e
class TestTransactionIsolation:
    """Tests for transaction isolation."""

    def test_transaction_isolation(self):
        """Test that transactions are properly isolated."""
        client1 = create_client()
        client2 = create_client()

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

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

        graph_name = unique_graph_name("e2e_txiso")

        try:
            # Create test graph
            client1.gql(f"CREATE GRAPH {graph_name}")

            # Begin transaction on client 1
            tx1 = client1.begin_transaction(graph_name, read_only=False)

            # Insert data in transaction
            try:
                from gqldb.client import QueryConfig
                client1.gql(f"USE GRAPH {graph_name} INSERT (:IsolationTest {{value: 'tx1'}})",
                           config=QueryConfig(transaction_id=tx1.id))
            except GqldbError:
                pass

            # Client 2 queries (may or may not see uncommitted data)
            try:
                response2 = client2.gql(f"USE GRAPH {graph_name} MATCH (n:IsolationTest) RETURN n")
            except GqldbError:
                pass

            # Commit
            client1.commit(tx1.id)

            # Client 2 should see data now
            try:
                response2_after = client2.gql(f"USE GRAPH {graph_name} MATCH (n:IsolationTest) RETURN n")
            except GqldbError:
                pass

        finally:
            try:
                client1.gql(f"DROP GRAPH {graph_name}")
            except GqldbError:
                pass
            client1.close()
            client2.close()


# ==================== Bulk Import Workflow ====================


@pytest.mark.e2e
class TestBulkImportWorkflow:
    """Tests for bulk import workflow."""

    def test_bulk_import_workflow(self):
        """Test the complete bulk import process."""
        client = create_client()

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

        graph_name = unique_graph_name("e2e_bulk")

        try:
            # Create graph
            client.gql(f"CREATE GRAPH {graph_name}")

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

            # Insert in batches
            batch_size = 100
            total_nodes = 500

            for i in range(0, total_nodes, batch_size):
                nodes = [
                    NodeData(labels=["BulkNode"], properties={"id": i + j, "batch": i // batch_size})
                    for j in range(batch_size)
                ]

                try:
                    client.insert_nodes(graph_name, nodes,
                                       bulk_import_session_id=session.session_id)
                except GqldbError:
                    pass

            # End session
            try:
                client.end_bulk_import(session.session_id)
            except GqldbError:
                pass

            # Verify
            try:
                response = client.gql(f"USE GRAPH {graph_name} MATCH (n:BulkNode) RETURN count(n) AS cnt")
            except GqldbError:
                pass

        finally:
            try:
                client.gql(f"DROP GRAPH {graph_name}")
            except GqldbError:
                pass
            client.close()


# ==================== Concurrent Operations ====================


@pytest.mark.e2e
class TestConcurrentOperations:
    """Tests for concurrent operations."""

    def test_concurrent_queries(self):
        """Test multiple clients performing parallel queries."""
        num_clients = 5
        queries_per_client = 10
        errors = []
        lock = threading.Lock()

        def client_worker(client_id: int):
            client = create_client()
            try:
                client.login(USERNAME, PASSWORD)
            except GqldbError as e:
                if "authentication is not enabled" not in str(e).lower():
                    with lock:
                        errors.append(f"Client {client_id} login: {e}")
                    return

            try:
                for j in range(queries_per_client):
                    try:
                        client.gql(f"RETURN {client_id} AS client, {j} AS query")
                    except GqldbError as e:
                        with lock:
                            errors.append(f"Client {client_id} query {j}: {e}")
            finally:
                client.close()

        threads = []
        for i in range(num_clients):
            t = threading.Thread(target=client_worker, args=(i,))
            threads.append(t)
            t.start()

        for t in threads:
            t.join(timeout=60)

        # Report errors but don't fail if some queries failed
        for error in errors:
            print(f"Concurrent error: {error}")


# ==================== Reconnection Scenario ====================


@pytest.mark.e2e
class TestReconnection:
    """Tests for reconnection scenarios."""

    def test_reconnection(self):
        """Test client reconnection after connection loss."""
        client = create_client()

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

        # Execute query
        client.gql("RETURN 1")

        # Close connection
        client.close()

        # Recreate and reconnect
        client = create_client()
        try:
            client.login(USERNAME, PASSWORD)
        except GqldbError as e:
            if "authentication is not enabled" not in str(e).lower():
                raise

        # Execute again
        response = client.gql("RETURN 2")
        assert not response.is_empty()

        client.close()
