//! Basic API endpoint tests for OpenFang.
//!
//! These tests verify the core API endpoints work correctly:
//! - Health and status endpoints
//! - Agent CRUD operations
//! - Session management
//! - Error handling

use super::e2e_common::*;

// ---------------------------------------------------------------------------
// Health & Status Tests
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_health_endpoint_returns_ok() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let response = get(&daemon, "/api/health").await;

    assert_eq!(response["status"], "ok");
    assert!(response["version"].is_string());
}

#[tokio::test]
async fn test_health_detail_endpoint() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let response = get(&daemon, "/api/health/detail").await;

    assert_eq!(response["status"], "ok");
    // Detailed health includes more information
    assert!(response["version"].is_string());
}

#[tokio::test]
async fn test_status_endpoint() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let response = get(&daemon, "/api/status").await;

    assert_eq!(response["status"], "running");
    assert_eq!(response["agent_count"], 0);
    assert!(response["uptime_seconds"].is_number());
    assert!(response["default_provider"].is_string());
}

#[tokio::test]
async fn test_version_endpoint() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let response = get(&daemon, "/api/version").await;

    assert!(response["version"].is_string());
    assert!(response["commit"].is_string() || response["commit"].is_null());
}

// ---------------------------------------------------------------------------
// Agent CRUD Tests
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_spawn_agent() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let agent = create_test_agent(&daemon, DEFAULT_MANIFEST).await;

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

#[tokio::test]
async fn test_list_agents_empty() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let agents = list_agents(&daemon).await;

    assert_eq!(agents.len(), 0);
}

#[tokio::test]
async fn test_list_agents_with_agents() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    // Create two agents
    let agent1 = create_named_test_agent(&daemon, "agent-1").await;
    let agent2 = create_named_test_agent(&daemon, "agent-2").await;

    let agents = list_agents(&daemon).await;

    assert_eq!(agents.len(), 2);

    // Verify both agents are present
    let ids: Vec<&str> = agents.iter().filter_map(|a| a["id"].as_str()).collect();
    assert!(ids.contains(&agent1.id.as_str()));
    assert!(ids.contains(&agent2.id.as_str()));
}

#[tokio::test]
async fn test_get_agent_by_id() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let agent = create_test_agent(&daemon, DEFAULT_MANIFEST).await;

    let response = get(&daemon, &format!("/api/agents/{}", agent.id)).await;

    assert_eq!(response["id"], agent.id);
    assert_eq!(response["name"], "test-agent");
}

#[tokio::test]
async fn test_kill_agent() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let agent = create_test_agent(&daemon, DEFAULT_MANIFEST).await;
    assert_eq!(agent_count(&daemon).await, 1);

    kill_agent(&daemon, &agent.id).await;

    assert_eq!(agent_count(&daemon).await, 0);
}

#[tokio::test]
async fn test_kill_nonexistent_agent_returns_404() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let fake_id = uuid::Uuid::new_v4().to_string();
    let client = reqwest::Client::new();

    let resp = client
        .delete(format!("{}/api/agents/{}", daemon.base_url(), fake_id))
        .send()
        .await
        .unwrap();

    assert_eq!(resp.status(), 404);
}

#[tokio::test]
async fn test_spawn_multiple_agents() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    // Create 5 agents
    let mut agent_ids = Vec::new();
    for i in 0..5 {
        let agent = create_named_test_agent(&daemon, &format!("agent-{}", i)).await;
        agent_ids.push(agent.id);
    }

    assert_eq!(agent_count(&daemon).await, 5);

    // Kill all agents
    for id in agent_ids {
        kill_agent(&daemon, &id).await;
    }

    assert_eq!(agent_count(&daemon).await, 0);
}

// ---------------------------------------------------------------------------
// Error Handling Tests
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_invalid_agent_id_returns_400() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let client = reqwest::Client::new();

    // Invalid (non-UUID) agent ID
    let resp = client
        .get(format!("{}/api/agents/not-a-uuid", daemon.base_url()))
        .send()
        .await
        .unwrap();

    assert_eq!(resp.status(), 400);
    let body: serde_json::Value = resp.json().await.unwrap();
    assert!(body["error"].as_str().unwrap().contains("Invalid"));
}

