//! Real OpenCode HTTP API integration tests
//!
//! T078: OpenCode Real HTTP Tests
//!
//! These tests are marked with #[ignore] and require a real OpenCode instance
//! running. Set the DIRIGENT_TEST_API_URL environment variable to enable them.
//!
//! Example:
//! ```bash
//! DIRIGENT_TEST_API_URL=http://localhost:12225 cargo test --package dirigent_core -- --ignored
//! ```

#![cfg(feature = "server")]
use dirigent_core::connectors::opencode::{OpenCodeConfig, OpenCodeConnector};

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

/// Get the test API URL from environment or skip the test
fn get_test_api_url() -> Option<String> {
    env::var("DIRIGENT_TEST_API_URL").ok()
}

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

    OpenCodeConnector::new(
        "real-test".to_string(),
        uuid::Uuid::nil(),
        "Real API Test".to_string(),
        config,
        SharingBus::new(),
    )
}

// ============================================================================
// T078: OpenCode Real HTTP Tests
// ============================================================================

#[tokio::test]
#[ignore]
async fn test_t078_real_connection_successful() {
    let Some(api_url) = get_test_api_url() else {
        println!("Skipping test: DIRIGENT_TEST_API_URL not set");
        return;
    };

    let connector = create_real_connector(&api_url);
    let mut events = connector.subscribe();

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

    // Wait for connection
    let connected = timeout(Duration::from_secs(10), async {
        while let Ok(event) = events.recv().await {
            if matches!(event, dirigent_protocol::Event::Connected) {
                return true;
            }
            if matches!(event, dirigent_protocol::Event::Error { .. }) {
                eprintln!("Connection error: {:?}", event);
                return false;
            }
        }
        false
    })
    .await;

    assert!(
        connected.is_ok() && connected.unwrap(),
        "Should successfully connect to real OpenCode instance"
    );

    // Verify state is Ready
    assert_eq!(connector.state(), ConnectorState::Ready);

    // Clean up
    connector.stop();
    let _ = timeout(Duration::from_secs(5), task_handle).await;
}

#[tokio::test]
#[ignore]
async fn test_t078_real_list_sessions() {
    let Some(api_url) = get_test_api_url() else {
        println!("Skipping test: DIRIGENT_TEST_API_URL not set");
        return;
    };

    let connector = create_real_connector(&api_url);
    let mut events = connector.subscribe();

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

    // Wait for connection
    let connected = timeout(Duration::from_secs(10), async {
        while let Ok(event) = events.recv().await {
            if matches!(event, dirigent_protocol::Event::Connected) {
                return true;
            }
        }
        false
    })
    .await;

    assert!(
        connected.is_ok() && connected.unwrap(),
        "Should connect first"
    );

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

    // Wait for SessionsListed event
    let sessions_received = timeout(Duration::from_secs(5), async {
        while let Ok(event) = events.recv().await {
            if let dirigent_protocol::Event::SessionsListed {
                connector_id: _,
                sessions,
            } = event
            {
                println!("Received {} sessions", sessions.len());
                return Some(sessions);
            }
        }
        None
    })
    .await;

    assert!(
        sessions_received.is_ok(),
        "Should receive SessionsListed event"
    );
    let sessions = sessions_received.unwrap();
    assert!(sessions.is_some(), "Should have sessions data");

    // Clean up
    connector.stop();
}

