use std::sync::Arc;

use serde_json::json;
use tetrix_sdk::errors::ErrorCode;
use tetrix_sdk::types::*;
use tetrix_sdk::{Config, RetryConfig, TetrixClient};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpListener;

async fn write_frame(writer: &mut (impl AsyncWriteExt + Unpin), payload: &serde_json::Value) {
    let body = serde_json::to_vec(payload).unwrap();
    let len = body.len() as u32;
    writer.write_all(&len.to_be_bytes()).await.unwrap();
    writer.write_all(&body).await.unwrap();
    writer.flush().await.unwrap();
}

async fn read_frame(reader: &mut (impl AsyncReadExt + Unpin)) -> serde_json::Value {
    let mut header = [0u8; 4];
    reader.read_exact(&mut header).await.unwrap();
    let len = u32::from_be_bytes(header);
    let mut body = vec![0u8; len as usize];
    reader.read_exact(&mut body).await.unwrap();
    serde_json::from_slice(&body).unwrap()
}

/// Start a mock server that handles AUTH + one request/response cycle.
/// The `handler` receives the request envelope and returns the response payload.
async fn mock_server<F>(handler: F) -> (String, tokio::task::JoinHandle<()>)
where
    F: Fn(serde_json::Value) -> serde_json::Value + Send + Sync + 'static,
{
    let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = listener.local_addr().unwrap();
    let endpoint = format!("tcp://127.0.0.1:{}", addr.port());

    let handle = tokio::spawn(async move {
        loop {
            let Ok((mut stream, _)) = listener.accept().await else {
                return;
            };
            let (mut reader, mut writer) = stream.split();

            // AUTH handshake
            let auth_req = read_frame(&mut reader).await;
            assert_eq!(auth_req["op"], "AUTH");
            write_frame(
                &mut writer,
                &json!({"protocolVersion": "1.0", "op": "AUTH", "status": "ok"}),
            )
            .await;

            // Handle requests
            loop {
                let mut header = [0u8; 4];
                if reader.read_exact(&mut header).await.is_err() {
                    break;
                }
                let len = u32::from_be_bytes(header);
                let mut body = vec![0u8; len as usize];
                if reader.read_exact(&mut body).await.is_err() {
                    break;
                }
                let request: serde_json::Value = serde_json::from_slice(&body).unwrap();

                let response_payload = handler(request.clone());
                let response = json!({
                    "protocolVersion": "1.0",
                    "requestId": request["requestId"],
                    "op": request["op"],
                    "status": "ok",
                    "payload": response_payload,
                });

                write_frame(&mut writer, &response).await;
            }
        }
    });

    (endpoint, handle)
}

/// Mock server that returns error responses.
async fn mock_error_server(
    error_code: &str,
    message: &str,
    retryable: bool,
) -> (String, tokio::task::JoinHandle<()>) {
    let error_code = error_code.to_string();
    let message = message.to_string();

    let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = listener.local_addr().unwrap();
    let endpoint = format!("tcp://127.0.0.1:{}", addr.port());

    let handle = tokio::spawn(async move {
        loop {
            let Ok((mut stream, _)) = listener.accept().await else {
                return;
            };
            let (mut reader, mut writer) = stream.split();

            let auth_req = read_frame(&mut reader).await;
            assert_eq!(auth_req["op"], "AUTH");
            write_frame(
                &mut writer,
                &json!({"protocolVersion": "1.0", "op": "AUTH", "status": "ok"}),
            )
            .await;

            loop {
                let mut h = [0u8; 4];
                if reader.read_exact(&mut h).await.is_err() {
                    break;
                }
                let len = u32::from_be_bytes(h);
                let mut body = vec![0u8; len as usize];
                if reader.read_exact(&mut body).await.is_err() {
                    break;
                }
                let request: serde_json::Value = serde_json::from_slice(&body).unwrap();

                let response = json!({
                    "protocolVersion": "1.0",
                    "requestId": request["requestId"],
                    "op": request["op"],
                    "status": "error",
                    "error": {
                        "code": error_code,
                        "message": message,
                        "retryable": retryable,
                    }
                });
                write_frame(&mut writer, &response).await;
            }
        }
    });

    (endpoint, handle)
}

