//! TEST-001: Comprehensive Stdio Integration Tests
//!
//! This file contains comprehensive integration tests for the ACP client over stdio transport.
//! It tests the full lifecycle of ACP communication using the dirigate in stdio mode.
//!
//! Test Coverage:
//! - T001: Full lifecycle (initialize, new session, prompt, cancel, load)
//! - T002: Initialize handshake
//! - T003: Session creation
//! - T004: Message sending with streaming
//! - T005: Session cancellation
//! - T006: Session loading
//! - T007: Multiple concurrent sessions
//! - T008: Graceful shutdown

#![cfg(feature = "server")]

use dirigent_core::connectors::acp::{AcpConfig, AcpConnector};
use dirigent_core::connectors::{Connector, ConnectorCommand};
use dirigent_protocol::Event;
use std::path::PathBuf;
use std::time::Duration;
use tokio::time::timeout;

/// Helper to create a test fixture path
fn test_fixture_path() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join("fixtures")
        .join("acp_test.yaml")
}

/// Helper to get the mocker binary path
fn mocker_binary_path() -> String {
    // First try the compiled binary in target directory
    let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    let project_root = manifest_dir.parent().unwrap().parent().unwrap();

    // Try debug build first
    let debug_path = project_root
        .join("target")
        .join("debug")
        .join("dirigate.exe");

    if debug_path.exists() {
        return debug_path.to_string_lossy().to_string();
    }

    // Try release build
    let release_path = project_root
        .join("target")
        .join("release")
        .join("dirigate.exe");

    if release_path.exists() {
        return release_path.to_string_lossy().to_string();
    }

    // Fallback to cargo run
    "cargo".to_string()
}

/// Helper to create mocker args
fn mocker_args(fixture_path: PathBuf) -> Vec<String> {
    let mocker_bin = mocker_binary_path();

    if mocker_bin == "cargo" {
        vec![
            "run".to_string(),
            "--package".to_string(),
            "dirigate".to_string(),
            "--".to_string(),
            "serve".to_string(),
            "--stdio".to_string(),
            "--fixtures".to_string(),
            fixture_path.to_string_lossy().to_string(),
        ]
    } else {
        vec![
            "serve".to_string(),
            "--stdio".to_string(),
            "--fixtures".to_string(),
            fixture_path.to_string_lossy().to_string(),
        ]
    }
}

/// Helper to create a test ACP connector with stdio transport
fn create_stdio_connector() -> AcpConnector {
    let mocker_bin = mocker_binary_path();
    let fixture_path = test_fixture_path();
    let args = mocker_args(fixture_path);

    let config = AcpConfig::stdio(mocker_bin, args)
        .with_retry_max_attempts(3)
        .with_retry_initial_delay(Duration::from_millis(500));

    AcpConnector::new(
        "stdio-test-connector".to_string(),
        uuid::Uuid::nil(),
        "Stdio Test Connector".to_string(),
        config,
        dirigent_core::sharing::bus::SharingBus::new(),
    )
    .expect("Failed to create connector")
}

/// Helper to wait for event with timeout
async fn wait_for_event<F>(
    events_rx: &mut tokio::sync::broadcast::Receiver<Event>,
    predicate: F,
    timeout_duration: Duration,
) -> anyhow::Result<Event>
where
    F: Fn(&Event) -> bool,
{
    let result = timeout(timeout_duration, async {
        loop {
            match events_rx.recv().await {
                Ok(event) => {
                    if predicate(&event) {
                        return Ok(event);
                    }
                }
                Err(e) => {
                    return Err(anyhow::anyhow!("Event receive error: {}", e));
                }
            }
        }
    })
    .await;

    match result {
        Ok(Ok(event)) => Ok(event),
        Ok(Err(e)) => Err(e),
        Err(_) => Err(anyhow::anyhow!("Timeout waiting for event")),
    }
}

// ============================================================================
// T001: Full Lifecycle Test
// ============================================================================

