use std::{
    sync::{
        atomic::{AtomicUsize, Ordering},
        Arc,
    },
    time::Duration,
};

use tokio::sync::Barrier;
use tokio_util::sync::CancellationToken;

use crate::{process::ProcessState, BoxError, Process, ProcessHandler, ProcessManager, ProcessResult};

// ============================================================================
// Test Helpers
// ============================================================================

/// A process that runs until cancelled
struct LongRunningProcess {
    started: Arc<AtomicUsize>,
    stopped: Arc<AtomicUsize>,
}

impl LongRunningProcess {
    fn new() -> (Self, Arc<AtomicUsize>, Arc<AtomicUsize>) {
        let started = Arc::new(AtomicUsize::new(0));
        let stopped = Arc::new(AtomicUsize::new(0));
        (
            Self {
                started: started.clone(),
                stopped: stopped.clone(),
            },
            started,
            stopped,
        )
    }
}

impl ProcessHandler for LongRunningProcess {
    fn call(
        &self,
        cancellation: CancellationToken,
    ) -> impl std::future::Future<Output = ProcessResult> + Send {
        let started = self.started.clone();
        let stopped = self.stopped.clone();
        async move {
            started.fetch_add(1, Ordering::SeqCst);
            cancellation.cancelled().await;
            stopped.fetch_add(1, Ordering::SeqCst);
            Ok(())
        }
    }
}

/// A process that returns an error
struct ErroringProcess;

/// Simple error type for testing
#[derive(Debug)]
struct TestError(String);

impl std::fmt::Display for TestError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl std::error::Error for TestError {}

impl ProcessHandler for ErroringProcess {
    fn call(
        &self,
        _cancellation: CancellationToken,
    ) -> impl std::future::Future<Output = ProcessResult> + Send {
        async move { Err(Box::new(TestError("intentional error".to_string())) as BoxError) }
    }
}

/// A process that panics
struct PanickingProcess;

impl ProcessHandler for PanickingProcess {
    fn call(
        &self,
        _cancellation: CancellationToken,
    ) -> impl std::future::Future<Output = ProcessResult> + Send {
        async move { panic!("intentional panic") }
    }
}

/// A process that ignores cancellation (stubborn)
struct StubbornProcess {
    started: Arc<AtomicUsize>,
}

impl StubbornProcess {
    fn new() -> (Self, Arc<AtomicUsize>) {
        let started = Arc::new(AtomicUsize::new(0));
        (
            Self {
                started: started.clone(),
            },
            started,
        )
    }
}

impl ProcessHandler for StubbornProcess {
    fn call(
        &self,
        _cancellation: CancellationToken,
    ) -> impl std::future::Future<Output = ProcessResult> + Send {
        let started = self.started.clone();
        async move {
            started.fetch_add(1, Ordering::SeqCst);
            // Never exits, ignores cancellation
            std::future::pending::<()>().await;
            Ok(())
        }
    }
}

/// A process that waits for a barrier before proceeding
struct BarrierProcess {
    barrier: Arc<Barrier>,
    completed: Arc<AtomicUsize>,
}

impl BarrierProcess {
    fn new(barrier: Arc<Barrier>) -> (Self, Arc<AtomicUsize>) {
        let completed = Arc::new(AtomicUsize::new(0));
        (
            Self {
                barrier,
                completed: completed.clone(),
            },
            completed,
        )
    }
}

impl ProcessHandler for BarrierProcess {
    fn call(
        &self,
        cancellation: CancellationToken,
    ) -> impl std::future::Future<Output = ProcessResult> + Send {
        let barrier = self.barrier.clone();
        let completed = self.completed.clone();
        async move {
            barrier.wait().await;
            tokio::select! {
                _ = cancellation.cancelled() => {}
                _ = tokio::time::sleep(Duration::from_secs(60)) => {}
            }
            completed.fetch_add(1, Ordering::SeqCst);
            Ok(())
        }
    }
}

// ============================================================================
// Basic Operation Tests
// ============================================================================

mod basic_operations {
    use super::*;

