#![cfg(feature = "server")]
//! Tests for OpenCodeConnector state transitions and command handling
//!
//! T069: OpenCodeConnector State Transitions
//! T070: OpenCodeConnector Command Handling

use dirigent_core::connectors::opencode::{OpenCodeConfig, OpenCodeConnector};
use dirigent_core::connectors::{Connector, ConnectorCommand};
use dirigent_core::sharing::bus::SharingBus;
use dirigent_core::types::{ConnectorKind, ConnectorState};
use std::time::Duration;
use tokio::time::timeout;

/// Helper to create a test connector
fn create_test_connector(base_url: &str) -> OpenCodeConnector {
    let config = OpenCodeConfig {
        base_url: base_url.to_string(),
        initial_session: None,
    };

    OpenCodeConnector::new(
        "test-conn".to_string(),
        uuid::Uuid::nil(),
        "Test Connector".to_string(),
        config,
        SharingBus::new(),
    )
}

// ============================================================================
// T069: OpenCodeConnector State Transitions
// ============================================================================

#[tokio::test]
async fn test_t069_initial_state_is_initializing() {
    let connector = create_test_connector("http://localhost:12225");
    assert_eq!(connector.state(), ConnectorState::Initializing);
}

#[tokio::test]
async fn test_t069_connector_metadata() {
    let connector = create_test_connector("http://localhost:12225");

    assert_eq!(connector.id(), "test-conn");
    assert_eq!(*connector.owner(), uuid::Uuid::nil());
    assert_eq!(connector.title(), "Test Connector");
    assert_eq!(connector.kind(), ConnectorKind::OpenCode);
}

#[tokio::test]
async fn test_t069_state_transition_connecting() {
    // This test verifies that when started, the connector transitions to Connecting
    // Since we don't have a real OpenCode server, it will fail to connect and
    // enter Error state after retries, but we can verify the initial transition

    let connector = create_test_connector("http://192.0.2.1:12225"); // TEST-NET-1, guaranteed non-routable
    let _events = connector.subscribe();

    // Start the connector
    let _task_handle = connector.start_task().await;

    // Give it a moment to start transitioning
    tokio::time::sleep(Duration::from_millis(100)).await;

    // At this point, state should have transitioned from Initializing
    // It will be either Connecting, Ready, or Error (depending on timing)
    let state = connector.state();
    assert!(
        !matches!(state, ConnectorState::Initializing),
        "Expected state to transition from Initializing, got {:?}",
        state
    );

    // Clean up: send shutdown
    connector.stop();
    tokio::time::sleep(Duration::from_millis(100)).await;
}

#[tokio::test]
#[ignore] // This test is flaky due to timing and network conditions - ignore for now
async fn test_t069_state_transition_to_error_on_connection_failure() {
    let connector = create_test_connector("http://192.0.2.1:12225"); // TEST-NET-1, guaranteed non-routable
    let mut events = connector.subscribe();

    // Start the connector
    let _task_handle = connector.start_task().await;

    // Wait for connection failures (it will retry a few times)
    let error_received = timeout(Duration::from_secs(10), async {
        loop {
            if let Ok(event) = events.recv().await {
                if let dirigent_protocol::Event::Error { message: _ } = event {
                    return true;
                }
            }
        }
    })
    .await;

    assert!(
        error_received.is_ok(),
        "Should receive error event on connection failure"
    );

    // Eventually, state should be Error after retries
    tokio::time::sleep(Duration::from_secs(2)).await;
    let state = connector.state();
    assert!(
        matches!(state, ConnectorState::Error(_)),
        "Expected Error state, got {:?}",
        state
    );

    // Clean up
    connector.stop();
}

#[tokio::test]
async fn test_t069_state_transition_to_stopped_on_shutdown() {
    let connector = create_test_connector("http://localhost:12225");

    // Start the connector
    let task_handle = connector.start_task().await;

    // Give it a moment to start
    tokio::time::sleep(Duration::from_millis(100)).await;

    // Send shutdown command
    let cmd_tx = connector.command_tx();
    cmd_tx.send(ConnectorCommand::Shutdown).await.unwrap();

    // Wait for task to complete
    let result = timeout(Duration::from_secs(5), task_handle).await;
    assert!(result.is_ok(), "Task should complete on shutdown");

    // State should be Stopped
    let state = connector.state();
    assert_eq!(state, ConnectorState::Stopped);
}

// ============================================================================
// T070: OpenCodeConnector Command Handling
// ============================================================================

#[tokio::test]
async fn test_t070_list_sessions_command_sent() {
    let connector = create_test_connector("http://localhost:12225");
    let mut events = connector.subscribe();

    // Start the connector (it will fail to connect, but we can still send commands)
    let _task_handle = connector.start_task().await;

    // Give it a moment to start
    tokio::time::sleep(Duration::from_millis(100)).await;

    // Send ListSessions command
    let cmd_tx = connector.command_tx();
    cmd_tx.send(ConnectorCommand::ListSessions).await.unwrap();

    // We expect an error event since we can't actually connect
    // But this verifies the command was processed
    let error_or_sessions = timeout(Duration::from_secs(2), async {
        loop {
            if let Ok(event) = events.recv().await {
                match event {
                    dirigent_protocol::Event::Error { .. } => return "error",
                    dirigent_protocol::Event::SessionsListed { .. } => return "sessions",
                    _ => continue,
                }
            }
        }
    })
    .await;

    // We expect either an error (can't connect) or sessions (if somehow it worked)
    assert!(
        error_or_sessions.is_ok(),
        "Should receive response to ListSessions"
    );

    // Clean up
    connector.stop();
}

