//! Tests for the agent_log module.
use super::*;
use crate::agents::AgentEvent;
use tempfile::tempdir;

#[test]
fn test_log_writer_creates_directory_and_file() {
    let tmp = tempdir().unwrap();
    let root = tmp.path();

    let _writer = AgentLogWriter::new(root, "42_story_foo", "coder-1", "sess-abc123").unwrap();

    let expected_path = root
        .join(".huskies")
        .join("logs")
        .join("42_story_foo")
        .join("coder-1-sess-abc123.log");
    assert!(expected_path.exists(), "Log file should exist");
}

#[test]
fn numeric_id_log_dir_uses_number_only() {
    let tmp = tempdir().unwrap();
    let root = tmp.path();

    let _writer = AgentLogWriter::new(root, "664", "coder-1", "sess-xyz").unwrap();

    let expected_path = root
        .join(".huskies")
        .join("logs")
        .join("664")
        .join("coder-1-sess-xyz.log");
    assert!(
        expected_path.exists(),
        "Log file for numeric ID should be at .huskies/logs/664/..."
    );
}

#[test]
fn test_log_writer_writes_jsonl_with_timestamps() {
    let tmp = tempdir().unwrap();
    let root = tmp.path();

    let mut writer = AgentLogWriter::new(root, "42_story_foo", "coder-1", "sess-001").unwrap();

    let event = AgentEvent::Status {
        story_id: "42_story_foo".to_string(),
        agent_name: "coder-1".to_string(),
        status: "running".to_string(),
    };
    writer.write_event(&event).unwrap();

    let event2 = AgentEvent::Output {
        story_id: "42_story_foo".to_string(),
        agent_name: "coder-1".to_string(),
        text: "Hello world".to_string(),
    };
    writer.write_event(&event2).unwrap();

    // Read the file and verify
    let path = log_file_path(root, "42_story_foo", "coder-1", "sess-001");
    let content = std::fs::read_to_string(&path).unwrap();
    let lines: Vec<&str> = content.lines().collect();
    assert_eq!(lines.len(), 2, "Should have 2 log lines");

    // Parse each line as valid JSON with a timestamp
    for line in &lines {
        let entry: LogEntry = serde_json::from_str(line).unwrap();
        assert!(!entry.timestamp.is_empty(), "Timestamp should be present");
        // Verify it's a valid ISO 8601 timestamp
        chrono::DateTime::parse_from_rfc3339(&entry.timestamp)
            .expect("Timestamp should be valid RFC3339");
    }

    // Verify the first entry is a status event
    let entry1: LogEntry = serde_json::from_str(lines[0]).unwrap();
    assert_eq!(entry1.event["type"], "status");
    assert_eq!(entry1.event["status"], "running");

    // Verify the second entry is an output event
    let entry2: LogEntry = serde_json::from_str(lines[1]).unwrap();
    assert_eq!(entry2.event["type"], "output");
    assert_eq!(entry2.event["text"], "Hello world");
}

#[test]
fn test_read_log_parses_written_events() {
    let tmp = tempdir().unwrap();
    let root = tmp.path();

    let mut writer = AgentLogWriter::new(root, "42_story_foo", "coder-1", "sess-002").unwrap();

    let events = vec![
        AgentEvent::Status {
            story_id: "42_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            status: "running".to_string(),
        },
        AgentEvent::Output {
            story_id: "42_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            text: "Processing...".to_string(),
        },
        AgentEvent::AgentJson {
            story_id: "42_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            data: serde_json::json!({"type": "tool_use", "name": "read_file"}),
        },
        AgentEvent::Done {
            story_id: "42_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            session_id: Some("sess-002".to_string()),
        },
    ];

    for event in &events {
        writer.write_event(event).unwrap();
    }

    let path = log_file_path(root, "42_story_foo", "coder-1", "sess-002");
    let entries = read_log(&path).unwrap();
    assert_eq!(entries.len(), 4, "Should read back all 4 events");

    // Verify event types round-trip correctly
    assert_eq!(entries[0].event["type"], "status");
    assert_eq!(entries[1].event["type"], "output");
    assert_eq!(entries[2].event["type"], "agent_json");
    assert_eq!(entries[3].event["type"], "done");
}

#[test]
fn test_separate_sessions_produce_separate_files() {
    let tmp = tempdir().unwrap();
    let root = tmp.path();

    let mut writer1 = AgentLogWriter::new(root, "42_story_foo", "coder-1", "sess-aaa").unwrap();
    let mut writer2 = AgentLogWriter::new(root, "42_story_foo", "coder-1", "sess-bbb").unwrap();

    writer1
        .write_event(&AgentEvent::Output {
            story_id: "42_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            text: "from session aaa".to_string(),
        })
        .unwrap();

    writer2
        .write_event(&AgentEvent::Output {
            story_id: "42_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            text: "from session bbb".to_string(),
        })
        .unwrap();

    let path1 = log_file_path(root, "42_story_foo", "coder-1", "sess-aaa");
    let path2 = log_file_path(root, "42_story_foo", "coder-1", "sess-bbb");

    assert_ne!(
        path1, path2,
        "Different sessions should use different files"
    );

    let entries1 = read_log(&path1).unwrap();
    let entries2 = read_log(&path2).unwrap();

    assert_eq!(entries1.len(), 1);
    assert_eq!(entries2.len(), 1);
    assert_eq!(entries1[0].event["text"], "from session aaa");
    assert_eq!(entries2[0].event["text"], "from session bbb");
}

