use std::collections::HashMap;
use std::sync::Arc;

use tetrix_sdk::errors::ErrorCode;
use tetrix_sdk::types::*;
use tetrix_sdk::{Config, RetryConfig, TetrixClient};

fn get_env(key: &str) -> Option<String> {
    std::env::var(key).ok().filter(|v| !v.is_empty())
}

fn integration_client() -> Option<Arc<TetrixClient>> {
    let host = get_env("TETRIX_HOST")?;
    let port = get_env("TETRIX_PORT").unwrap_or_else(|| "7779".to_string());
    let password = get_env("TETRIX_SUPERADMIN_PASSWORD")?;

    let client = TetrixClient::new(Config {
        endpoint: format!("tcp://{host}:{port}"),
        username: "superadmin".to_string(),
        password,
        max_connections: Some(4),
        timeout_ms: Some(10_000),
        retry: Some(RetryConfig {
            max_retries: Some(3),
            base_delay_ms: Some(200),
            max_delay_ms: Some(5000),
        }),
        ..Default::default()
    })
    .expect("failed to create client");

    Some(client)
}

#[tokio::test]
async fn test_integration_health() {
    let Some(client) = integration_client() else { return };
    let health = client.health().await.unwrap();
    assert_eq!(health.status, "ok");
    assert!(health.uptime_seconds > 0.0);
}