#[tokio::test]
async fn test_invalid_manifest_returns_400() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let client = reqwest::Client::new();

    let resp = client
        .post(format!("{}/api/agents", daemon.base_url()))
        .json(&serde_json::json!({"manifest_toml": "invalid {{ toml"}))
        .send()
        .await
        .unwrap();

    assert_eq!(resp.status(), 400);
    let body: serde_json::Value = resp.json().await.unwrap();
    assert!(body["error"].as_str().unwrap().contains("Invalid manifest"));
}

#[tokio::test]
async fn test_message_to_nonexistent_agent_returns_404() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let client = reqwest::Client::new();
    let fake_id = uuid::Uuid::new_v4().to_string();

    let resp = client
        .post(format!(
            "{}/api/agents/{}/message",
            daemon.base_url(),
            fake_id
        ))
        .json(&serde_json::json!({"message": "hello"}))
        .send()
        .await
        .unwrap();

    assert_eq!(resp.status(), 404);
}

// ---------------------------------------------------------------------------
// Session Tests
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_agent_session_empty_initially() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let agent = create_test_agent(&daemon, DEFAULT_MANIFEST).await;

    let response = get(&daemon, &format!("/api/agents/{}/session", agent.id)).await;

    assert_eq!(response["message_count"], 0);
    assert_eq!(response["messages"].as_array().unwrap().len(), 0);
}

// ---------------------------------------------------------------------------
// Auth Tests
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_health_is_public_without_auth() {
    // Start daemon with auth enabled
    let config = DaemonConfig::default().with_auth_key("secret-key-123");
    let daemon = spawn_daemon_with_config(config).await;
    wait_for_health(daemon.base_url()).await;

    // Health endpoint should be accessible without auth
    let response = get(&daemon, "/api/health").await;
    assert_eq!(response["status"], "ok");
}

#[tokio::test]
async fn test_protected_endpoint_requires_auth() {
    // Start daemon with auth enabled
    let config = DaemonConfig::default().with_auth_key("secret-key-123");
    let daemon = spawn_daemon_with_config(config).await;
    wait_for_health(daemon.base_url()).await;

    let client = reqwest::Client::new();

    // Protected endpoint without auth header should return 401
    let resp = client
        .get(format!("{}/api/models", daemon.base_url()))
        .send()
        .await
        .unwrap();

    assert_eq!(resp.status(), 401);
}

#[tokio::test]
async fn test_protected_endpoint_with_correct_auth() {
    // Start daemon with auth enabled
    let config = DaemonConfig::default().with_auth_key("secret-key-123");
    let daemon = spawn_daemon_with_config(config).await;
    wait_for_health(daemon.base_url()).await;

    let client = reqwest::Client::new();

    // Protected endpoint with correct auth header should succeed
    let resp = client
        .get(format!("{}/api/models", daemon.base_url()))
        .header("authorization", "Bearer secret-key-123")
        .send()
        .await
        .unwrap();

    assert_eq!(resp.status(), 200);
}

#[tokio::test]
async fn test_protected_endpoint_rejects_wrong_auth() {
    // Start daemon with auth enabled
    let config = DaemonConfig::default().with_auth_key("secret-key-123");
    let daemon = spawn_daemon_with_config(config).await;
    wait_for_health(daemon.base_url()).await;

    let client = reqwest::Client::new();

    // Protected endpoint with wrong auth header should return 401
    let resp = client
        .get(format!("{}/api/models", daemon.base_url()))
        .header("authorization", "Bearer wrong-key")
        .send()
        .await
        .unwrap();

    assert_eq!(resp.status(), 401);
}

// ---------------------------------------------------------------------------
// Request ID Tests
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_request_id_header_is_present() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let client = reqwest::Client::new();

    let resp = client
        .get(format!("{}/api/health", daemon.base_url()))
        .send()
        .await
        .unwrap();

    let request_id = resp
        .headers()
        .get("x-request-id")
        .expect("x-request-id header should be present");

    let id_str = request_id.to_str().unwrap();
    assert!(
        uuid::Uuid::parse_str(id_str).is_ok(),
        "x-request-id should be a valid UUID, got: {}",
        id_str
    );
}

// ---------------------------------------------------------------------------
// Config Tests
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_get_config() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let response = get(&daemon, "/api/config").await;

    // Config should have basic fields
    assert!(response.is_object());
}

