#![cfg(feature = "server")]
//! Tests for CoreRuntime operations
//!
//! T072: CoreRuntime::create_connector
//! T073: CoreRuntime::start_connector
//! T074: CoreRuntime::stop_connector
//! T075: CoreRuntime::send_command

use dirigent_core::connectors::{Connector, ConnectorCommand};
use dirigent_core::types::{ConnectorKind, ConnectorState};
use dirigent_core::{ConnectorConfig, CoreConfig, CoreError, CoreRuntime};
use serde_json::json;

/// Helper to create a test runtime
fn create_test_runtime() -> CoreRuntime {
    CoreRuntime::new(CoreConfig::default(), None)
}

/// Helper to create an OpenCode connector config
fn create_opencode_config(id: Option<String>, title: &str) -> ConnectorConfig {
    ConnectorConfig {
        id,
        kind: ConnectorKind::OpenCode,
        owner: None,
        title: Some(title.to_string()),
        working_directory: None,
        params: json!({
            "base_url": "http://localhost:12225",
            "title": title,
            "initial_session": null
        }),
        ..Default::default()
    }
}

// ============================================================================
// T072: CoreRuntime::create_connector
// ============================================================================

#[tokio::test]
async fn test_t072_connector_id_auto_generated_if_not_provided() {
    let runtime = create_test_runtime();
    let cfg = create_opencode_config(None, "Auto ID Test");

    let result = runtime.create_connector(uuid::Uuid::nil(), cfg).await;

    assert!(result.is_ok(), "Should create connector successfully");

    let connector_id = result.unwrap();
    assert!(!connector_id.is_empty(), "Generated ID should not be empty");

    // Verify it's a valid UUID format (36 chars with hyphens)
    assert_eq!(connector_id.len(), 36, "Should be UUID format");

    // Clean up
    runtime.remove_connector(&connector_id).await.ok();
}

#[tokio::test]
async fn test_t072_connector_uses_provided_id() {
    let runtime = create_test_runtime();
    let cfg = create_opencode_config(Some("my-custom-id".to_string()), "Custom ID Test");

    let result = runtime.create_connector(uuid::Uuid::nil(), cfg).await;

    assert!(result.is_ok(), "Should create connector successfully");
    assert_eq!(result.unwrap(), "my-custom-id");

    // Clean up
    runtime
        .remove_connector(&"my-custom-id".to_string())
        .await
        .ok();
}

#[tokio::test]
async fn test_t072_already_exists_error_if_id_conflicts() {
    let runtime = create_test_runtime();
    let cfg1 = create_opencode_config(Some("duplicate-id".to_string()), "First");

    // Create first connector
    let result1 = runtime
        .create_connector(uuid::Uuid::nil(), cfg1.clone())
        .await;
    assert!(result1.is_ok(), "First creation should succeed");

    // Try to create another with the same ID
    let result2 = runtime.create_connector(uuid::Uuid::nil(), cfg1).await;

    assert!(result2.is_err(), "Second creation should fail");
    assert_eq!(result2.unwrap_err(), CoreError::AlreadyExists);

    // Clean up
    runtime
        .remove_connector(&"duplicate-id".to_string())
        .await
        .ok();
}

#[tokio::test]
async fn test_t072_connector_appears_in_list_after_creation() {
    let runtime = create_test_runtime();

    // Initially empty
    let list_before = runtime.list_connectors(None).await;
    let initial_count = list_before.len();

    // Create a connector
    let cfg = create_opencode_config(Some("test-conn-1".to_string()), "Test 1");
    let connector_id = runtime
        .create_connector(uuid::Uuid::nil(), cfg)
        .await
        .unwrap();

    // List should now contain it
    let list_after = runtime.list_connectors(None).await;
    assert_eq!(list_after.len(), initial_count + 1);

    let found = list_after.iter().find(|c| c.id == connector_id);
    assert!(found.is_some(), "Created connector should be in list");

    let connector_summary = found.unwrap();
    assert_eq!(connector_summary.id, "test-conn-1");
    assert_eq!(connector_summary.title, "Test 1");
    assert_eq!(connector_summary.owner, uuid::Uuid::nil());
    assert_eq!(connector_summary.kind, ConnectorKind::OpenCode);

    // Clean up
    runtime.remove_connector(&connector_id).await.ok();
}