fn test_config(endpoint: &str) -> Config {
    Config {
        endpoint: endpoint.to_string(),
        username: "admin".to_string(),
        password: "test-password".to_string(),
        max_connections: Some(2),
        timeout_ms: Some(5000),
        ..Default::default()
    }
}

// --- Auth Tests ---

#[tokio::test]
async fn test_auth_success() {
    let (endpoint, _handle) = mock_server(|_| json!({"status": "ok", "uptimeSeconds": 1.0})).await;
    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let health = client.health().await.unwrap();
    assert_eq!(health.status, "ok");
    client.close().await;
}

#[tokio::test]
async fn test_auth_failure() {
    let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = listener.local_addr().unwrap();
    let endpoint = format!("tcp://127.0.0.1:{}", addr.port());

    tokio::spawn(async move {
        let (mut stream, _) = listener.accept().await.unwrap();
        let (mut reader, mut writer) = stream.split();
        let _auth = read_frame(&mut reader).await;
        write_frame(
            &mut writer,
            &json!({
                "protocolVersion": "1.0",
                "op": "AUTH",
                "status": "error",
                "error": {"code": "UNAUTHENTICATED", "message": "bad credentials", "retryable": false}
            }),
        )
        .await;
    });

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let result = client.health().await;
    assert!(result.is_err());
    let err = result.unwrap_err();
    assert_eq!(err.code, ErrorCode::Unauthenticated);
}

// --- Data Operation Tests ---

#[tokio::test]
async fn test_health() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "HEALTH");
        json!({"status": "ok", "uptimeSeconds": 42.5, "backendStatus": {"pg": "up", "neo4j": "up"}})
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let health = client.health().await.unwrap();
    assert_eq!(health.status, "ok");
    assert_eq!(health.uptime_seconds, 42.5);
}

#[tokio::test]
async fn test_index() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "INDEX");
        let payload = &req["payload"];
        assert_eq!(payload["entity"]["name"], "test-entity");
        json!({"id": "e123", "version": "1", "createdAt": "2026-01-01T00:00:00Z"})
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .index(&IndexRequest {
            entity: Entity {
                id: "e123".to_string(),
                entity_type: "test".to_string(),
                owner: "user1".to_string(),
                name: "test-entity".to_string(),
                metadata: None,
                relations: None,
                version: None,
            },
            content: Some(EntityContent {
                text: "hello world".to_string(),
                raw: None,
                encoding: None,
            }),
            embedding: None,
            category_ids: None,
            write_consistency: None,
        })
        .await
        .unwrap();

    assert_eq!(resp.id, "e123");
    assert_eq!(resp.version, "1");
}

#[tokio::test]
async fn test_index_batch() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "INDEX_BATCH");
        json!({
            "results": [{"id": "e1", "version": "1", "status": "ok"}, {"id": "e2", "version": "1", "status": "ok"}],
            "failedCount": 0,
            "totalCount": 2
        })
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .index_batch(&IndexBatchRequest {
            items: vec![
                IndexBatchItem {
                    entity: Entity {
                        id: "e1".to_string(),
                        entity_type: "test".to_string(),
                        owner: "user1".to_string(),
                        name: "entity-1".to_string(),
                        metadata: None,
                        relations: None,
                        version: None,
                    },
                    content: None,
                    embedding: None,
                    category_ids: None,
                },
                IndexBatchItem {
                    entity: Entity {
                        id: "e2".to_string(),
                        entity_type: "test".to_string(),
                        owner: "user1".to_string(),
                        name: "entity-2".to_string(),
                        metadata: None,
                        relations: None,
                        version: None,
                    },
                    content: None,
                    embedding: None,
                    category_ids: None,
                },
            ],
            options: None,
        })
        .await
        .unwrap();

    assert_eq!(resp.total_count, 2);
    assert_eq!(resp.failed_count, 0);
}

#[tokio::test]
async fn test_get() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "GET");
        json!({
            "entity": {
                "id": "e123",
                "type": "test",
                "owner": "user1",
                "name": "test-entity",
                "version": "1"
            },
            "content": {
                "text": "hello world"
            }
        })
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .get(&GetRequest {
            id: "e123".to_string(),
            include_content: Some(true),
            include_relations: None,
        })
        .await
        .unwrap();

    assert_eq!(resp.entity.id, "e123");
    assert_eq!(resp.entity.name, "test-entity");
    assert!(resp.content.is_some());
    assert_eq!(resp.content.unwrap().text, "hello world");
}