#[tokio::test]
#[ignore = "Requires dirigate binary to be built"]
async fn test_t001_full_lifecycle_stdio() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter("debug")
        .with_test_writer()
        .try_init()
        .ok();

    // Create connector
    let connector = create_stdio_connector();
    let mut events = connector.subscribe();

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

    // Wait for connection
    let connected_event = wait_for_event(
        &mut events,
        |e| matches!(e, Event::Connected),
        Duration::from_secs(10),
    )
    .await?;

    assert!(matches!(connected_event, Event::Connected));
    tracing::info!("✓ Connected to mocker");

    // Create a new session
    let cmd_tx = connector.command_tx();
    cmd_tx
        .send(ConnectorCommand::CreateSession {
            cwd: None,
            project_id: None,
            ownership: dirigent_protocol::SessionOwnership::internal(),
        })
        .await?;

    let session_created = wait_for_event(
        &mut events,
        |e| matches!(e, Event::SessionCreated { .. }),
        Duration::from_secs(5),
    )
    .await?;

    let session_id = match session_created {
        Event::SessionCreated { session, .. } => {
            tracing::info!("✓ Session created: {}", session.id);
            session.id
        }
        _ => anyhow::bail!("Expected SessionCreated event"),
    };

    // Send a message
    cmd_tx
        .send(ConnectorCommand::SendMessage {
            session_id: session_id.clone(),
            text: "Hello, test!".to_string(),
        })
        .await?;

    // Wait for message started
    let message_started = wait_for_event(
        &mut events,
        |e| matches!(e, Event::MessageStarted { .. }),
        Duration::from_secs(5),
    )
    .await?;

    let _message_id = match message_started {
        Event::MessageStarted { message, .. } => {
            tracing::info!("✓ Message started: {}", message.id);
            message.id
        }
        _ => anyhow::bail!("Expected MessageStarted event"),
    };

    // Wait for message content (streaming chunks)
    let mut received_content = false;
    for _ in 0..10 {
        if let Ok(event) = timeout(Duration::from_secs(2), events.recv()).await {
            match event? {
                Event::SessionUpdate {
                    connector_id: _,
                    session_id: _,
                    update,
                } => {
                    if let dirigent_protocol::SessionUpdate::AgentMessageChunk { .. } = update {
                        received_content = true;
                        tracing::info!("✓ Received message content");
                        break;
                    }
                }
                Event::MessageCompleted { message, .. } => {
                    tracing::info!("✓ Message completed: {}", message.id);
                    received_content = true;
                    break;
                }
                _ => {}
            }
        }
    }

    assert!(received_content, "Should receive message content");

    // Test cancellation (send another message then cancel immediately)
    cmd_tx
        .send(ConnectorCommand::SendMessage {
            session_id: session_id.clone(),
            text: "This should be cancelled".to_string(),
        })
        .await?;

    // Cancel immediately
    cmd_tx
        .send(ConnectorCommand::CancelGeneration {
            session_id: session_id.clone(),
        })
        .await?;

    tracing::info!("✓ Sent cancellation request");

    // Stop the connector
    connector.stop();

    // Wait for task to complete
    let _ = timeout(Duration::from_secs(5), task_handle).await?;

    tracing::info!("✓ Full lifecycle completed successfully");
    Ok(())
}

// ============================================================================
// T002: Initialize Handshake
// ============================================================================

#[tokio::test]
#[ignore = "Requires dirigate binary to be built"]
async fn test_t002_initialize_handshake() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter("debug")
        .with_test_writer()
        .try_init()
        .ok();

    let connector = create_stdio_connector();
    let mut events = connector.subscribe();

    let task_handle = connector.start_task().await;

    // Wait for connected event
    let connected = wait_for_event(
        &mut events,
        |e| matches!(e, Event::Connected),
        Duration::from_secs(10),
    )
    .await?;

    assert!(matches!(connected, Event::Connected));
    tracing::info!("✓ Initialize handshake completed");

    // Cleanup
    connector.stop();
    let _ = timeout(Duration::from_secs(5), task_handle).await?;

    Ok(())
}

// ============================================================================
// T003: Session Creation
// ============================================================================

#[tokio::test]
#[ignore = "Requires dirigate binary to be built"]
async fn test_t003_session_creation() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter("debug")
        .with_test_writer()
        .try_init()
        .ok();

    let connector = create_stdio_connector();
    let mut events = connector.subscribe();
    let task_handle = connector.start_task().await;

    // Wait for connection
    wait_for_event(
        &mut events,
        |e| matches!(e, Event::Connected),
        Duration::from_secs(10),
    )
    .await?;

    // Create session
    let cmd_tx = connector.command_tx();
    cmd_tx
        .send(ConnectorCommand::CreateSession {
            cwd: None,
            project_id: None,
            ownership: dirigent_protocol::SessionOwnership::internal(),
        })
        .await?;

    // Wait for session created event
    let session_created = wait_for_event(
        &mut events,
        |e| matches!(e, Event::SessionCreated { .. }),
        Duration::from_secs(5),
    )
    .await?;

    match session_created {
        Event::SessionCreated { session, .. } => {
            assert_eq!(session.title, "Session Creation Test");
            tracing::info!("✓ Session created with correct title");
        }
        _ => anyhow::bail!("Expected SessionCreated event"),
    }

    // Cleanup
    connector.stop();
    let _ = timeout(Duration::from_secs(5), task_handle).await?;

    Ok(())
}