#[tokio::test]
async fn test_t072_invalid_config_returns_error() {
    let runtime = create_test_runtime();

    let cfg = ConnectorConfig {
        id: None,
        kind: ConnectorKind::OpenCode,
        owner: None,
        title: Some("Invalid".to_string()),
        working_directory: None,
        params: json!({
            "invalid": "config"
            // Missing required fields like base_url
        }),
        ..Default::default()
    };

    let result = runtime.create_connector(uuid::Uuid::nil(), cfg).await;

    assert!(result.is_err(), "Should fail with invalid config");
    assert_eq!(result.unwrap_err(), CoreError::InvalidConfig);
}

#[tokio::test]
async fn test_t072_mock_connector_not_allowed() {
    let runtime = create_test_runtime();

    let cfg = ConnectorConfig {
        id: None,
        kind: ConnectorKind::Mock,
        owner: None,
        title: Some("Mock".to_string()),
        working_directory: None,
        params: json!({}),
        ..Default::default()
    };

    let result = runtime.create_connector(uuid::Uuid::nil(), cfg).await;

    assert!(
        result.is_err(),
        "Mock connectors should not be creatable via API"
    );
    assert_eq!(result.unwrap_err(), CoreError::InvalidConfig);
}

#[tokio::test]
async fn test_t072_owner_override() {
    let runtime = create_test_runtime();

    let mut cfg = create_opencode_config(None, "Owner Test");
    // Try to set owner in config
    cfg.owner = Some(uuid::Uuid::from_u128(99));

    // Create with different owner
    let connector_id = runtime
        .create_connector(uuid::Uuid::from_u128(42), cfg)
        .await
        .unwrap();

    // Verify owner was overridden
    let list = runtime.list_connectors(None).await;
    let connector = list.iter().find(|c| c.id == connector_id).unwrap();
    assert_eq!(
        connector.owner,
        uuid::Uuid::from_u128(42),
        "Owner should be from parameter, not config"
    );

    // Clean up
    runtime.remove_connector(&connector_id).await.ok();
}

// ============================================================================
// T073: CoreRuntime::start_connector
// ============================================================================

#[tokio::test]
async fn test_t073_start_connector_not_found() {
    let runtime = create_test_runtime();

    let result = runtime.start_connector(&"nonexistent".to_string()).await;

    assert!(result.is_err(), "Should fail for nonexistent connector");
    assert_eq!(result.unwrap_err(), CoreError::NotFound);
}

#[tokio::test]
async fn test_t073_start_connector_not_yet_implemented() {
    // Note: As per the runtime.rs code, starting an existing connector
    // is not yet fully implemented. This test documents the current behavior.

    let runtime = create_test_runtime();
    let cfg = create_opencode_config(Some("start-test".to_string()), "Start Test");

    let connector_id = runtime
        .create_connector(uuid::Uuid::nil(), cfg)
        .await
        .unwrap();

    // Try to start it
    let result = runtime.start_connector(&connector_id).await;

    // Currently returns an error indicating not implemented
    assert!(
        result.is_err(),
        "Starting existing connector not yet supported"
    );

    // Clean up
    runtime.remove_connector(&connector_id).await.ok();
}

// ============================================================================
// T074: CoreRuntime::stop_connector
// ============================================================================

#[tokio::test]
async fn test_t074_stop_connector_not_found() {
    let runtime = create_test_runtime();

    let result = runtime.stop_connector(&"nonexistent".to_string()).await;

    assert!(result.is_err(), "Should fail for nonexistent connector");
    assert_eq!(result.unwrap_err(), CoreError::NotFound);
}

#[tokio::test]
async fn test_t074_stop_connector_changes_state_to_stopped() {
    let runtime = create_test_runtime();
    let cfg = create_opencode_config(Some("stop-test".to_string()), "Stop Test");

    let connector_id = runtime
        .create_connector(uuid::Uuid::nil(), cfg)
        .await
        .unwrap();

    // Get the connector and verify initial state
    let connector = runtime.get_connector(&connector_id).await.unwrap();
    let initial_state = connector.state();
    assert_eq!(initial_state, ConnectorState::Initializing);

    // Stop it
    let result = runtime.stop_connector(&connector_id).await;
    assert!(result.is_ok(), "Stop should succeed");

    // Verify state changed to Stopped
    let connector = runtime.get_connector(&connector_id).await.unwrap();
    let final_state = connector.state();
    assert_eq!(final_state, ConnectorState::Stopped);

    // Clean up
    runtime.remove_connector(&connector_id).await.ok();
}