#[tokio::test]
async fn test_update() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "UPDATE");
        json!({"id": "e123", "version": "2", "updatedAt": "2026-01-01T00:00:01Z"})
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .update(&UpdateRequest {
            id: "e123".to_string(),
            expected_version: Some("1".to_string()),
            patch: Some(UpdatePatch {
                name: Some("renamed".to_string()),
                ..Default::default()
            }),
            content_patch: None,
            write_consistency: None,
        })
        .await
        .unwrap();

    assert_eq!(resp.version, "2");
}

#[tokio::test]
async fn test_delete() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "DELETE");
        json!({"id": "e123", "status": "deleted", "deletedAt": "2026-01-01T00:00:02Z"})
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .delete(&DeleteRequest {
            id: "e123".to_string(),
            expected_version: None,
        })
        .await
        .unwrap();

    assert_eq!(resp.status, "deleted");
}

#[tokio::test]
async fn test_delete_batch() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "DELETE_BATCH");
        json!({
            "results": [{"id": "e1", "status": "deleted"}, {"id": "e2", "status": "deleted"}],
            "failedCount": 0,
            "totalCount": 2
        })
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .delete_batch(&DeleteBatchRequest {
            ids: vec!["e1".to_string(), "e2".to_string()],
            continue_on_error: None,
        })
        .await
        .unwrap();

    assert_eq!(resp.total_count, 2);
}

#[tokio::test]
async fn test_list_entities() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "LIST_ENTITIES");
        json!({
            "items": [
                {"id": "e1", "type": "test", "name": "entity-1"},
                {"id": "e2", "type": "test", "name": "entity-2"}
            ],
            "total": 2,
            "offset": 0
        })
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .list_entities(Some(&ListEntitiesRequest {
            filters: Some(SearchFilters {
                entity_type: Some("test".to_string()),
                ..Default::default()
            }),
            limit: Some(10),
            offset: None,
        }))
        .await
        .unwrap();

    assert_eq!(resp.items.len(), 2);
    assert_eq!(resp.total, 2);
}

#[tokio::test]
async fn test_list_entities_nil() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "LIST_ENTITIES");
        json!({"items": [], "total": 0, "offset": 0})
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client.list_entities(None).await.unwrap();
    assert_eq!(resp.total, 0);
}

#[tokio::test]
async fn test_semantic_search() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "SEMANTIC_SEARCH");
        json!({
            "items": [{
                "entity": {"id": "e1", "type": "test", "owner": "u1", "name": "result"},
                "score": 0.95,
                "source": "vector"
            }],
            "total": 1
        })
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .semantic_search(&SemanticSearchRequest {
            text: "hello world".to_string(),
            filters: None,
            options: None,
        })
        .await
        .unwrap();

    assert_eq!(resp.items.len(), 1);
    assert!(resp.items[0].score > 0.9);
}

#[tokio::test]
async fn test_list_entity_events() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "LIST_ENTITY_EVENTS");
        json!({
            "events": [{
                "id": "ev1",
                "entityId": "e1",
                "eventType": "created",
                "actorId": "u1",
                "operation": "INDEX",
                "createdAt": "2026-01-01T00:00:00Z"
            }],
            "total": 1
        })
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .list_entity_events(&ListEntityEventsRequest {
            entity_id: "e1".to_string(),
            event_types: None,
            limit: None,
            offset: None,
        })
        .await
        .unwrap();

    assert_eq!(resp.events.len(), 1);
    assert_eq!(resp.events[0].event_type, "created");
}

// --- IAM Tests ---

#[tokio::test]
async fn test_create_org() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "CREATE_ORG");
        json!({"id": "org1", "name": "Test Org"})
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .create_org(&CreateOrgRequest {
            name: "Test Org".to_string(),
        })
        .await
        .unwrap();

    assert_eq!(resp.id, "org1");
    assert_eq!(resp.name, "Test Org");
}

#[tokio::test]
async fn test_get_org() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "GET_ORG");
        json!({"id": "org1", "name": "Test Org", "createdAt": "2026-01-01T00:00:00Z"})
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client.get_org("org1").await.unwrap();
    assert_eq!(resp.name, "Test Org");
}