// ============================================================================
// T004: Message Sending with Streaming
// ============================================================================

#[tokio::test]
#[ignore = "Requires dirigate binary to be built"]
async fn test_t004_message_streaming() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter("debug")
        .with_test_writer()
        .try_init()
        .ok();

    let connector = create_stdio_connector();
    let mut events = connector.subscribe();
    let task_handle = connector.start_task().await;

    // Wait for connection and create session
    wait_for_event(
        &mut events,
        |e| matches!(e, Event::Connected),
        Duration::from_secs(10),
    )
    .await?;

    let cmd_tx = connector.command_tx();
    cmd_tx
        .send(ConnectorCommand::CreateSession {
            cwd: None,
            project_id: None,
            ownership: dirigent_protocol::SessionOwnership::internal(),
        })
        .await?;

    let session_id = match wait_for_event(
        &mut events,
        |e| matches!(e, Event::SessionCreated { .. }),
        Duration::from_secs(5),
    )
    .await?
    {
        Event::SessionCreated { session, .. } => session.id,
        _ => anyhow::bail!("Expected SessionCreated"),
    };

    // Send message
    cmd_tx
        .send(ConnectorCommand::SendMessage {
            session_id: session_id.clone(),
            text: "Tell me a story".to_string(),
        })
        .await?;

    // Wait for message started
    wait_for_event(
        &mut events,
        |e| matches!(e, Event::MessageStarted { .. }),
        Duration::from_secs(5),
    )
    .await?;

    // Collect streaming chunks
    let mut chunk_count = 0;
    let mut completed = false;

    for _ in 0..20 {
        if let Ok(result) = timeout(Duration::from_secs(2), events.recv()).await {
            match result? {
                Event::SessionUpdate { update, .. } => {
                    if let dirigent_protocol::SessionUpdate::AgentMessageChunk { .. } = update {
                        chunk_count += 1;
                        tracing::info!("Received chunk {}", chunk_count);
                    }
                }
                Event::MessageCompleted { .. } => {
                    completed = true;
                    tracing::info!("✓ Message completed after {} chunks", chunk_count);
                    break;
                }
                _ => {}
            }
        }
    }

    assert!(chunk_count > 0, "Should receive at least one chunk");
    assert!(completed, "Message should complete");

    // Cleanup
    connector.stop();
    let _ = timeout(Duration::from_secs(5), task_handle).await?;

    Ok(())
}

// ============================================================================
// T005: Session Cancellation
// ============================================================================

#[tokio::test]
#[ignore = "Requires dirigate binary to be built"]
async fn test_t005_session_cancellation() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter("debug")
        .with_test_writer()
        .try_init()
        .ok();

    let connector = create_stdio_connector();
    let mut events = connector.subscribe();
    let task_handle = connector.start_task().await;

    // Setup: connect and create session
    wait_for_event(
        &mut events,
        |e| matches!(e, Event::Connected),
        Duration::from_secs(10),
    )
    .await?;

    let cmd_tx = connector.command_tx();
    cmd_tx
        .send(ConnectorCommand::CreateSession {
            cwd: None,
            project_id: None,
            ownership: dirigent_protocol::SessionOwnership::internal(),
        })
        .await?;

    let session_id = match wait_for_event(
        &mut events,
        |e| matches!(e, Event::SessionCreated { .. }),
        Duration::from_secs(5),
    )
    .await?
    {
        Event::SessionCreated { session, .. } => session.id,
        _ => anyhow::bail!("Expected SessionCreated"),
    };

    // Send message
    cmd_tx
        .send(ConnectorCommand::SendMessage {
            session_id: session_id.clone(),
            text: "Long response please".to_string(),
        })
        .await?;

    // Wait for message to start
    wait_for_event(
        &mut events,
        |e| matches!(e, Event::MessageStarted { .. }),
        Duration::from_secs(5),
    )
    .await?;

    // Wait for at least one chunk
    wait_for_event(
        &mut events,
        |e| matches!(e, Event::SessionUpdate { .. }),
        Duration::from_secs(5),
    )
    .await?;

    // Now cancel
    cmd_tx
        .send(ConnectorCommand::CancelGeneration {
            session_id: session_id.clone(),
        })
        .await?;

    tracing::info!("✓ Cancellation sent successfully");

    // The message should either complete or be cancelled
    // Either way, we verify the cancel command was accepted

    // Cleanup
    connector.stop();
    let _ = timeout(Duration::from_secs(5), task_handle).await?;

    Ok(())
}

// ============================================================================
// T006: Session Loading
// ============================================================================