#[tokio::test]
async fn test_integration_auth_failure() {
    let host = match get_env("TETRIX_HOST") {
        Some(h) => h,
        None => {
            eprintln!("SKIP: TETRIX_HOST not set");
            return;
        }
    };
    let port = get_env("TETRIX_PORT").unwrap_or_else(|| "7779".to_string());

    let client = TetrixClient::new(Config {
        endpoint: format!("tcp://{host}:{port}"),
        username: "superadmin".to_string(),
        password: "wrong-password".to_string(),
        timeout_ms: Some(5000),
        ..Default::default()
    })
    .unwrap();

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

#[tokio::test]
async fn test_integration_crud_lifecycle() {
    let Some(client) = integration_client() else { return };

    // 1. Create org
    let org = client
        .create_org(&CreateOrgRequest {
            name: format!("rust-sdk-test-{}", rand_suffix()),
        })
        .await
        .unwrap();

    // 2. Create user
    let user = client
        .create_user(&CreateUserRequest {
            username: format!("rustuser-{}", rand_suffix()),
            password: "testpass123".to_string(),
            org_id: org.id.clone(),
            role: Some("admin".to_string()),
        })
        .await
        .unwrap();

    // 3. Index entity
    let entity_id = format!("rust-test-{}", rand_suffix());
    let index_resp = client
        .index(&IndexRequest {
            entity: Entity {
                id: entity_id.clone(),
                entity_type: "test".to_string(),
                owner: user.id.clone(),
                name: "Rust SDK Test Entity".to_string(),
                metadata: Some(HashMap::from([
                    ("language".to_string(), serde_json::json!("rust")),
                    ("sdk".to_string(), serde_json::json!("v0.1")),
                ])),
                relations: None,
                version: None,
            },
            content: Some(EntityContent {
                text: "This is a test entity created by the Rust SDK integration tests."
                    .to_string(),
                raw: None,
                encoding: None,
            }),
            embedding: None,
            category_ids: None,
            write_consistency: Some(WriteConsistency::Strong),
        })
        .await
        .unwrap();

    assert_eq!(index_resp.id, entity_id);
    assert_eq!(index_resp.version, "1");

    // 4. Get entity
    let get_resp = client
        .get(&GetRequest {
            id: entity_id.clone(),
            include_content: Some(true),
            include_relations: Some(true),
        })
        .await
        .unwrap();

    assert_eq!(get_resp.entity.name, "Rust SDK Test Entity");
    assert!(get_resp.content.is_some());
    assert!(get_resp
        .content
        .as_ref()
        .unwrap()
        .text
        .contains("Rust SDK"));

    // 5. Update entity
    let update_resp = client
        .update(&UpdateRequest {
            id: entity_id.clone(),
            expected_version: Some("1".to_string()),
            patch: Some(UpdatePatch {
                name: Some("Rust SDK Updated Entity".to_string()),
                ..Default::default()
            }),
            content_patch: None,
            write_consistency: Some(WriteConsistency::Strong),
        })
        .await
        .unwrap();

    assert_eq!(update_resp.version, "2");

    // 6. Verify update
    let get_resp2 = client
        .get(&GetRequest {
            id: entity_id.clone(),
            include_content: None,
            include_relations: None,
        })
        .await
        .unwrap();

    assert_eq!(get_resp2.entity.name, "Rust SDK Updated Entity");

    // 7. List entity events
    let events = client
        .list_entity_events(&ListEntityEventsRequest {
            entity_id: entity_id.clone(),
            event_types: None,
            limit: None,
            offset: None,
        })
        .await
        .unwrap();

    assert!(events.total >= 2); // created + updated

    // 8. Delete entity
    let del_resp = client
        .delete(&DeleteRequest {
            id: entity_id.clone(),
            expected_version: None,
        })
        .await
        .unwrap();

    assert_eq!(del_resp.id, entity_id);

    // 9. Verify deleted
    let get_result = client
        .get(&GetRequest {
            id: entity_id,
            include_content: None,
            include_relations: None,
        })
        .await;

    assert!(get_result.is_err());
    assert_eq!(get_result.unwrap_err().code, ErrorCode::NotFound);

    // Cleanup IAM
    client.delete_user(&user.id).await.unwrap();
    client.delete_org(&org.id).await.unwrap();
}

#[tokio::test]
async fn test_integration_batch() {
    let Some(client) = integration_client() else { return };

    let org = client
        .create_org(&CreateOrgRequest {
            name: format!("rust-batch-{}", rand_suffix()),
        })
        .await
        .unwrap();

    let user = client
        .create_user(&CreateUserRequest {
            username: format!("rustbatch-{}", rand_suffix()),
            password: "testpass123".to_string(),
            org_id: org.id.clone(),
            role: Some("admin".to_string()),
        })
        .await
        .unwrap();

    let ids: Vec<String> = (0..5).map(|i| format!("rust-batch-{}-{}", i, rand_suffix())).collect();
    let items: Vec<IndexBatchItem> = ids
        .iter()
        .enumerate()
        .map(|(i, id)| IndexBatchItem {
            entity: Entity {
                id: id.clone(),
                entity_type: "test".to_string(),
                owner: user.id.clone(),
                name: format!("Batch Entity {i}"),
                metadata: None,
                relations: None,
                version: None,
            },
            content: Some(EntityContent {
                text: format!("Batch content {i}"),
                raw: None,
                encoding: None,
            }),
            embedding: None,
            category_ids: None,
        })
        .collect();

    let batch_resp = client
        .index_batch(&IndexBatchRequest {
            items,
            options: Some(IndexBatchOptions {
                write_consistency: Some(WriteConsistency::Strong),
                ..Default::default()
            }),
        })
        .await
        .unwrap();

    assert_eq!(batch_resp.total_count, 5);
    assert_eq!(batch_resp.failed_count, 0);

    // Cleanup
    let del_resp = client
        .delete_batch(&DeleteBatchRequest {
            ids,
            continue_on_error: Some(true),
        })
        .await
        .unwrap();
    assert_eq!(del_resp.total_count, 5);

    client.delete_user(&user.id).await.unwrap();
    client.delete_org(&org.id).await.unwrap();
}

#[tokio::test]
async fn test_integration_iam() {
    let Some(client) = integration_client() else { return };

    // Create org
    let org = client
        .create_org(&CreateOrgRequest {
            name: format!("rust-iam-{}", rand_suffix()),
        })
        .await
        .unwrap();

    // Create user
    let user = client
        .create_user(&CreateUserRequest {
            username: format!("rustiam-{}", rand_suffix()),
            password: "testpass123".to_string(),
            org_id: org.id.clone(),
            role: Some("member".to_string()),
        })
        .await
        .unwrap();

    assert_eq!(user.role, "member");

    // Get user
    let got = client.get_user(&user.id).await.unwrap();
    assert_eq!(got.username, user.username);

    // Create team
    let team = client
        .create_team(&CreateTeamRequest {
            name: format!("rust-team-{}", rand_suffix()),
            org_id: Some(org.id.clone()),
        })
        .await
        .unwrap();

    // Add member
    let member = client
        .add_team_member(&AddTeamMemberRequest {
            team_id: team.id.clone(),
            user_id: user.id.clone(),
            role: Some("member".to_string()),
        })
        .await
        .unwrap();
    assert_eq!(member.role, "member");

    // List team members
    let members = client.list_team_members(&team.id).await.unwrap();
    assert_eq!(members.total, 1);

    // Remove member
    client
        .remove_team_member(&team.id, &user.id)
        .await
        .unwrap();

    // Cleanup
    client.delete_team(&team.id).await.unwrap();
    client.delete_user(&user.id).await.unwrap();
    client.delete_org(&org.id).await.unwrap();
}

#[tokio::test]
async fn test_integration_categories() {
    let Some(client) = integration_client() else { return };

    let org = client
        .create_org(&CreateOrgRequest {
            name: format!("rust-cat-{}", rand_suffix()),
        })
        .await
        .unwrap();

    let user = client
        .create_user(&CreateUserRequest {
            username: format!("rustcat-{}", rand_suffix()),
            password: "testpass123".to_string(),
            org_id: org.id.clone(),
            role: Some("admin".to_string()),
        })
        .await
        .unwrap();

    // Create category
    let cat = client
        .create_category(&CreateCategoryRequest {
            name: format!("rust-category-{}", rand_suffix()),
            description: Some("Test category from Rust SDK".to_string()),
            parent_id: None,
        })
        .await
        .unwrap();

    // Get category
    let got = client.get_category(&cat.id).await.unwrap();
    assert_eq!(got.category.name, cat.name);

    // Index entity and assign to category
    let entity_id = format!("rust-cat-entity-{}", rand_suffix());
    client
        .index(&IndexRequest {
            entity: Entity {
                id: entity_id.clone(),
                entity_type: "test".to_string(),
                owner: user.id.clone(),
                name: "Category Test Entity".to_string(),
                metadata: None,
                relations: None,
                version: None,
            },
            content: None,
            embedding: None,
            category_ids: Some(vec![cat.id.clone()]),
            write_consistency: Some(WriteConsistency::Strong),
        })
        .await
        .unwrap();

    // List entity categories
    let entity_cats = client.list_entity_categories(&entity_id).await.unwrap();
    assert!(entity_cats.categories.iter().any(|c| c.id == cat.id));

    // Cleanup
    client
        .delete(&DeleteRequest {
            id: entity_id,
            expected_version: None,
        })
        .await
        .unwrap();
    client.delete_category(&cat.id).await.unwrap();
    client.delete_user(&user.id).await.unwrap();
    client.delete_org(&org.id).await.unwrap();
}

#[tokio::test]
async fn test_integration_permissions() {
    let Some(client) = integration_client() else { return };

    let org = client
        .create_org(&CreateOrgRequest {
            name: format!("rust-perm-{}", rand_suffix()),
        })
        .await
        .unwrap();

    let user_a = client
        .create_user(&CreateUserRequest {
            username: format!("rustperm-a-{}", rand_suffix()),
            password: "testpass123".to_string(),
            org_id: org.id.clone(),
            role: Some("admin".to_string()),
        })
        .await
        .unwrap();

    let user_b = client
        .create_user(&CreateUserRequest {
            username: format!("rustperm-b-{}", rand_suffix()),
            password: "testpass123".to_string(),
            org_id: org.id.clone(),
            role: Some("member".to_string()),
        })
        .await
        .unwrap();

    // Index entity as user A
    let entity_id = format!("rust-perm-{}", rand_suffix());
    client
        .index(&IndexRequest {
            entity: Entity {
                id: entity_id.clone(),
                entity_type: "test".to_string(),
                owner: user_a.id.clone(),
                name: "Permission Test".to_string(),
                metadata: None,
                relations: None,
                version: None,
            },
            content: None,
            embedding: None,
            category_ids: None,
            write_consistency: Some(WriteConsistency::Strong),
        })
        .await
        .unwrap();

    // Grant read to user B
    let perm = client
        .grant_permission(&GrantPermissionRequest {
            entity_id: entity_id.clone(),
            grantee_type: "user".to_string(),
            grantee_id: user_b.id.clone(),
            permission: Some("read".to_string()),
        })
        .await
        .unwrap();
    assert_eq!(perm.permission, "read");

    // List permissions
    let perms = client.list_permissions(&entity_id).await.unwrap();
    assert!(perms.permissions.len() >= 1);

    // Revoke
    client
        .revoke_permission(&RevokePermissionRequest {
            entity_id: entity_id.clone(),
            grantee_type: "user".to_string(),
            grantee_id: user_b.id.clone(),
        })
        .await
        .unwrap();

    // Cleanup
    client
        .delete(&DeleteRequest {
            id: entity_id,
            expected_version: None,
        })
        .await
        .unwrap();
    client.delete_user(&user_b.id).await.unwrap();
    client.delete_user(&user_a.id).await.unwrap();
    client.delete_org(&org.id).await.unwrap();
}

fn rand_suffix() -> String {
    use rand::RngExt;
    let mut rng = rand::rng();
    let bytes: [u8; 4] = rng.random();
    bytes.iter().map(|b| format!("{b:02x}")).collect()
}