#[tokio::test]
async fn test_create_user() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "CREATE_USER");
        json!({"id": "u1", "username": "alice", "orgId": "org1", "role": "admin"})
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .create_user(&CreateUserRequest {
            username: "alice".to_string(),
            password: "pass123".to_string(),
            org_id: "org1".to_string(),
            role: Some("admin".to_string()),
        })
        .await
        .unwrap();

    assert_eq!(resp.username, "alice");
    assert_eq!(resp.role, "admin");
}

#[tokio::test]
async fn test_list_users() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "LIST_USERS");
        json!({
            "users": [{"id": "u1", "username": "alice", "orgId": "org1", "role": "admin"}],
            "total": 1
        })
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client.list_users(Some("org1")).await.unwrap();
    assert_eq!(resp.users.len(), 1);
}

#[tokio::test]
async fn test_create_team() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "CREATE_TEAM");
        json!({"id": "t1", "name": "Engineering", "orgId": "org1"})
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .create_team(&CreateTeamRequest {
            name: "Engineering".to_string(),
            org_id: None,
        })
        .await
        .unwrap();

    assert_eq!(resp.name, "Engineering");
}

#[tokio::test]
async fn test_grant_permission() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "GRANT_PERMISSION");
        json!({
            "entityId": "e1",
            "granteeType": "user",
            "granteeId": "u1",
            "permission": "read"
        })
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .grant_permission(&GrantPermissionRequest {
            entity_id: "e1".to_string(),
            grantee_type: "user".to_string(),
            grantee_id: "u1".to_string(),
            permission: Some("read".to_string()),
        })
        .await
        .unwrap();

    assert_eq!(resp.permission, "read");
}

// --- Category Tests ---

#[tokio::test]
async fn test_create_category() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "CREATE_CATEGORY");
        json!({"id": "cat1", "name": "ML Models", "orgId": "org1"})
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client
        .create_category(&CreateCategoryRequest {
            name: "ML Models".to_string(),
            description: None,
            parent_id: None,
        })
        .await
        .unwrap();

    assert_eq!(resp.name, "ML Models");
}

#[tokio::test]
async fn test_list_categories() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "LIST_CATEGORIES");
        json!({
            "categories": [{"id": "cat1", "name": "ML", "orgId": "org1"}],
            "total": 1
        })
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client.list_categories(None).await.unwrap();
    assert_eq!(resp.categories.len(), 1);
}

#[tokio::test]
async fn test_assign_entity_category() {
    let (endpoint, _handle) = mock_server(|req| {
        assert_eq!(req["op"], "ASSIGN_ENTITY_CATEGORY");
        json!({"entityId": "e1", "categoryId": "cat1"})
    })
    .await;

    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let resp = client.assign_entity_category("e1", "cat1").await.unwrap();
    assert_eq!(resp.entity_id, "e1");
}

// --- Error Handling Tests ---

#[tokio::test]
async fn test_not_found_error() {
    let (endpoint, _handle) = mock_error_server("NOT_FOUND", "entity not found", false).await;
    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let result = client
        .get(&GetRequest {
            id: "nonexistent".to_string(),
            include_content: None,
            include_relations: None,
        })
        .await;

    assert!(result.is_err());
    let err = result.unwrap_err();
    assert_eq!(err.code, ErrorCode::NotFound);
    assert!(!err.is_retryable());
}

#[tokio::test]
async fn test_permission_denied_error() {
    let (endpoint, _handle) =
        mock_error_server("PERMISSION_DENIED", "access denied", false).await;
    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    let result = client.get_org("org1").await;
    assert!(result.is_err());
    assert_eq!(result.unwrap_err().code, ErrorCode::PermissionDenied);
}

// --- Connection Pool Tests ---

#[tokio::test]
async fn test_connection_reuse() {
    let (endpoint, _handle) = mock_server(|_| json!({"status": "ok", "uptimeSeconds": 1.0})).await;
    let client = TetrixClient::new(test_config(&endpoint)).unwrap();

    // Multiple sequential requests should reuse the same connection
    for _ in 0..5 {
        let health = client.health().await.unwrap();
        assert_eq!(health.status, "ok");
    }
    client.close().await;
}