#[tokio::test]
#[ignore = "Requires dirigate binary to be built"]
async fn test_t006_session_loading() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter("debug")
        .with_test_writer()
        .try_init()
        .ok();

    let connector = create_stdio_connector();
    let mut events = connector.subscribe();
    let task_handle = connector.start_task().await;

    // Wait for connection
    wait_for_event(
        &mut events,
        |e| matches!(e, Event::Connected),
        Duration::from_secs(10),
    )
    .await?;

    // Try to load a session from the fixture
    // The fixture should have a pre-defined session we can load
    let cmd_tx = connector.command_tx();
    cmd_tx
        .send(ConnectorCommand::LoadSession {
            session_id: "test-session-1".to_string(),
            cwd: ".".to_string(),
            mcp_servers: None,
        })
        .await?;

    // Wait for session loaded event
    let session_loaded = wait_for_event(
        &mut events,
        |e| matches!(e, Event::SessionCreated { .. }),
        Duration::from_secs(5),
    )
    .await?;

    match session_loaded {
        Event::SessionCreated { session, .. } => {
            assert_eq!(session.id, "test-session-1");
            tracing::info!("✓ Session loaded successfully");
        }
        _ => anyhow::bail!("Expected SessionCreated event"),
    }

    // Cleanup
    connector.stop();
    let _ = timeout(Duration::from_secs(5), task_handle).await?;

    Ok(())
}

// ============================================================================
// T007: Multiple Concurrent Sessions
// ============================================================================

#[tokio::test]
#[ignore = "Requires dirigate binary to be built"]
async fn test_t007_multiple_concurrent_sessions() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter("debug")
        .with_test_writer()
        .try_init()
        .ok();

    let connector = create_stdio_connector();
    let mut events = connector.subscribe();
    let task_handle = connector.start_task().await;

    // Wait for connection
    wait_for_event(
        &mut events,
        |e| matches!(e, Event::Connected),
        Duration::from_secs(10),
    )
    .await?;

    let cmd_tx = connector.command_tx();
    let mut session_ids = Vec::new();

    // Create multiple sessions
    for i in 0..3 {
        cmd_tx
            .send(ConnectorCommand::CreateSession {
                cwd: None,
                project_id: None,
                ownership: dirigent_protocol::SessionOwnership::internal(),
            })
            .await?;

        let session_id = match wait_for_event(
            &mut events,
            |e| matches!(e, Event::SessionCreated { .. }),
            Duration::from_secs(5),
        )
        .await?
        {
            Event::SessionCreated { session, .. } => {
                tracing::info!("✓ Created session {}: {}", i + 1, session.id);
                session.id
            }
            _ => anyhow::bail!("Expected SessionCreated"),
        };

        session_ids.push(session_id);
    }

    assert_eq!(session_ids.len(), 3, "Should create 3 sessions");

    // Send messages to all sessions concurrently
    for (i, session_id) in session_ids.iter().enumerate() {
        cmd_tx
            .send(ConnectorCommand::SendMessage {
                session_id: session_id.clone(),
                text: format!("Message to session {}", i + 1),
            })
            .await?;
    }

    // Wait for all messages to start
    let mut started_count = 0;
    for _ in 0..30 {
        if let Ok(result) = timeout(Duration::from_secs(1), events.recv()).await {
            if let Event::MessageStarted { .. } = result? {
                started_count += 1;
                if started_count == 3 {
                    break;
                }
            }
        }
    }

    assert_eq!(started_count, 3, "All messages should start");
    tracing::info!("✓ All messages started successfully");

    // Cleanup
    connector.stop();
    let _ = timeout(Duration::from_secs(5), task_handle).await?;

    Ok(())
}

// ============================================================================
// T008: Graceful Shutdown
// ============================================================================

#[tokio::test]
#[ignore = "Requires dirigate binary to be built"]
async fn test_t008_graceful_shutdown() -> anyhow::Result<()> {
    tracing_subscriber::fmt()
        .with_env_filter("debug")
        .with_test_writer()
        .try_init()
        .ok();

    let connector = create_stdio_connector();
    let mut events = connector.subscribe();
    let task_handle = connector.start_task().await;

    // Wait for connection
    wait_for_event(
        &mut events,
        |e| matches!(e, Event::Connected),
        Duration::from_secs(10),
    )
    .await?;

    tracing::info!("Connected, now shutting down gracefully");

    // Stop the connector
    connector.stop();

    // Task should complete without hanging
    let result = timeout(Duration::from_secs(5), task_handle).await;

    assert!(result.is_ok(), "Task should complete within timeout");
    tracing::info!("✓ Graceful shutdown completed");

    Ok(())
}