#[tokio::test]
async fn test_t074_stop_connector_idempotent() {
    let runtime = create_test_runtime();
    let cfg = create_opencode_config(Some("stop-twice".to_string()), "Stop Twice");

    let connector_id = runtime
        .create_connector(uuid::Uuid::nil(), cfg)
        .await
        .unwrap();

    // Stop once
    let result1 = runtime.stop_connector(&connector_id).await;
    assert!(result1.is_ok(), "First stop should succeed");

    // Stop again
    let result2 = runtime.stop_connector(&connector_id).await;
    assert!(result2.is_ok(), "Second stop should also succeed");

    // State should still be Stopped
    let connector = runtime.get_connector(&connector_id).await.unwrap();
    assert_eq!(connector.state(), ConnectorState::Stopped);

    // Clean up
    runtime.remove_connector(&connector_id).await.ok();
}

// ============================================================================
// T075: CoreRuntime::send_command
// ============================================================================

#[tokio::test]
async fn test_t075_send_command_not_found() {
    let runtime = create_test_runtime();

    let result = runtime
        .send_command(&"nonexistent".to_string(), ConnectorCommand::ListSessions)
        .await;

    assert!(result.is_err(), "Should fail for nonexistent connector");
    assert_eq!(result.unwrap_err(), CoreError::NotFound);
}

#[tokio::test]
#[ignore] // TODO: Fix - cmd_rx is dropped when connector is dropped in create_connector
async fn test_t075_send_command_to_connector() {
    let runtime = create_test_runtime();
    let cfg = create_opencode_config(Some("cmd-test".to_string()), "Command Test");

    let connector_id = runtime
        .create_connector(uuid::Uuid::nil(), cfg)
        .await
        .unwrap();

    // Get the connector and subscribe to events
    let connector = runtime.get_connector(&connector_id).await.unwrap();
    let _events = connector.subscribe();

    // Send a command via runtime
    let result = runtime
        .send_command(&connector_id, ConnectorCommand::ListSessions)
        .await;

    assert!(result.is_ok(), "Send command should succeed");

    // Note: The command won't be processed until the connector is started,
    // but we've verified that the command was accepted

    // Clean up
    runtime.remove_connector(&connector_id).await.ok();
}

#[ignore] // TODO: Fix - cmd_rx is dropped when connector is dropped in create_connector
#[tokio::test]
async fn test_t075_send_all_command_types() {
    let runtime = create_test_runtime();
    let cfg = create_opencode_config(Some("all-cmds".to_string()), "All Commands");

    let connector_id = runtime
        .create_connector(uuid::Uuid::nil(), cfg)
        .await
        .unwrap();

    // Send ListSessions
    let result = runtime
        .send_command(&connector_id, ConnectorCommand::ListSessions)
        .await;
    assert!(result.is_ok());

    // Send ListMessages
    let result = runtime
        .send_command(
            &connector_id,
            ConnectorCommand::ListMessages {
                session_id: "test-session".to_string(),
            },
        )
        .await;
    assert!(result.is_ok());

    // Send SendMessage
    let result = runtime
        .send_command(
            &connector_id,
            ConnectorCommand::SendMessage {
                session_id: "test-session".to_string(),
                text: "Hello".to_string(),
            },
        )
        .await;
    assert!(result.is_ok());

    // Send Reconnect
    let result = runtime
        .send_command(&connector_id, ConnectorCommand::Reconnect)
        .await;
    assert!(result.is_ok());

    // Send Shutdown
    let result = runtime
        .send_command(&connector_id, ConnectorCommand::Shutdown)
        .await;
    assert!(result.is_ok());

    // Clean up
    runtime.remove_connector(&connector_id).await.ok();
}

#[tokio::test]
#[ignore] // TODO: Fix - cmd_rx is dropped when connector is dropped in create_connector
async fn test_t075_send_command_channel_capacity() {
    let runtime = create_test_runtime();
    let cfg = create_opencode_config(Some("capacity-test".to_string()), "Capacity Test");

    let connector_id = runtime
        .create_connector(uuid::Uuid::nil(), cfg)
        .await
        .unwrap();

    // Send many commands (the channel has capacity 100)
    for i in 0..50 {
        let result = runtime
            .send_command(&connector_id, ConnectorCommand::ListSessions)
            .await;
        assert!(result.is_ok(), "Command {} should be accepted", i);
    }

    // Clean up
    runtime.remove_connector(&connector_id).await.ok();
}

// ============================================================================
// Additional Runtime Tests
// ============================================================================