#[tokio::test]
#[ignore]
async fn test_t078_real_list_messages() {
    let Some(api_url) = get_test_api_url() else {
        println!("Skipping test: DIRIGENT_TEST_API_URL not set");
        return;
    };

    let connector = create_real_connector(&api_url);
    let mut events = connector.subscribe();

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

    // Wait for connection
    timeout(Duration::from_secs(10), async {
        while let Ok(event) = events.recv().await {
            if matches!(event, dirigent_protocol::Event::Connected) {
                break;
            }
        }
    })
    .await
    .ok();

    // First, get a session ID by listing sessions
    let cmd_tx = connector.command_tx();
    cmd_tx.send(ConnectorCommand::ListSessions).await.unwrap();

    let session_id = timeout(Duration::from_secs(5), async {
        while let Ok(event) = events.recv().await {
            if let dirigent_protocol::Event::SessionsListed {
                connector_id: _,
                sessions,
            } = event
            {
                if let Some(first_session) = sessions.first() {
                    return Some(first_session.id.clone());
                }
            }
        }
        None
    })
    .await;

    if session_id.is_err() || session_id.as_ref().unwrap().is_none() {
        println!("No sessions available to test ListMessages");
        connector.stop();
        return;
    }

    let session_id = session_id.unwrap().unwrap();
    println!("Testing with session: {}", session_id);

    // Now list messages for that session
    cmd_tx
        .send(ConnectorCommand::ListMessages {
            session_id: session_id.clone(),
        })
        .await
        .unwrap();

    let messages_received = timeout(Duration::from_secs(5), async {
        while let Ok(event) = events.recv().await {
            if let dirigent_protocol::Event::MessagesListed { messages } = event {
                println!("Received {} messages", messages.len());
                return Some(messages);
            }
        }
        None
    })
    .await;

    assert!(
        messages_received.is_ok(),
        "Should receive MessagesListed event"
    );
    let messages = messages_received.unwrap();
    assert!(messages.is_some(), "Should have messages data");

    // Clean up
    connector.stop();
}

#[tokio::test]
#[ignore]
async fn test_t078_real_send_message() {
    let Some(api_url) = get_test_api_url() else {
        println!("Skipping test: DIRIGENT_TEST_API_URL not set");
        return;
    };

    let connector = create_real_connector(&api_url);
    let mut events = connector.subscribe();

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

    // Wait for connection
    timeout(Duration::from_secs(10), async {
        while let Ok(event) = events.recv().await {
            if matches!(event, dirigent_protocol::Event::Connected) {
                break;
            }
        }
    })
    .await
    .ok();

    // Get a session ID
    let cmd_tx = connector.command_tx();
    cmd_tx.send(ConnectorCommand::ListSessions).await.unwrap();

    let session_id = timeout(Duration::from_secs(5), async {
        while let Ok(event) = events.recv().await {
            if let dirigent_protocol::Event::SessionsListed {
                connector_id: _,
                sessions,
            } = event
            {
                if let Some(first_session) = sessions.first() {
                    return Some(first_session.id.clone());
                }
            }
        }
        None
    })
    .await;

    if session_id.is_err() || session_id.as_ref().unwrap().is_none() {
        println!("No sessions available to test SendMessage");
        connector.stop();
        return;
    }

    let session_id = session_id.unwrap().unwrap();
    println!("Sending message to session: {}", session_id);

    // Send a message
    cmd_tx
        .send(ConnectorCommand::SendMessage {
            session_id: session_id.clone(),
            text: "Test message from integration test".to_string(),
        })
        .await
        .unwrap();

    // Wait for response events (MessageStarted, SessionUpdate, MessageCompleted)
    let message_events_received = timeout(Duration::from_secs(30), async {
        let mut started = false;
        let mut parts = 0;
        let mut completed = false;

        while let Ok(event) = events.recv().await {
            match event {
                dirigent_protocol::Event::MessageStarted { .. } => {
                    println!("Received MessageStarted");
                    started = true;
                }
                dirigent_protocol::Event::SessionUpdate { .. } => {
                    parts += 1;
                }
                dirigent_protocol::Event::MessageCompleted { .. } => {
                    println!("Received MessageCompleted after {} parts", parts);
                    completed = true;
                    break;
                }
                dirigent_protocol::Event::Error { message } => {
                    eprintln!("Error during message send: {}", message);
                    break;
                }
                _ => {}
            }
        }

        (started, parts, completed)
    })
    .await;

    assert!(
        message_events_received.is_ok(),
        "Should receive message response events"
    );

    let (started, parts, completed) = message_events_received.unwrap();
    println!(
        "Message events: started={}, parts={}, completed={}",
        started, parts, completed
    );

    assert!(started, "Should receive MessageStarted event");
    assert!(parts > 0, "Should receive at least one SessionUpdate event");
    assert!(completed, "Should receive MessageCompleted event");

    // Clean up
    connector.stop();
}

