//! Integration tests for the SentryAgent.ai AgentIdP Rust SDK.
//!
//! These tests run against a real API instance. They are marked `#[ignore]`
//! and will not execute in CI unless explicitly opted in with:
//!
//! ```bash
//! AGENTIDP_API_URL=https://api.sentryagent.ai \
//! AGENTIDP_CLIENT_ID=... \
//! AGENTIDP_CLIENT_SECRET=... \
//! cargo test -- --ignored
//! ```

use sentryagent_idp::{
    AgentIdPClient, AuditLogFilters, DelegateRequest, MarketplaceFilters, RegisterAgentRequest,
    UpdateAgentRequest,
};

/// Helper — build a client from environment variables, skipping the test when
/// any required variable is unset (rather than panicking).
fn client_from_env() -> Option<AgentIdPClient> {
    AgentIdPClient::from_env().ok()
}

// ─── Agent CRUD ───────────────────────────────────────────────────────────────

#[tokio::test]
#[ignore]
async fn test_register_and_delete_agent() {
    let client = client_from_env().expect("AGENTIDP_* env vars must be set");

    let agent = client
        .register_agent(RegisterAgentRequest {
            name: "integration-test-agent".to_owned(),
            description: Some("Created by integration test".to_owned()),
            agent_type: "worker".to_owned(),
            capabilities: vec!["read:data".to_owned()],
            metadata: None,
        })
        .await
        .expect("register_agent should succeed");

    assert!(!agent.id.is_empty());
    assert_eq!(agent.name, "integration-test-agent");

    client
        .delete_agent(&agent.id)
        .await
        .expect("delete_agent should succeed");
}

#[tokio::test]
#[ignore]
async fn test_get_agent() {
    let client = client_from_env().expect("AGENTIDP_* env vars must be set");

    let created = client
        .register_agent(RegisterAgentRequest {
            name: "get-test-agent".to_owned(),
            description: None,
            agent_type: "worker".to_owned(),
            capabilities: vec![],
            metadata: None,
        })
        .await
        .expect("register_agent should succeed");

    let fetched = client
        .get_agent(&created.id)
        .await
        .expect("get_agent should succeed");

    assert_eq!(fetched.id, created.id);
    assert_eq!(fetched.name, "get-test-agent");

    client.delete_agent(&created.id).await.ok();
}

#[tokio::test]
#[ignore]
async fn test_list_agents() {
    let client = client_from_env().expect("AGENTIDP_* env vars must be set");

    let list = client
        .list_agents(Some(1), Some(10))
        .await
        .expect("list_agents should succeed");

    // Must return a valid pagination envelope.
    assert!(list.page >= 1);
    assert!(list.per_page > 0);
}

#[tokio::test]
#[ignore]
async fn test_update_agent() {
    let client = client_from_env().expect("AGENTIDP_* env vars must be set");

    let agent = client
        .register_agent(RegisterAgentRequest {
            name: "update-test-agent".to_owned(),
            description: None,
            agent_type: "worker".to_owned(),
            capabilities: vec![],
            metadata: None,
        })
        .await
        .expect("register_agent should succeed");

    let updated = client
        .update_agent(
            &agent.id,
            UpdateAgentRequest {
                name: Some("updated-name".to_owned()),
                description: Some("Updated description".to_owned()),
                capabilities: None,
                is_public: None,
                metadata: None,
            },
        )
        .await
        .expect("update_agent should succeed");

    assert_eq!(updated.name, "updated-name");

    client.delete_agent(&agent.id).await.ok();
}

#[tokio::test]
#[ignore]
async fn test_get_agent_not_found() {
    use sentryagent_idp::AgentIdPError;

    let client = client_from_env().expect("AGENTIDP_* env vars must be set");

    let result = client
        .get_agent("00000000-0000-0000-0000-000000000000")
        .await;

    assert!(
        matches!(result, Err(AgentIdPError::NotFound(_))),
        "Expected NotFound error, got: {:?}",
        result
    );
}

// ─── Credentials ─────────────────────────────────────────────────────────────

#[tokio::test]
#[ignore]
async fn test_generate_and_rotate_credentials() {
    let client = client_from_env().expect("AGENTIDP_* env vars must be set");

    let agent = client
        .register_agent(RegisterAgentRequest {
            name: "creds-test-agent".to_owned(),
            description: None,
            agent_type: "worker".to_owned(),
            capabilities: vec![],
            metadata: None,
        })
        .await
        .expect("register_agent should succeed");

    let creds = client
        .generate_credentials(&agent.id)
        .await
        .expect("generate_credentials should succeed");

    assert!(!creds.client_id.is_empty());
    assert!(!creds.client_secret.is_empty());

    let rotated = client
        .rotate_credentials(&agent.id)
        .await
        .expect("rotate_credentials should succeed");

    // Rotated secret must differ from the original.
    assert_ne!(rotated.client_secret, creds.client_secret);

    client.delete_agent(&agent.id).await.ok();
}

// ─── OAuth2 ──────────────────────────────────────────────────────────────────