#[tokio::test]
async fn test_list_connectors_filters_by_owner() {
    let runtime = create_test_runtime();

    // Create connectors for different users
    let cfg1 = create_opencode_config(Some("user1-conn1".to_string()), "User 1 Conn 1");
    let cfg2 = create_opencode_config(Some("user1-conn2".to_string()), "User 1 Conn 2");
    let cfg3 = create_opencode_config(Some("user2-conn1".to_string()), "User 2 Conn 1");

    runtime
        .create_connector(uuid::Uuid::nil(), cfg1)
        .await
        .unwrap();
    runtime
        .create_connector(uuid::Uuid::nil(), cfg2)
        .await
        .unwrap();
    runtime
        .create_connector(uuid::Uuid::from_u128(2), cfg3)
        .await
        .unwrap();

    // List all
    let all = runtime.list_connectors(None).await;
    assert!(all.len() >= 3, "Should have at least 3 connectors");

    // List for user-1
    let user1_list = runtime.list_connectors(Some(uuid::Uuid::nil())).await;
    assert_eq!(user1_list.len(), 2, "User 1 should have 2 connectors");

    // List for user-2
    let user2_list = runtime
        .list_connectors(Some(uuid::Uuid::from_u128(2)))
        .await;
    assert_eq!(user2_list.len(), 1, "User 2 should have 1 connector");

    // Clean up
    runtime
        .remove_connector(&"user1-conn1".to_string())
        .await
        .ok();
    runtime
        .remove_connector(&"user1-conn2".to_string())
        .await
        .ok();
    runtime
        .remove_connector(&"user2-conn1".to_string())
        .await
        .ok();
}

#[tokio::test]
async fn test_get_connector_returns_some_if_exists() {
    let runtime = create_test_runtime();
    let cfg = create_opencode_config(Some("get-test".to_string()), "Get Test");

    let connector_id = runtime
        .create_connector(uuid::Uuid::nil(), cfg)
        .await
        .unwrap();

    let result = runtime.get_connector(&connector_id).await;
    assert!(result.is_some(), "Should find connector");

    let connector = result.unwrap();
    assert_eq!(connector.id(), &connector_id);
    assert_eq!(connector.title(), "Get Test");

    // Clean up
    runtime.remove_connector(&connector_id).await.ok();
}

#[tokio::test]
async fn test_get_connector_returns_none_if_not_exists() {
    let runtime = create_test_runtime();

    let result = runtime.get_connector(&"nonexistent".to_string()).await;
    assert!(result.is_none(), "Should not find nonexistent connector");
}

#[tokio::test]
async fn test_remove_connector_success() {
    let runtime = create_test_runtime();
    let cfg = create_opencode_config(Some("remove-test".to_string()), "Remove Test");

    let connector_id = runtime
        .create_connector(uuid::Uuid::nil(), cfg)
        .await
        .unwrap();

    // Verify it exists
    assert!(runtime.get_connector(&connector_id).await.is_some());

    // Remove it
    let result = runtime.remove_connector(&connector_id).await;
    assert!(result.is_ok(), "Remove should succeed");

    // Verify it's gone
    assert!(runtime.get_connector(&connector_id).await.is_none());
}

#[tokio::test]
async fn test_sharing_bus_subscribe_all() {
    let runtime = create_test_runtime();

    // Subscribe to every event on the SharingBus — this is the
    // replacement for the retired `subscribe_global()` API.
    let rx1 = runtime.sharing_bus().subscribe_all().await;
    let rx2 = runtime.sharing_bus().subscribe_all().await;

    // Verify both subscriptions are valid
    drop(rx1);
    drop(rx2);
    // If this compiles and runs, subscriptions work
}

// ============================================================================
// Issue 2: Zero-Connector State (Regression Test)
// ============================================================================