#[tokio::test]
async fn test_t070_list_messages_command_sent() {
    let connector = create_test_connector("http://localhost:12225");
    let mut events = connector.subscribe();

    // Start the connector
    let _task_handle = connector.start_task().await;
    tokio::time::sleep(Duration::from_millis(100)).await;

    // Send ListMessages command
    let cmd_tx = connector.command_tx();
    cmd_tx
        .send(ConnectorCommand::ListMessages {
            session_id: "test-session".to_string(),
        })
        .await
        .unwrap();

    // We expect an error event since we can't actually connect
    let error_or_messages = timeout(Duration::from_secs(2), async {
        loop {
            if let Ok(event) = events.recv().await {
                match event {
                    dirigent_protocol::Event::Error { .. } => return "error",
                    dirigent_protocol::Event::MessagesListed { .. } => return "messages",
                    _ => continue,
                }
            }
        }
    })
    .await;

    assert!(
        error_or_messages.is_ok(),
        "Should receive response to ListMessages"
    );

    // Clean up
    connector.stop();
}

#[tokio::test]
async fn test_t070_send_message_command_sent() {
    let connector = create_test_connector("http://localhost:12225");
    let mut events = connector.subscribe();

    // Start the connector
    let _task_handle = connector.start_task().await;
    tokio::time::sleep(Duration::from_millis(100)).await;

    // Send SendMessage command
    let cmd_tx = connector.command_tx();
    cmd_tx
        .send(ConnectorCommand::SendMessage {
            session_id: "test-session".to_string(),
            text: "Hello, world!".to_string(),
        })
        .await
        .unwrap();

    // We expect an error event since we can't actually connect
    let error_received = timeout(Duration::from_secs(2), async {
        loop {
            if let Ok(event) = events.recv().await {
                if matches!(event, dirigent_protocol::Event::Error { .. }) {
                    return true;
                }
            }
        }
    })
    .await;

    assert!(
        error_received.is_ok(),
        "Should receive error for SendMessage"
    );

    // Clean up
    connector.stop();
}

#[tokio::test]
#[ignore] // This test is flaky due to timing and network conditions - ignore for now
async fn test_t070_reconnect_command_restarts_sse() {
    let connector = create_test_connector("http://192.0.2.1:12225"); // TEST-NET-1, guaranteed non-routable
    let _events = connector.subscribe();

    // Start the connector
    let _task_handle = connector.start_task().await;

    // Wait for it to fail and enter Error state
    tokio::time::sleep(Duration::from_secs(3)).await;

    let state_before = connector.state();
    assert!(
        matches!(state_before, ConnectorState::Error(_)),
        "Should be in Error state before reconnect"
    );

    // Send Reconnect command
    let cmd_tx = connector.command_tx();
    cmd_tx.send(ConnectorCommand::Reconnect).await.unwrap();

    // Give it a moment to process reconnect
    tokio::time::sleep(Duration::from_millis(500)).await;

    // State should transition to Connecting (and then Error again)
    let state_after = connector.state();
    assert!(
        matches!(state_after, ConnectorState::Connecting)
            || matches!(state_after, ConnectorState::Error(_)),
        "Should be Connecting or Error after reconnect, got {:?}",
        state_after
    );

    // Clean up
    connector.stop();
}

#[tokio::test]
async fn test_t070_command_channel_is_cloneable() {
    let connector = create_test_connector("http://localhost:12225");

    // Get multiple command senders
    let cmd_tx1 = connector.command_tx();
    let cmd_tx2 = connector.command_tx();

    // Start the connector
    let _task_handle = connector.start_task().await;
    tokio::time::sleep(Duration::from_millis(100)).await;

    // Both senders should work
    assert!(cmd_tx1.send(ConnectorCommand::ListSessions).await.is_ok());
    assert!(cmd_tx2.send(ConnectorCommand::ListSessions).await.is_ok());

    // Clean up
    connector.stop();
}

#[tokio::test]
async fn test_t070_multiple_subscriptions() {
    let connector = create_test_connector("http://localhost:12225");

    // Create multiple subscriptions
    let mut events1 = connector.subscribe();
    let mut events2 = connector.subscribe();

    // Start the connector
    let _task_handle = connector.start_task().await;
    tokio::time::sleep(Duration::from_millis(100)).await;

    // Both should receive events
    let receive1 = timeout(Duration::from_secs(2), events1.recv());
    let receive2 = timeout(Duration::from_secs(2), events2.recv());

    // At least one should succeed (we'll get error events from failed connection)
    assert!(receive1.await.is_ok() || receive2.await.is_ok());

    // Clean up
    connector.stop();
}

#[tokio::test]
async fn test_t070_shutdown_command_stops_task() {
    let connector = create_test_connector("http://localhost:12225");

    // Start the connector
    let task_handle = connector.start_task().await;
    tokio::time::sleep(Duration::from_millis(100)).await;

    // Verify task is running by checking state is not Stopped
    let state_before = connector.state();
    assert!(!matches!(state_before, ConnectorState::Stopped));

    // Send Shutdown command via command channel
    let cmd_tx = connector.command_tx();
    cmd_tx.send(ConnectorCommand::Shutdown).await.unwrap();

    // Task should complete
    let result = timeout(Duration::from_secs(5), task_handle).await;
    assert!(result.is_ok(), "Task should complete within timeout");
    assert!(result.unwrap().is_ok(), "Task should not panic");

    // State should be Stopped
    assert_eq!(connector.state(), ConnectorState::Stopped);
}