#[test]
fn test_find_latest_log_returns_most_recent() {
    let tmp = tempdir().unwrap();
    let root = tmp.path();

    // Create two log files with a small delay
    let mut writer1 = AgentLogWriter::new(root, "42_story_foo", "coder-1", "sess-old").unwrap();
    writer1
        .write_event(&AgentEvent::Output {
            story_id: "42_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            text: "old".to_string(),
        })
        .unwrap();
    drop(writer1);

    // Touch the second file to ensure it's newer
    std::thread::sleep(std::time::Duration::from_millis(50));

    let mut writer2 = AgentLogWriter::new(root, "42_story_foo", "coder-1", "sess-new").unwrap();
    writer2
        .write_event(&AgentEvent::Output {
            story_id: "42_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            text: "new".to_string(),
        })
        .unwrap();
    drop(writer2);

    let latest = find_latest_log(root, "42_story_foo", "coder-1").unwrap();
    assert!(
        latest.to_string_lossy().contains("sess-new"),
        "Should find the newest log file, got: {}",
        latest.display()
    );
}

#[test]
fn test_list_story_log_files_returns_empty_for_missing_dir() {
    let tmp = tempdir().unwrap();
    let files = list_story_log_files(tmp.path(), "nonexistent", None);
    assert!(files.is_empty());
}

#[test]
fn test_list_story_log_files_no_filter_returns_all_logs() {
    let tmp = tempdir().unwrap();
    let root = tmp.path();

    let mut w1 = AgentLogWriter::new(root, "42_story_foo", "coder-1", "sess-aaa").unwrap();
    w1.write_event(&AgentEvent::Output {
        story_id: "42_story_foo".to_string(),
        agent_name: "coder-1".to_string(),
        text: "from coder-1".to_string(),
    })
    .unwrap();
    drop(w1);
    std::thread::sleep(std::time::Duration::from_millis(10));

    let mut w2 = AgentLogWriter::new(root, "42_story_foo", "mergemaster", "sess-bbb").unwrap();
    w2.write_event(&AgentEvent::Output {
        story_id: "42_story_foo".to_string(),
        agent_name: "mergemaster".to_string(),
        text: "from mergemaster".to_string(),
    })
    .unwrap();
    drop(w2);

    let files = list_story_log_files(root, "42_story_foo", None);
    assert_eq!(files.len(), 2, "Should find both log files");
    // Oldest first
    assert!(
        files[0].to_string_lossy().contains("coder-1"),
        "coder-1 should be first (older)"
    );
}

#[test]
fn test_list_story_log_files_with_agent_filter() {
    let tmp = tempdir().unwrap();
    let root = tmp.path();

    let mut w1 = AgentLogWriter::new(root, "42_story_foo", "coder-1", "sess-a").unwrap();
    w1.write_event(&AgentEvent::Output {
        story_id: "42_story_foo".to_string(),
        agent_name: "coder-1".to_string(),
        text: "from coder-1".to_string(),
    })
    .unwrap();
    drop(w1);

    let mut w2 = AgentLogWriter::new(root, "42_story_foo", "mergemaster", "sess-b").unwrap();
    w2.write_event(&AgentEvent::Output {
        story_id: "42_story_foo".to_string(),
        agent_name: "mergemaster".to_string(),
        text: "from mergemaster".to_string(),
    })
    .unwrap();
    drop(w2);

    let files = list_story_log_files(root, "42_story_foo", Some("coder-1"));
    assert_eq!(files.len(), 1, "Should find only coder-1 log");
    assert!(files[0].to_string_lossy().contains("coder-1"));
}

#[test]
fn test_format_log_entry_output_event() {
    let ts = "2026-04-10T12:48:02.123456789+00:00";
    let event = serde_json::json!({
        "type": "output",
        "story_id": "42_story",
        "agent_name": "coder-1",
        "text": "Hello world"
    });
    let result = format_log_entry_as_text(ts, &event).unwrap();
    assert!(result.contains("12:48:02"), "should include timestamp");
    assert!(result.contains("coder-1"), "should include agent name");
    assert!(result.contains("Hello world"), "should include text");
}

#[test]
fn test_format_log_entry_skips_empty_output() {
    let ts = "2026-04-10T12:48:02.123456789+00:00";
    let event = serde_json::json!({
        "type": "output",
        "story_id": "42_story",
        "agent_name": "coder-1",
        "text": "   "
    });
    assert!(format_log_entry_as_text(ts, &event).is_none());
}