/// Test that new connectors can be added after removing all existing connectors
///
/// This test verifies the fix for Issue 2: "Removing All Connectors Breaks New Connection"
///
/// Regression test ensures:
/// - Creating first connector works (0 -> 1 transition)
/// - Removing all connectors works (1 -> 0 transition)
/// - Creating connector after zero state works (0 -> 1 again)
/// - No state corruption or race conditions
/// - Config persistence handles empty state correctly
#[tokio::test]
async fn test_issue_2_create_connector_after_removing_all() {
    let runtime = create_test_runtime();

    // Verify starting state is empty
    let initial_connectors = runtime.list_connectors(None).await;
    assert_eq!(
        initial_connectors.len(),
        0,
        "Should start with zero connectors"
    );

    // Create first connector (0 -> 1 transition)
    let cfg1 = create_opencode_config(Some("test-connector-1".to_string()), "First Connector");
    let id1 = runtime
        .create_connector(uuid::Uuid::nil(), cfg1)
        .await
        .expect("Should create first connector");

    let connectors_after_create = runtime.list_connectors(None).await;
    assert_eq!(
        connectors_after_create.len(),
        1,
        "Should have 1 connector after creation"
    );

    // Verify connector is in the list
    let connector1 = runtime.get_connector(&id1).await;
    assert!(connector1.is_some(), "First connector should exist");

    // Remove the connector (1 -> 0 transition)
    runtime
        .remove_connector(&id1)
        .await
        .expect("Should remove connector successfully");

    let connectors_after_remove = runtime.list_connectors(None).await;
    assert_eq!(
        connectors_after_remove.len(),
        0,
        "Should have zero connectors after removal"
    );

    // Verify connector is gone
    let connector1_after_remove = runtime.get_connector(&id1).await;
    assert!(
        connector1_after_remove.is_none(),
        "First connector should not exist after removal"
    );

    // Create new connector after reaching zero state (0 -> 1 again)
    let cfg2 = create_opencode_config(Some("test-connector-2".to_string()), "Second Connector");
    let id2 = runtime
        .create_connector(uuid::Uuid::nil(), cfg2)
        .await
        .expect("Should create connector after removing all");

    let connectors_final = runtime.list_connectors(None).await;
    assert_eq!(
        connectors_final.len(),
        1,
        "Should have 1 connector after recreation"
    );

    // Verify new connector is in the list
    let connector2 = runtime.get_connector(&id2).await;
    assert!(connector2.is_some(), "Second connector should exist");

    // Verify IDs are different
    assert_ne!(id1, id2, "New connector should have different ID");

    // Verify new connector is functional (state check)
    let connector2_handle = connector2.unwrap();
    let state = connector2_handle.state();
    assert_eq!(
        state,
        ConnectorState::Initializing,
        "New connector should be initializing"
    );

    // Clean up
    runtime.remove_connector(&id2).await.ok();
}

/// Test rapid remove-create cycles to check for race conditions
///
/// This test verifies that repeated transitions between empty and non-empty
/// connector states don't cause issues with config persistence or internal state.
#[tokio::test]
async fn test_issue_2_rapid_remove_create_cycles() {
    let runtime = create_test_runtime();

    // Perform 5 cycles of create -> remove
    for i in 0..5 {
        let cfg = create_opencode_config(
            Some(format!("cycle-connector-{}", i)),
            &format!("Cycle Test {}", i),
        );

        // Create connector
        let id = runtime
            .create_connector(uuid::Uuid::nil(), cfg)
            .await
            .expect(&format!("Should create connector in cycle {}", i));

        // Verify it exists
        let connectors = runtime.list_connectors(None).await;
        assert_eq!(
            connectors.len(),
            1,
            "Should have 1 connector after creation"
        );

        // Remove connector
        runtime
            .remove_connector(&id)
            .await
            .expect(&format!("Should remove connector in cycle {}", i));

        // Verify empty state
        let connectors = runtime.list_connectors(None).await;
        assert_eq!(
            connectors.len(),
            0,
            "Should have 0 connectors after removal"
        );
    }

    // Final verification: Create one more connector to ensure state is still valid
    let final_cfg = create_opencode_config(Some("final-connector".to_string()), "Final Test");
    let final_id = runtime
        .create_connector(uuid::Uuid::nil(), final_cfg)
        .await
        .expect("Should create connector after rapid cycles");

    let final_connectors = runtime.list_connectors(None).await;
    assert_eq!(final_connectors.len(), 1, "Should have 1 connector at end");

    // Clean up
    runtime.remove_connector(&final_id).await.ok();
}

/// Test that SharingBus subscriptions work across zero-connector transitions.
///
/// Ensures SSE subscriptions remain valid when all connectors are removed
/// and new connectors are added.
#[tokio::test]
async fn test_issue_2_global_events_survive_zero_connectors() {
    let runtime = create_test_runtime();

    // Subscribe to events before any connectors exist.
    let _rx = runtime.sharing_bus().subscribe_all().await;

    // Create connector
    let cfg = create_opencode_config(Some("event-test".to_string()), "Event Test");
    let id = runtime
        .create_connector(uuid::Uuid::nil(), cfg)
        .await
        .expect("Should create connector");

    // Remove connector (transition to zero)
    runtime
        .remove_connector(&id)
        .await
        .expect("Should remove connector");

    // Subscribe again after zero state
    let _rx2 = runtime.sharing_bus().subscribe_all().await;

    // Create another connector
    let cfg2 = create_opencode_config(Some("event-test-2".to_string()), "Event Test 2");
    let id2 = runtime
        .create_connector(uuid::Uuid::nil(), cfg2)
        .await
        .expect("Should create connector after zero state");

    // If we get here, subscriptions survived the zero-connector transition
    // Clean up
    runtime.remove_connector(&id2).await.ok();
}