    #[tokio::test]
    async fn test_start_process() {
        let (process, started, _stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        assert_eq!(started.load(Ordering::SeqCst), 0);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;

        assert_eq!(started.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_stop_process() {
        let (process, started, stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;
        assert_eq!(started.load(Ordering::SeqCst), 1);
        assert_eq!(stopped.load(Ordering::SeqCst), 0);

        proc.stop().await.unwrap();
        proc.wait_for(ProcessState::Stopped).await;

        assert_eq!(stopped.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_kill_process() {
        let (process, started, _stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;
        assert_eq!(started.load(Ordering::SeqCst), 1);

        proc.kill().await.unwrap();
        proc.wait_for(ProcessState::Stopped).await;
    }

    #[tokio::test]
    async fn test_restart_process() {
        let (process, started, stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;
        assert_eq!(started.load(Ordering::SeqCst), 1);

        proc.restart().await.unwrap();
        proc.wait_for(ProcessState::Running).await;

        // Give a moment for the new process to start
        tokio::time::sleep(Duration::from_millis(10)).await;

        assert_eq!(started.load(Ordering::SeqCst), 2);
        assert_eq!(stopped.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_process_with_custom_handle_id() {
        let (process, _started, _stopped) = LongRunningProcess::new();
        let proc = Process::new_with_handle("my-custom-id", process);

        assert_eq!(proc.handle_id().to_string(), "my-custom-id");
    }

    #[tokio::test]
    async fn test_process_builder() {
        let (process, started, stopped) = LongRunningProcess::new();
        let mut proc = Process::builder(process)
            .handle_id("builder-test")
            .build();

        assert_eq!(proc.handle_id().to_string(), "builder-test");

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;
        assert_eq!(started.load(Ordering::SeqCst), 1);

        proc.stop().await.unwrap();
        proc.wait_for(ProcessState::Stopped).await;
        assert_eq!(stopped.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_process_builder_with_shutdown_config() {
        use crate::ShutdownConfig;

        let (process, started) = StubbornProcess::new();
        let mut proc = Process::builder(process)
            .shutdown_config(ShutdownConfig {
                graceful_timeout: Duration::from_millis(100),
                restart_on_success: false,
                restart_delay: Duration::ZERO,
            })
            .build();

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;
        assert_eq!(started.load(Ordering::SeqCst), 1);

        // Stop should timeout quickly (100ms) and force kill
        let start_time = std::time::Instant::now();
        proc.stop().await.unwrap();
        proc.wait_for(ProcessState::Stopped).await;

        let elapsed = start_time.elapsed();
        // Should be around 100ms, not the default 5s
        assert!(elapsed < Duration::from_secs(1));
    }

    #[tokio::test]
    async fn test_add_process_returns_handle_id() {
        let manager = ProcessManager::new();
        let (process, _started, _stopped) = LongRunningProcess::new();

        let handle_id = manager.add_process(Process::new(process)).await;

        // Can immediately use the returned handle_id
        manager.start_process(&handle_id).await.unwrap();
    }
}

// ============================================================================
// Edge Case Tests
// ============================================================================

mod edge_cases {
    use super::*;

    #[tokio::test]
    async fn test_start_already_running_process() {
        let (process, started, _stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;

        // Second start should be ignored
        proc.start().await.unwrap();
        tokio::time::sleep(Duration::from_millis(50)).await;

        assert_eq!(started.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_stop_not_running_process() {
        let (process, _started, _stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        // Stop without starting should succeed
        proc.stop().await.unwrap();
    }

    #[tokio::test]
    async fn test_kill_not_running_process() {
        let (process, _started, _stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        // Kill without starting should succeed
        proc.kill().await.unwrap();
    }

    #[tokio::test]
    async fn test_stop_already_stopped_process() {
        let (process, _started, stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;

        proc.stop().await.unwrap();
        proc.wait_for(ProcessState::Stopped).await;

        // Second stop should succeed
        proc.stop().await.unwrap();

        assert_eq!(stopped.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_restart_not_running_process() {
        let (process, started, _stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        // Restart without starting should work (stop is no-op, start succeeds)
        proc.restart().await.unwrap();
        proc.wait_for(ProcessState::Running).await;

        assert_eq!(started.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_process_that_errors() {
        let mut proc = Process::new(ErroringProcess);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Errored).await;
    }

    #[tokio::test]
    async fn test_process_that_panics() {
        let mut proc = Process::new(PanickingProcess);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Errored).await;
    }

    #[tokio::test]
    async fn test_start_after_error() {
        let (process, started, _stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        // We can't easily make it error then recover, so test start after error state
        // by using the process manager approach instead
        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;

        assert_eq!(started.load(Ordering::SeqCst), 1);
    }
}

// ============================================================================
// Graceful Shutdown Tests
// ============================================================================

mod graceful_shutdown {
    use super::*;

    #[tokio::test]
    async fn test_stubborn_process_is_killed_after_timeout() {
        let (process, started) = StubbornProcess::new();
        let mut proc = Process::new(process);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;
        assert_eq!(started.load(Ordering::SeqCst), 1);

        // Stop will timeout and force kill
        let start = std::time::Instant::now();
        proc.stop().await.unwrap();
        proc.wait_for(ProcessState::Stopped).await;

        // Should have waited for graceful timeout (default 5s)
        // but we can't easily test that without changing the config
        // Just verify it completed
        let elapsed = start.elapsed();
        assert!(elapsed >= Duration::from_secs(4)); // Allow some tolerance
    }

    #[tokio::test]
    async fn test_kill_is_immediate_for_stubborn_process() {
        let (process, started) = StubbornProcess::new();
        let mut proc = Process::new(process);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;
        assert_eq!(started.load(Ordering::SeqCst), 1);

        // Kill should be immediate
        let start = std::time::Instant::now();
        proc.kill().await.unwrap();
        proc.wait_for(ProcessState::Stopped).await;

        let elapsed = start.elapsed();
        assert!(elapsed < Duration::from_secs(1));
    }
}

// ============================================================================
// Process Manager Tests
// ============================================================================

mod process_manager {
    use super::*;

    #[tokio::test]
    async fn test_manager_add_and_start_process() {
        let manager = ProcessManager::new();
        let (process, started, _stopped) = LongRunningProcess::new();
        let proc = Process::new(process);
        let handle_id = proc.handle_id().clone();

        manager.add_process(proc).await;
        manager.start_process(&handle_id).await.unwrap();

        tokio::time::sleep(Duration::from_millis(50)).await;
        assert_eq!(started.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_manager_stop_process() {
        let manager = ProcessManager::new();
        let (process, _started, stopped) = LongRunningProcess::new();
        let proc = Process::new(process);
        let handle_id = proc.handle_id().clone();

        manager.add_process(proc).await;
        manager.start_process(&handle_id).await.unwrap();
        tokio::time::sleep(Duration::from_millis(50)).await;

        manager.stop_process(&handle_id).await.unwrap();

        assert_eq!(stopped.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_manager_multiple_processes() {
        let manager = ProcessManager::new();

        let (process1, started1, _) = LongRunningProcess::new();
        let (process2, started2, _) = LongRunningProcess::new();
        let (process3, started3, _) = LongRunningProcess::new();

        let proc1 = Process::new(process1);
        let proc2 = Process::new(process2);
        let proc3 = Process::new(process3);

        let id1 = proc1.handle_id().clone();
        let id2 = proc2.handle_id().clone();
        let id3 = proc3.handle_id().clone();

        manager.add_process(proc1).await;
        manager.add_process(proc2).await;
        manager.add_process(proc3).await;

        manager.start_process(&id1).await.unwrap();
        manager.start_process(&id2).await.unwrap();
        manager.start_process(&id3).await.unwrap();

        tokio::time::sleep(Duration::from_millis(50)).await;

        assert_eq!(started1.load(Ordering::SeqCst), 1);
        assert_eq!(started2.load(Ordering::SeqCst), 1);
        assert_eq!(started3.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_manager_process_not_found() {
        let manager = ProcessManager::new();
        let nonexistent_id = "nonexistent".into();

        let result = manager.start_process(&nonexistent_id).await.unwrap();
        assert!(result.is_none());
    }

    #[tokio::test]
    async fn test_manager_restart_process() {
        let manager = ProcessManager::new();
        let (process, started, stopped) = LongRunningProcess::new();
        let proc = Process::new(process);
        let handle_id = proc.handle_id().clone();

        manager.add_process(proc).await;
        manager.start_process(&handle_id).await.unwrap();
        tokio::time::sleep(Duration::from_millis(50)).await;

        manager.restart_process(&handle_id).await.unwrap();
        tokio::time::sleep(Duration::from_millis(50)).await;

        assert_eq!(started.load(Ordering::SeqCst), 2);
        assert_eq!(stopped.load(Ordering::SeqCst), 1);
    }
}

// ============================================================================
// Concurrency Tests
// ============================================================================

mod concurrency {
    use super::*;

    #[tokio::test]
    async fn test_sequential_start_commands() {
        let (process, started, _stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        // Send multiple start commands sequentially
        proc.start().await.unwrap();
        proc.start().await.unwrap();
        proc.start().await.unwrap();

        tokio::time::sleep(Duration::from_millis(50)).await;

        // Only one should have started
        assert_eq!(started.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_sequential_stop_commands() {
        let (process, _started, stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;

        // Send multiple stop commands sequentially
        proc.stop().await.unwrap();
        proc.stop().await.unwrap();
        proc.stop().await.unwrap();

        // Only one should have stopped
        assert_eq!(stopped.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_rapid_start_stop_cycles() {
        let (process, started, stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        for _ in 0..5 {
            proc.start().await.unwrap();
            proc.wait_for(ProcessState::Running).await;
            proc.stop().await.unwrap();
            proc.wait_for(ProcessState::Stopped).await;
        }

        assert_eq!(started.load(Ordering::SeqCst), 5);
        assert_eq!(stopped.load(Ordering::SeqCst), 5);
    }

    #[tokio::test]
    async fn test_manager_concurrent_operations_different_processes() {
        let manager = Arc::new(ProcessManager::new());

        let (process1, started1, _) = LongRunningProcess::new();
        let (process2, started2, _) = LongRunningProcess::new();

        let proc1 = Process::new(process1);
        let proc2 = Process::new(process2);

        let id1 = proc1.handle_id().clone();
        let id2 = proc2.handle_id().clone();

        manager.add_process(proc1).await;
        manager.add_process(proc2).await;

        // Start both processes concurrently
        let manager1 = manager.clone();
        let manager2 = manager.clone();
        let id1_clone = id1.clone();
        let id2_clone = id2.clone();

        let (r1, r2) = tokio::join!(
            async move { manager1.start_process(&id1_clone).await },
            async move { manager2.start_process(&id2_clone).await },
        );

        r1.unwrap();
        r2.unwrap();

        tokio::time::sleep(Duration::from_millis(50)).await;

        assert_eq!(started1.load(Ordering::SeqCst), 1);
        assert_eq!(started2.load(Ordering::SeqCst), 1);
    }

    #[tokio::test]
    async fn test_barrier_synchronized_processes() {
        let barrier = Arc::new(Barrier::new(3));

        let (process1, completed1) = BarrierProcess::new(barrier.clone());
        let (process2, completed2) = BarrierProcess::new(barrier.clone());

        let mut proc1 = Process::new(process1);
        let mut proc2 = Process::new(process2);

        proc1.start().await.unwrap();
        proc2.start().await.unwrap();

        // Wait at the barrier (the third waiter)
        barrier.wait().await;

        // Both processes should now be past the barrier
        tokio::time::sleep(Duration::from_millis(50)).await;

        proc1.stop().await.unwrap();
        proc2.stop().await.unwrap();

        assert_eq!(completed1.load(Ordering::SeqCst), 1);
        assert_eq!(completed2.load(Ordering::SeqCst), 1);
    }
}

// ============================================================================
// Parametric Tests
// ============================================================================

mod parametric {
    use super::*;
    use test_case::test_case;

    #[test_case(1; "single start")]
    #[test_case(5; "five starts")]
    #[test_case(10; "ten starts")]
    #[tokio::test]
    async fn test_multiple_start_attempts(attempts: usize) {
        let (process, started, _stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        for _ in 0..attempts {
            proc.start().await.unwrap();
        }

        proc.wait_for(ProcessState::Running).await;
        tokio::time::sleep(Duration::from_millis(50)).await;

        // Only one should have actually started
        assert_eq!(started.load(Ordering::SeqCst), 1);
    }

    #[test_case(1; "single cycle")]
    #[test_case(3; "three cycles")]
    #[test_case(5; "five cycles")]
    #[tokio::test]
    async fn test_start_stop_cycles(cycles: usize) {
        let (process, started, stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        for _ in 0..cycles {
            proc.start().await.unwrap();
            proc.wait_for(ProcessState::Running).await;
            proc.stop().await.unwrap();
            proc.wait_for(ProcessState::Stopped).await;
        }

        assert_eq!(started.load(Ordering::SeqCst), cycles);
        assert_eq!(stopped.load(Ordering::SeqCst), cycles);
    }

    #[test_case(Duration::from_millis(10); "10ms delay")]
    #[test_case(Duration::from_millis(50); "50ms delay")]
    #[test_case(Duration::from_millis(100); "100ms delay")]
    #[tokio::test]
    async fn test_delayed_stop(delay: Duration) {
        let (process, started, stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;

        tokio::time::sleep(delay).await;

        proc.stop().await.unwrap();
        proc.wait_for(ProcessState::Stopped).await;

        assert_eq!(started.load(Ordering::SeqCst), 1);
        assert_eq!(stopped.load(Ordering::SeqCst), 1);
    }
}

// ============================================================================
// Drop Behavior Tests
// ============================================================================

mod drop_behavior {
    use super::*;

    #[tokio::test]
    async fn test_process_dropped_while_running() {
        let (process, started, _stopped) = LongRunningProcess::new();
        let mut proc = Process::new(process);

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;
        assert_eq!(started.load(Ordering::SeqCst), 1);

        // Drop the process
        drop(proc);

        // The task should be aborted
        tokio::time::sleep(Duration::from_millis(50)).await;
    }

    #[tokio::test]
    async fn test_process_dropped_before_start() {
        let (process, started, _stopped) = LongRunningProcess::new();
        let proc = Process::new(process);

        // Drop without starting
        drop(proc);

        assert_eq!(started.load(Ordering::SeqCst), 0);
    }
}

// ============================================================================
// Error Handler Tests
// ============================================================================

mod error_handler {
    use super::*;
    use crate::{ProcessFailure, ProcessFailureKind, ShutdownConfig};
    use std::sync::Mutex;

    #[tokio::test]
    async fn test_on_error_callback_for_returned_error() {
        let errors: Arc<Mutex<Vec<ProcessFailure>>> = Arc::new(Mutex::new(Vec::new()));
        let errors_clone = errors.clone();

        let mut proc = Process::builder(ErroringProcess)
            .handle_id("error-test")
            .shutdown_config(ShutdownConfig {
                graceful_timeout: Duration::from_secs(1),
                restart_on_success: false,
                restart_delay: Duration::ZERO,
            })
            .on_error(move |e| {
                errors_clone.lock().unwrap().push(e);
            })
            .build();

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Errored).await;

        let errors = errors.lock().unwrap();
        assert_eq!(errors.len(), 1);
        assert_eq!(errors[0].handle_id.to_string(), "error-test");
        assert!(matches!(errors[0].kind, ProcessFailureKind::Returned(_)));
    }

    #[tokio::test]
    async fn test_on_error_callback_for_panic() {
        let errors: Arc<Mutex<Vec<ProcessFailure>>> = Arc::new(Mutex::new(Vec::new()));
        let errors_clone = errors.clone();

        let mut proc = Process::builder(PanickingProcess)
            .handle_id("panic-test")
            .shutdown_config(ShutdownConfig {
                graceful_timeout: Duration::from_secs(1),
                restart_on_success: false,
                restart_delay: Duration::ZERO,
            })
            .on_error(move |e| {
                errors_clone.lock().unwrap().push(e);
            })
            .build();

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Errored).await;

        let errors = errors.lock().unwrap();
        assert_eq!(errors.len(), 1);
        assert_eq!(errors[0].handle_id.to_string(), "panic-test");
        assert!(matches!(errors[0].kind, ProcessFailureKind::Panicked(_)));
    }

    #[tokio::test]
    async fn test_on_error_not_called_for_successful_process() {
        let errors: Arc<Mutex<Vec<ProcessFailure>>> = Arc::new(Mutex::new(Vec::new()));
        let errors_clone = errors.clone();

        let (process, _started, _stopped) = LongRunningProcess::new();
        let mut proc = Process::builder(process)
            .shutdown_config(ShutdownConfig {
                graceful_timeout: Duration::from_secs(1),
                restart_on_success: false,
                restart_delay: Duration::ZERO,
            })
            .on_error(move |e| {
                errors_clone.lock().unwrap().push(e);
            })
            .build();

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;
        proc.stop().await.unwrap();
        proc.wait_for(ProcessState::Stopped).await;

        let errors = errors.lock().unwrap();
        assert_eq!(errors.len(), 0);
    }

    #[tokio::test]
    async fn test_process_failure_display() {
        let error = ProcessFailure {
            handle_id: "test-id".into(),
            kind: ProcessFailureKind::Returned("something went wrong".to_string()),
        };
        assert_eq!(
            error.to_string(),
            "process test-id returned error: something went wrong"
        );

        let error = ProcessFailure {
            handle_id: "test-id".into(),
            kind: ProcessFailureKind::Panicked("panic message".to_string()),
        };
        assert_eq!(error.to_string(), "process test-id panicked: panic message");
    }
}

// ============================================================================
// Tracing Tests
// ============================================================================

mod tracing_tests {
    use super::*;
    use crate::ShutdownConfig;
    use tracing_test::traced_test;

    #[tokio::test]
    #[traced_test]
    async fn test_error_is_logged_when_process_returns_error() {
        let mut proc = Process::builder(ErroringProcess)
            .handle_id("traced-error-test")
            .shutdown_config(ShutdownConfig {
                graceful_timeout: Duration::from_secs(1),
                restart_on_success: false,
                restart_delay: Duration::ZERO,
            })
            .build();

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Errored).await;

        assert!(logs_contain("process returned error"));
        assert!(logs_contain("intentional error"));
    }

    #[tokio::test]
    #[traced_test]
    async fn test_panic_is_logged_when_process_panics() {
        let mut proc = Process::builder(PanickingProcess)
            .handle_id("traced-panic-test")
            .shutdown_config(ShutdownConfig {
                graceful_timeout: Duration::from_secs(1),
                restart_on_success: false,
                restart_delay: Duration::ZERO,
            })
            .build();

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Errored).await;

        assert!(logs_contain("process panicked"));
    }

    #[tokio::test]
    #[traced_test]
    async fn test_graceful_shutdown_timeout_is_logged() {
        let (process, _started) = StubbornProcess::new();
        let mut proc = Process::builder(process)
            .handle_id("traced-timeout-test")
            .shutdown_config(ShutdownConfig {
                graceful_timeout: Duration::from_millis(100),
                restart_on_success: false,
                restart_delay: Duration::ZERO,
            })
            .build();

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;

        proc.stop().await.unwrap();
        proc.wait_for(ProcessState::Stopped).await;

        assert!(logs_contain("graceful shutdown timed out"));
    }

    #[tokio::test]
    #[traced_test]
    async fn test_start_stop_logging() {
        let (process, _started, _stopped) = LongRunningProcess::new();
        let mut proc = Process::builder(process)
            .handle_id("traced-lifecycle-test")
            .shutdown_config(ShutdownConfig {
                graceful_timeout: Duration::from_secs(1),
                restart_on_success: false,
                restart_delay: Duration::ZERO,
            })
            .build();

        proc.start().await.unwrap();
        proc.wait_for(ProcessState::Running).await;

        assert!(logs_contain("starting process"));
        assert!(logs_contain("traced-lifecycle-test"));

        proc.stop().await.unwrap();
        proc.wait_for(ProcessState::Stopped).await;

        assert!(logs_contain("stopping process"));
    }
}