#[tokio::test]
#[ignore]
async fn test_t078_real_reconnect_command() {
    let Some(api_url) = get_test_api_url() else {
        println!("Skipping test: DIRIGENT_TEST_API_URL not set");
        return;
    };

    let connector = create_real_connector(&api_url);
    let mut events = connector.subscribe();

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

    // Wait for initial connection
    timeout(Duration::from_secs(10), async {
        while let Ok(event) = events.recv().await {
            if matches!(event, dirigent_protocol::Event::Connected) {
                break;
            }
        }
    })
    .await
    .ok();

    assert_eq!(connector.state(), ConnectorState::Ready);

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

    // Should disconnect and reconnect
    let reconnected = timeout(Duration::from_secs(10), async {
        let mut saw_disconnect = false;
        while let Ok(event) = events.recv().await {
            match event {
                dirigent_protocol::Event::Disconnected => {
                    println!("Saw disconnect");
                    saw_disconnect = true;
                }
                dirigent_protocol::Event::Connected => {
                    println!("Saw reconnect");
                    if saw_disconnect {
                        return true;
                    }
                }
                _ => {}
            }
        }
        false
    })
    .await;

    // Note: The reconnect behavior may vary depending on the implementation
    // We just verify the command was processed without error
    println!(
        "Reconnect completed (saw reconnect cycle: {:?})",
        reconnected.ok()
    );

    // Clean up
    connector.stop();
}

#[tokio::test]
#[ignore]
async fn test_t078_real_sse_stream_reliability() {
    let Some(api_url) = get_test_api_url() else {
        println!("Skipping test: DIRIGENT_TEST_API_URL not set");
        return;
    };

    let connector = create_real_connector(&api_url);
    let mut events = connector.subscribe();

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

    // Collect events for 5 seconds
    let events_collected = timeout(Duration::from_secs(5), async {
        let mut count = 0;
        let mut event_types = std::collections::HashMap::new();

        while let Ok(event) = events.recv().await {
            count += 1;
            let type_name = match &event {
                dirigent_protocol::Event::Connected => "Connected",
                dirigent_protocol::Event::Disconnected => "Disconnected",
                dirigent_protocol::Event::SessionCreated { .. } => "SessionCreated",
                dirigent_protocol::Event::MessageStarted { .. } => "MessageStarted",
                dirigent_protocol::Event::SessionUpdate { .. } => "SessionUpdate",
                dirigent_protocol::Event::MessageCompleted { .. } => "MessageCompleted",
                dirigent_protocol::Event::Error { .. } => "Error",
                _ => "Other",
            };
            *event_types.entry(type_name).or_insert(0) += 1;
        }

        (count, event_types)
    })
    .await;

    assert!(
        events_collected.is_ok(),
        "Should collect events without timeout"
    );

    let (count, event_types) = events_collected.unwrap();
    println!("Collected {} events:", count);
    for (type_name, count) in event_types {
        println!("  {}: {}", type_name, count);
    }

    assert!(
        count > 0,
        "Should receive at least some events from SSE stream"
    );

    // Clean up
    connector.stop();
}

#[tokio::test]
#[ignore]
async fn test_t078_real_error_handling() {
    // Test with an invalid URL to verify error handling
    let connector = create_real_connector("http://invalid-hostname-that-does-not-exist:99999");
    let mut events = connector.subscribe();

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

    // Should receive error events
    let error_received = timeout(Duration::from_secs(10), async {
        while let Ok(event) = events.recv().await {
            if let dirigent_protocol::Event::Error { message } = event {
                println!("Received expected error: {}", message);
                return true;
            }
        }
        false
    })
    .await;

    assert!(
        error_received.is_ok() && error_received.unwrap(),
        "Should receive error event"
    );

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

    // Clean up
    connector.stop();
}
