//! Integration tests for log_service! macro.

use std::fs;

// ── Full declaration ──

mod full_service {
    mad_hatter::log_service! {
        TestLogService,
        tracing { time_format: "%Y-%m-%d %H:%M:%S", level: info, }
        crash { file: "crash.log", }
        dump beat_infer {
            dir: "infer",
            filename: "{timestamp}.{suffix}.log",
            retention_days: 7,
        }
        dump capture_infer {
            dir: "infer",
            filename: "{timestamp}.capture.{suffix}.log",
            retention_days: 7,
        }
        rotate engine_log {
            file: "engine.log",
            max_files: 2,
        }
    }
}

// ── Minimal declaration ──

mod minimal_service {
    mad_hatter::log_service!(MinimalLogService,);
}

// ── With named loggers ──

mod logger_service {
    mad_hatter::log_service! {
        LoggerLogService,
        tracing { time_format: "%Y-%m-%d %H:%M:%S", level: info, }
        crash { file: "crash.log", }
        loggers infer {
            dir: "infer",
            retention_days: 7,
            beat_out: "{timestamp}.out.log",
            beat_in: "{timestamp}.in.log",
            capture_out: "{timestamp}.out.capture.log",
        }
    }
}

// ── No dump, no rotate ──

mod bare_service {
    mad_hatter::log_service! {
        BareLogService,
        tracing { time_format: "%H:%M:%S", level: debug, }
        crash { file: "bare_crash.log", }
    }
}

// ── Tests ──

#[test]
fn test_full_declaration_compiles() {
    let _ = std::any::type_name::<full_service::TestLogService>();
}

#[test]
fn test_minimal_declaration_compiles() {
    let _ = std::any::type_name::<minimal_service::MinimalLogService>();
}

#[test]
fn test_logger_declaration_compiles() {
    let _ = std::any::type_name::<logger_service::LoggerLogService>();
}

#[test]
fn test_named_logger_dump() {
    let dir = tempfile::tempdir().unwrap();
    logger_service::LoggerLogService::init(dir.path());

    // Use named logger
    let ts = logger_service::infer::timestamp();
    let result = logger_service::infer::beat_out::dump_at("test output", &ts, None);
    assert!(result.is_some());
    let path = result.unwrap();
    assert!(path.exists());
    let content = std::fs::read_to_string(&path).unwrap();
    assert_eq!(content, "test output");

    // beat_in logger
    let result2 = logger_service::infer::beat_in::dump_at("test input", &ts, None);
    assert!(result2.is_some());
    let path2 = result2.unwrap();
    assert!(path2.exists());
    assert_ne!(path, path2); // different files

    // path() function
    let p = logger_service::infer::capture_out::path(&ts, None);
    assert!(p.is_some());
}

#[test]
fn test_bare_declaration_compiles() {
    let _ = std::any::type_name::<bare_service::BareLogService>();
}

#[test]
fn test_dump_write_via_runtime() {
    let dir = tempfile::tempdir().unwrap();
    let result = mad_hatter::log_service::dump_write(
        dir.path(),
        "infer",
        "{timestamp}.{suffix}.log",
        "test content",
        "in",
        None,
        None,
    );
    assert!(result.is_some());
    let path = result.unwrap();
    assert!(path.exists());
    assert_eq!(fs::read_to_string(&path).unwrap(), "test content");
}

#[test]
fn test_rotate_via_runtime() {
    let dir = tempfile::tempdir().unwrap();
    let log = dir.path().join("engine.log");
    fs::write(&log, "v0").unwrap();

    mad_hatter::log_service::rotate_file(dir.path(), "engine.log", 2);

    assert!(!log.exists());
    assert_eq!(
        fs::read_to_string(dir.path().join("engine.log.1")).unwrap(),
        "v0"
    );
}

#[test]
fn test_clean_via_runtime() {
    let dir = tempfile::tempdir().unwrap();
    let dump_dir = dir.path().join("infer");
    fs::create_dir_all(&dump_dir).unwrap();
    fs::write(dump_dir.join("test.log"), "data").unwrap();

    mad_hatter::log_service::clean_dump_dir(dir.path(), "infer", 9999);
    assert!(dump_dir.join("test.log").exists());
}

#[test]
fn test_multiple_dump_loggers() {
    let dir = tempfile::tempdir().unwrap();

    let r1 = mad_hatter::log_service::dump_write(
        dir.path(), "infer", "{timestamp}.{suffix}.log", "beat input", "in",
        None, None,
    );
    let r2 = mad_hatter::log_service::dump_write(
        dir.path(), "infer", "{timestamp}.capture.{suffix}.log", "capture input", "in",
        None, None,
    );

    assert!(r1.is_some());
    assert!(r2.is_some());

    let n1 = r1.unwrap().file_name().unwrap().to_str().unwrap().to_string();
    let n2 = r2.unwrap().file_name().unwrap().to_str().unwrap().to_string();
    assert!(n1.ends_with(".in.log"));
    assert!(n2.contains(".capture.") && n2.ends_with(".in.log"));
}

#[test]
fn test_dump_write_with_shared_timestamp() {
    let dir = tempfile::tempdir().unwrap();
    let ts = mad_hatter::log_service::make_timestamp();
    let p1 = mad_hatter::log_service::dump_write(
        dir.path(), "infer", "{timestamp}.{suffix}.log", "output", "out",
        None, Some(&ts),
    );
    let p2 = mad_hatter::log_service::dump_write(
        dir.path(), "infer", "{timestamp}.{suffix}.log", "input", "in",
        None, Some(&ts),
    );

    let path1 = p1.unwrap();
    let path2 = p2.unwrap();
    let name1 = path1.file_name().unwrap().to_str().unwrap().to_owned();
    let name2 = path2.file_name().unwrap().to_str().unwrap().to_owned();
    assert!(name1.contains(&ts));
    assert!(name2.contains(&ts));
    assert!(name1.ends_with(".out.log"));
    assert!(name2.ends_with(".in.log"));
    assert_eq!(fs::read_to_string(&path1).unwrap(), "output");
    assert_eq!(fs::read_to_string(&path2).unwrap(), "input");
}

#[test]
fn test_dump_write_with_sub_dir() {
    let dir = tempfile::tempdir().unwrap();
    let ts = mad_hatter::log_service::make_timestamp();
    let p = mad_hatter::log_service::dump_write(
        dir.path(), "infer", "{timestamp}.{suffix}.log", "content", "out",
        Some("instance_abc"), Some(&ts),
    );
    let path = p.unwrap();
    assert!(path.exists());
    assert!(path.parent().unwrap().ends_with("infer/instance_abc"));
    assert_eq!(fs::read_to_string(&path).unwrap(), "content");
}

#[test]
fn test_mad_expect_option() {
    let x: Option<i32> = Some(42);
    let v = mad_hatter::mad_expect!(x, "should have value");
    assert_eq!(v, 42);
}

#[test]
fn test_mad_expect_result() {
    let x: Result<i32, &str> = Ok(42);
    let v = mad_hatter::mad_expect!(x, "should be ok");
    assert_eq!(v, 42);
}
