// Tests for mecha10_cli::handlers::logs

use mecha10_cli::commands::{LogSource, LogsArgs};
use mecha10_cli::context::CliContextBuilder;
use mecha10_cli::handlers::logs::handle_logs;
use serial_test::serial;
use std::path::PathBuf;
use tempfile::TempDir;
use tokio::io::AsyncWriteExt;

#[test]
fn test_logs_args_construction() {
    let args = LogsArgs {
        node: Some("camera_driver".to_string()),
        follow: true,
        no_follow: false,
        lines: Some(100),
        filter: Some("ERROR".to_string()),
        source: Some(LogSource::Nodes),
        timestamps: true,
        level: Some("ERROR".to_string()),
    };

    assert_eq!(args.node, Some("camera_driver".to_string()));
    assert!(args.follow);
    assert!(!args.no_follow);
    assert_eq!(args.lines, Some(100));
    assert_eq!(args.filter, Some("ERROR".to_string()));
}

#[test]
fn test_logs_args_defaults() {
    let args = LogsArgs {
        node: None,
        follow: false,
        no_follow: false,
        lines: None,
        filter: None,
        source: None,
        timestamps: false,
        level: None,
    };

    assert!(args.node.is_none());
    assert!(!args.follow);
    assert!(!args.no_follow);
    assert!(args.lines.is_none());
    assert!(args.filter.is_none());
}

#[tokio::test]
#[serial]
async fn test_handle_logs_not_in_project() {
    use anyhow::Result;

    let temp_dir = TempDir::new().unwrap();
    std::env::set_var("PWD", temp_dir.path());

    let mut ctx = CliContextBuilder::new().build().unwrap();

    let args = LogsArgs {
        node: None,
        follow: false,
        no_follow: true,
        lines: Some(10),
        filter: None,
        source: None,
        timestamps: false,
        level: None,
    };

    let result = handle_logs(&mut ctx, &args).await;
    assert!(result.is_err());
    assert!(result.unwrap_err().to_string().contains("Not in a Mecha10 project"));

    std::env::remove_var("PWD");
}

#[tokio::test]
#[serial]
async fn test_handle_logs_empty_logs_dir() -> Result<(), Box<dyn std::error::Error>> {
    let temp_dir = TempDir::new()?;
    let project_root = temp_dir.path();

    // Create minimal project structure
    std::fs::create_dir_all(project_root.join("logs"))?;
    std::fs::write(
        project_root.join("mecha10.json"),
        r#"{"robot":{"id":"test","platform":"test"}}"#,
    )?;

    std::env::set_var("PWD", project_root);

    let mut ctx = CliContextBuilder::new().build()?;

    let args = LogsArgs {
        node: None,
        follow: false,
        no_follow: true,
        lines: Some(10),
        filter: None,
        source: None,
        timestamps: false,
        level: None,
    };

    // Should succeed but show "No log files found"
    let result = handle_logs(&mut ctx, &args).await;
    assert!(result.is_ok());

    std::env::remove_var("PWD");
    Ok(())
}

#[tokio::test]
#[serial]
async fn test_handle_logs_with_log_files() -> Result<(), Box<dyn std::error::Error>> {
    let temp_dir = TempDir::new()?;
    let project_root = temp_dir.path();

    // Create project structure with logs
    let logs_dir = project_root.join("logs");
    std::fs::create_dir_all(&logs_dir)?;
    std::fs::write(
        project_root.join("mecha10.json"),
        r#"{"robot":{"id":"test","platform":"test"}}"#,
    )?;

    // Create test log files
    let camera_log = logs_dir.join("camera_driver.log");
    let mut file = tokio::fs::File::create(&camera_log).await?;
    file.write_all(b"INFO: Camera started\nDEBUG: Frame captured\nERROR: Connection lost\n")
        .await?;
    file.flush().await?;
    drop(file);

    std::env::set_var("PWD", project_root);

    let mut ctx = CliContextBuilder::new().build()?;

    let args = LogsArgs {
        node: None,
        follow: false,
        no_follow: true,
        lines: Some(10),
        filter: None,
        source: None,
        timestamps: false,
        level: None,
    };

    // Should succeed and display logs
    let result = handle_logs(&mut ctx, &args).await;
    assert!(result.is_ok());

    std::env::remove_var("PWD");
    Ok(())
}

#[tokio::test]
#[serial]
async fn test_handle_logs_with_node_filter() -> Result<(), Box<dyn std::error::Error>> {
    let temp_dir = TempDir::new()?;
    let project_root = temp_dir.path();

    // Create project structure with logs
    let logs_dir = project_root.join("logs");
    std::fs::create_dir_all(&logs_dir)?;
    std::fs::write(
        project_root.join("mecha10.json"),
        r#"{"robot":{"id":"test","platform":"test"}}"#,
    )?;

    // Create multiple log files
    tokio::fs::write(logs_dir.join("camera_driver.log"), "Camera log\n").await?;
    tokio::fs::write(logs_dir.join("lidar_driver.log"), "Lidar log\n").await?;

    std::env::set_var("PWD", project_root);

    let mut ctx = CliContextBuilder::new().build()?;

    let args = LogsArgs {
        node: Some("camera".to_string()),
        follow: false,
        no_follow: true,
        lines: Some(10),
        filter: None,
        source: None,
        timestamps: false,
        level: None,
    };

    // Should succeed and only show camera logs
    let result = handle_logs(&mut ctx, &args).await;
    assert!(result.is_ok());

    std::env::remove_var("PWD");
    Ok(())
}

#[tokio::test]
#[serial]
async fn test_handle_logs_with_level_filter() -> Result<(), Box<dyn std::error::Error>> {
    let temp_dir = TempDir::new()?;
    let project_root = temp_dir.path();

    // Create project structure with logs
    let logs_dir = project_root.join("logs");
    std::fs::create_dir_all(&logs_dir)?;
    std::fs::write(
        project_root.join("mecha10.json"),
        r#"{"robot":{"id":"test","platform":"test"}}"#,
    )?;

    // Create log file with different levels
    let log_path = logs_dir.join("test.log");
    let mut file = tokio::fs::File::create(&log_path).await?;
    file.write_all(b"INFO: Starting up\nERROR: Failed to connect\nDEBUG: Debug message\n")
        .await?;
    file.flush().await?;
    drop(file);

    std::env::set_var("PWD", project_root);

    let mut ctx = CliContextBuilder::new().build()?;

    let args = LogsArgs {
        node: None,
        follow: false,
        no_follow: true,
        lines: Some(10),
        filter: None,
        source: None,
        timestamps: false,
        level: Some("ERROR".to_string()),
    };

    // Should succeed and only show ERROR logs
    let result = handle_logs(&mut ctx, &args).await;
    assert!(result.is_ok());

    std::env::remove_var("PWD");
    Ok(())
}