// ---------------------------------------------------------------------------
// Provider & Model Tests
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_list_providers() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let response = get(&daemon, "/api/providers").await;

    assert!(response.is_array());
    // Should have at least the default provider
    let providers = response.as_array().unwrap();
    assert!(!providers.is_empty());
}

#[tokio::test]
async fn test_list_models() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let response = get(&daemon, "/api/models").await;

    assert!(response.is_array());
}

// ---------------------------------------------------------------------------
// Workflow Tests
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_workflow_crud() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    // Create agent for workflow
    let agent = create_test_agent(&daemon, DEFAULT_MANIFEST).await;

    // Create workflow
    let workflow = create_test_workflow(&agent.name);
    let response = post(&daemon, "/api/workflows", &workflow).await;

    assert!(response["workflow_id"].is_string());
    let workflow_id = response["workflow_id"].as_str().unwrap();

    // List workflows
    let workflows = get(&daemon, "/api/workflows").await;
    assert_eq!(workflows.as_array().unwrap().len(), 1);
    assert_eq!(workflows[0]["name"], "test-workflow");
}

// ---------------------------------------------------------------------------
// Trigger Tests
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_trigger_crud() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    // Create agent for trigger
    let agent = create_test_agent(&daemon, DEFAULT_MANIFEST).await;

    // Create trigger
    let trigger = create_test_trigger(&agent.id);
    let response = post(&daemon, "/api/triggers", &trigger).await;

    assert!(response["trigger_id"].is_string());
    assert_eq!(response["agent_id"], agent.id);

    // List triggers
    let triggers = get(&daemon, "/api/triggers").await;
    assert_eq!(triggers.as_array().unwrap().len(), 1);
    assert_eq!(triggers[0]["enabled"], true);
}

// ---------------------------------------------------------------------------
// Budget Tests
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_budget_status() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let response = get(&daemon, "/api/budget").await;

    // Budget should have basic fields
    assert!(response["enabled"].is_boolean() || response["daily_limit"].is_number());
}

// ---------------------------------------------------------------------------
// Usage Tests
// ---------------------------------------------------------------------------

#[tokio::test]
async fn test_usage_stats() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let response = get(&daemon, "/api/usage").await;

    assert!(response.is_object());
}

#[tokio::test]
async fn test_usage_summary() {
    let daemon = spawn_daemon().await;
    wait_for_health(daemon.base_url()).await;

    let response = get(&daemon, "/api/usage/summary").await;

    assert!(response.is_object());
}

// ---------------------------------------------------------------------------
// LLM Integration Tests (require API key)
// ---------------------------------------------------------------------------

#[tokio::test]
#[ignore = "Requires GROQ_API_KEY environment variable"]
async fn test_send_message_with_real_llm() {
    if !can_run_llm_tests() {
        eprintln!("{}", llm_skip_reason());
        return;
    }

    let config = DaemonConfig::groq();
    let daemon = spawn_daemon_with_config(config).await;
    wait_for_health(daemon.base_url()).await;

    let agent = create_test_agent(&daemon, LLM_MANIFEST).await;
    let response = send_message(&daemon, &agent.id, "Say hello in exactly 3 words.").await;

    assert!(!response.response.is_empty(), "LLM response should not be empty");
    assert!(response.input_tokens > 0, "Should have input tokens");
    assert!(response.output_tokens > 0, "Should have output tokens");

    // Verify session has messages
    let session = get(&daemon, &format!("/api/agents/{}/session", agent.id)).await;
    assert!(session["message_count"].as_u64().unwrap() > 0);
}

#[tokio::test]
#[ignore = "Requires GROQ_API_KEY environment variable"]
async fn test_budget_increases_after_llm_call() {
    if !can_run_llm_tests() {
        eprintln!("{}", llm_skip_reason());
        return;
    }

    let config = DaemonConfig::groq();
    let daemon = spawn_daemon_with_config(config).await;
    wait_for_health(daemon.base_url()).await;

    // Get initial budget
    let initial_budget = get(&daemon, "/api/budget").await;

    // Make an LLM call
    let agent = create_test_agent(&daemon, LLM_MANIFEST).await;
    let _ = send_message(&daemon, &agent.id, "Hello").await;

    // Get updated budget
    let updated_budget = get(&daemon, "/api/budget").await;

    // Budget tracking should reflect the usage
    // (The exact structure depends on the implementation)
    assert!(updated_budget.is_object());
}