#[tokio::test]
#[ignore]
async fn test_issue_token() {
    let client = client_from_env().expect("AGENTIDP_* env vars must be set");

    let agent = client
        .register_agent(RegisterAgentRequest {
            name: "token-test-agent".to_owned(),
            description: None,
            agent_type: "worker".to_owned(),
            capabilities: vec![],
            metadata: None,
        })
        .await
        .expect("register_agent should succeed");

    let token = client
        .issue_token(&agent.id, &["agents:read"])
        .await
        .expect("issue_token should succeed");

    assert!(!token.access_token.is_empty());
    assert_eq!(token.token_type.to_lowercase(), "bearer");

    client.delete_agent(&agent.id).await.ok();
}

// ─── Audit logs ──────────────────────────────────────────────────────────────

#[tokio::test]
#[ignore]
async fn test_list_audit_logs() {
    let client = client_from_env().expect("AGENTIDP_* env vars must be set");

    let logs = client
        .list_audit_logs(AuditLogFilters {
            agent_id: None,
            event_type: None,
            from: None,
            to: None,
            page: 1,
            per_page: 20,
        })
        .await
        .expect("list_audit_logs should succeed");

    assert!(logs.page >= 1);
    assert!(logs.per_page > 0);
}

// ─── Marketplace ─────────────────────────────────────────────────────────────

#[tokio::test]
#[ignore]
async fn test_list_public_agents() {
    let client = client_from_env().expect("AGENTIDP_* env vars must be set");

    let results = client
        .list_public_agents(MarketplaceFilters {
            q: None,
            capability: None,
            publisher: None,
            page: 1,
            per_page: 10,
        })
        .await
        .expect("list_public_agents should succeed");

    assert!(results.page >= 1);
}

#[tokio::test]
#[ignore]
async fn test_marketplace_search() {
    let client = client_from_env().expect("AGENTIDP_* env vars must be set");

    let results = client
        .list_public_agents(MarketplaceFilters {
            q: Some("agent".to_owned()),
            capability: None,
            publisher: None,
            page: 1,
            per_page: 5,
        })
        .await
        .expect("list_public_agents with query should succeed");

    // Result may be empty but must be a valid envelope.
    assert!(results.per_page > 0);
}

// ─── Delegation ──────────────────────────────────────────────────────────────

#[tokio::test]
#[ignore]
async fn test_delegate_and_verify() {
    let client = client_from_env().expect("AGENTIDP_* env vars must be set");

    let delegator = client
        .register_agent(RegisterAgentRequest {
            name: "delegator-agent".to_owned(),
            description: None,
            agent_type: "orchestrator".to_owned(),
            capabilities: vec!["agents:write".to_owned()],
            metadata: None,
        })
        .await
        .expect("register delegator should succeed");

    let delegatee = client
        .register_agent(RegisterAgentRequest {
            name: "delegatee-agent".to_owned(),
            description: None,
            agent_type: "worker".to_owned(),
            capabilities: vec!["agents:read".to_owned()],
            metadata: None,
        })
        .await
        .expect("register delegatee should succeed");

    let delegation = client
        .delegate(DelegateRequest {
            delegatee_agent_id: delegatee.id.clone(),
            scopes: vec!["agents:read".to_owned()],
            ttl_seconds: 3600,
        })
        .await
        .expect("delegate should succeed");

    assert!(!delegation.delegation_token.is_empty());
    assert!(!delegation.chain_id.is_empty());

    let verification = client
        .verify_delegation(&delegation.delegation_token)
        .await
        .expect("verify_delegation should succeed");

    assert!(verification.valid);
    assert_eq!(
        verification.delegatee_agent_id.as_deref(),
        Some(delegatee.id.as_str())
    );

    client.delete_agent(&delegator.id).await.ok();
    client.delete_agent(&delegatee.id).await.ok();
}

// ─── Token manager concurrency ────────────────────────────────────────────────

#[tokio::test]
#[ignore]
async fn test_token_manager_concurrent_calls() {
    use std::sync::Arc;
    use sentryagent_idp::TokenManager;

    let api_url = std::env::var("AGENTIDP_API_URL").expect("AGENTIDP_API_URL must be set");
    let client_id =
        std::env::var("AGENTIDP_CLIENT_ID").expect("AGENTIDP_CLIENT_ID must be set");
    let client_secret =
        std::env::var("AGENTIDP_CLIENT_SECRET").expect("AGENTIDP_CLIENT_SECRET must be set");

    let tm = Arc::new(TokenManager::new(&api_url, &client_id, &client_secret));

    let handles: Vec<_> = (0..50)
        .map(|_| {
            let tm_clone = Arc::clone(&tm);
            tokio::spawn(async move { tm_clone.get_token().await })
        })
        .collect();

    let mut tokens = Vec::with_capacity(50);
    for handle in handles {
        let token = handle
            .await
            .expect("task did not panic")
            .expect("get_token succeeded");
        tokens.push(token);
    }

    // All 50 calls must return the same token (single fetch, all from cache).
    let first = &tokens[0];
    for t in &tokens[1..] {
        assert_eq!(t, first, "all concurrent calls must return the same token");
    }
}