#[tokio::test]
async fn test_connection_refused() {
    let client = TetrixClient::new(Config {
        endpoint: "tcp://127.0.0.1:1".to_string(), // nothing listening here
        username: "admin".to_string(),
        password: "pass".to_string(),
        timeout_ms: Some(1000),
        ..Default::default()
    })
    .unwrap();

    let result = client.health().await;
    assert!(result.is_err());
    assert_eq!(result.unwrap_err().code, ErrorCode::ConnectionError);
}

#[tokio::test]
async fn test_closed_client() {
    let (endpoint, _handle) = mock_server(|_| json!({"status": "ok", "uptimeSeconds": 1.0})).await;
    let client = TetrixClient::new(test_config(&endpoint)).unwrap();
    client.close().await;

    let result = client.health().await;
    assert!(result.is_err());
    assert_eq!(result.unwrap_err().code, ErrorCode::ConnectionError);
}

// --- Retry Tests ---

#[tokio::test]
async fn test_retry_on_rate_limited() {
    use std::sync::atomic::{AtomicU32, Ordering};

    let attempt_count = Arc::new(AtomicU32::new(0));
    let counter = attempt_count.clone();

    let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = listener.local_addr().unwrap();
    let endpoint = format!("tcp://127.0.0.1:{}", addr.port());

    tokio::spawn(async move {
        loop {
            let Ok((mut stream, _)) = listener.accept().await else { return };
            let counter = counter.clone();
            tokio::spawn(async move {
                let (mut reader, mut writer) = stream.split();

                let auth = read_frame(&mut reader).await;
                assert_eq!(auth["op"], "AUTH");
                write_frame(
                    &mut writer,
                    &json!({"protocolVersion": "1.0", "op": "AUTH", "status": "ok"}),
                ).await;

                loop {
                    let mut h = [0u8; 4];
                    if reader.read_exact(&mut h).await.is_err() { break }
                    let len = u32::from_be_bytes(h);
                    let mut body = vec![0u8; len as usize];
                    if reader.read_exact(&mut body).await.is_err() { break }
                    let request: serde_json::Value = serde_json::from_slice(&body).unwrap();

                    let n = counter.fetch_add(1, Ordering::SeqCst);
                    let response = if n < 2 {
                        json!({
                            "protocolVersion": "1.0",
                            "requestId": request["requestId"],
                            "op": request["op"],
                            "status": "error",
                            "error": {"code": "RATE_LIMITED", "message": "slow down", "retryable": true}
                        })
                    } else {
                        json!({
                            "protocolVersion": "1.0",
                            "requestId": request["requestId"],
                            "op": request["op"],
                            "status": "ok",
                            "payload": {"status": "ok", "uptimeSeconds": 1.0}
                        })
                    };
                    write_frame(&mut writer, &response).await;
                }
            });
        }
    });

    let client = TetrixClient::new(Config {
        endpoint,
        username: "admin".to_string(),
        password: "test".to_string(),
        retry: Some(RetryConfig {
            max_retries: Some(5),
            base_delay_ms: Some(10),
            max_delay_ms: Some(50),
        }),
        timeout_ms: Some(5000),
        ..Default::default()
    })
    .unwrap();

    let resp = client.health().await.unwrap();
    assert_eq!(resp.status, "ok");
    assert!(attempt_count.load(Ordering::SeqCst) >= 3);
}

// --- Error Display ---

#[tokio::test]
async fn test_error_display() {
    let err = tetrix_sdk::TetrixError::new(ErrorCode::NotFound, "entity not found");
    assert_eq!(err.to_string(), "tetrixaidb: NOT_FOUND: entity not found");
    assert!(!err.is_retryable());
}

#[tokio::test]
async fn test_retryable_error_codes() {
    let rate_limited =
        tetrix_sdk::TetrixError::new(ErrorCode::RateLimited, "too many requests");
    assert!(rate_limited.is_retryable());

    let backend =
        tetrix_sdk::TetrixError::new(ErrorCode::BackendUnavailable, "pg down");
    assert!(backend.is_retryable());

    let conn = tetrix_sdk::TetrixError::new(ErrorCode::ConnectionError, "reset");
    assert!(conn.is_retryable());

    let not_found = tetrix_sdk::TetrixError::new(ErrorCode::NotFound, "gone");
    assert!(!not_found.is_retryable());
}