#[test]
fn test_format_log_entry_error_event() {
    let ts = "2026-04-10T12:48:02.123+00:00";
    let event = serde_json::json!({
        "type": "error",
        "story_id": "42_story",
        "agent_name": "coder-1",
        "message": "Something went wrong"
    });
    let result = format_log_entry_as_text(ts, &event).unwrap();
    assert!(result.contains("ERROR"));
    assert!(result.contains("Something went wrong"));
}

#[test]
fn test_format_log_entry_done_event() {
    let ts = "2026-04-10T12:48:02.123+00:00";
    let event = serde_json::json!({
        "type": "done",
        "story_id": "42_story",
        "agent_name": "coder-1",
        "session_id": null
    });
    let result = format_log_entry_as_text(ts, &event).unwrap();
    assert!(result.contains("DONE"));
}

#[test]
fn test_format_log_entry_skips_running_status() {
    let ts = "2026-04-10T12:48:02.123+00:00";
    let event = serde_json::json!({
        "type": "status",
        "story_id": "42_story",
        "agent_name": "coder-1",
        "status": "running"
    });
    assert!(format_log_entry_as_text(ts, &event).is_none());
}

#[test]
fn test_format_log_entry_agent_json_tool_use() {
    let ts = "2026-04-10T12:48:03.000+00:00";
    let event = serde_json::json!({
        "type": "agent_json",
        "story_id": "42_story",
        "agent_name": "coder-1",
        "data": {
            "type": "assistant",
            "message": {
                "content": [
                    {
                        "type": "tool_use",
                        "id": "tool-1",
                        "name": "Read",
                        "input": {"file_path": "/some/file.rs"}
                    }
                ]
            }
        }
    });
    let result = format_log_entry_as_text(ts, &event).unwrap();
    assert!(
        result.contains("TOOL: Read"),
        "should show tool call: {result}"
    );
    assert!(result.contains("file_path"), "should show input: {result}");
}

#[test]
fn test_format_log_entry_agent_json_text() {
    let ts = "2026-04-10T12:48:04.000+00:00";
    let event = serde_json::json!({
        "type": "agent_json",
        "story_id": "42_story",
        "agent_name": "coder-1",
        "data": {
            "type": "assistant",
            "message": {
                "content": [
                    {
                        "type": "text",
                        "text": "Now I will read the file."
                    }
                ]
            }
        }
    });
    let result = format_log_entry_as_text(ts, &event).unwrap();
    assert!(
        result.contains("Now I will read the file."),
        "should show text: {result}"
    );
}

#[test]
fn test_format_log_entry_skips_stream_events() {
    let ts = "2026-04-10T12:48:04.000+00:00";
    let event = serde_json::json!({
        "type": "agent_json",
        "story_id": "42_story",
        "agent_name": "coder-1",
        "data": {
            "type": "stream_event",
            "event": {"type": "content_block_delta", "delta": {"type": "text_delta", "text": "chunk"}}
        }
    });
    assert!(
        format_log_entry_as_text(ts, &event).is_none(),
        "stream events should be skipped"
    );
}

#[test]
fn test_read_log_as_readable_lines_produces_formatted_output() {
    let tmp = tempdir().unwrap();
    let root = tmp.path();

    let mut writer = AgentLogWriter::new(root, "42_story_foo", "coder-1", "sess-readable").unwrap();
    writer
        .write_event(&AgentEvent::Output {
            story_id: "42_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            text: "Let me read the file".to_string(),
        })
        .unwrap();
    writer
        .write_event(&AgentEvent::Done {
            story_id: "42_story_foo".to_string(),
            agent_name: "coder-1".to_string(),
            session_id: Some("sess-readable".to_string()),
        })
        .unwrap();

    let path = log_file_path(root, "42_story_foo", "coder-1", "sess-readable");
    let lines = read_log_as_readable_lines(&path).unwrap();
    assert_eq!(lines.len(), 2, "Should produce 2 readable lines");
    assert!(
        lines[0].contains("Let me read the file"),
        "first line: {}",
        lines[0]
    );
    assert!(lines[1].contains("DONE"), "second line: {}", lines[1]);
}

#[test]
fn test_find_latest_log_returns_none_for_missing_dir() {
    let tmp = tempdir().unwrap();
    let result = find_latest_log(tmp.path(), "nonexistent", "coder-1");
    assert!(result.is_none());
}

#[test]
fn test_log_files_persist_on_disk() {
    let tmp = tempdir().unwrap();
    let root = tmp.path();

    let path = {
        let mut writer =
            AgentLogWriter::new(root, "42_story_foo", "coder-1", "sess-persist").unwrap();
        writer
            .write_event(&AgentEvent::Status {
                story_id: "42_story_foo".to_string(),
                agent_name: "coder-1".to_string(),
                status: "running".to_string(),
            })
            .unwrap();
        log_file_path(root, "42_story_foo", "coder-1", "sess-persist")
        // writer is dropped here
    };

    // File should still exist and be readable
    assert!(
        path.exists(),
        "Log file should persist after writer is dropped"
    );
    let entries = read_log(&path).unwrap();
    assert_eq!(entries.len(), 1);
    assert_eq!(entries[0].event["type"], "status");
}
