//! Agent I/O wrappers — the ONLY place in `service/agents/` that may perform
//! filesystem reads, process invocations, or other side effects.
//!
//! Every function here is a thin adapter over an existing lower-level call.
//! No business logic lives here; all branching belongs in the pure topic files
//! or in `mod.rs`.
use crate::agent_log::{self, LogEntry};
use crate::agents::token_usage::{self, TokenUsageRecord};
use crate::config::ProjectConfig;
use std::path::Path;

use super::Error;

/// Return `true` if the story's `.md` file exists in `5_done/` or `6_archived/`.
pub fn is_archived(project_root: &Path, story_id: &str) -> bool {
    let work = project_root.join(".huskies").join("work");
    let filename = format!("{story_id}.md");
    work.join("5_done").join(&filename).exists() || work.join("6_archived").join(&filename).exists()
}

/// Read and return all log entries for the most recent session of an agent.
///
/// Returns `Ok(vec![])` when no log file exists yet.
pub fn read_agent_log(
    project_root: &Path,
    story_id: &str,
    agent_name: &str,
) -> Result<Vec<LogEntry>, Error> {
    let log_path = agent_log::find_latest_log(project_root, story_id, agent_name);
    let Some(path) = log_path else {
        return Ok(Vec::new());
    };
    agent_log::read_log(&path).map_err(Error::Io)
}

/// Read all token usage records from the persistent JSONL file.
///
/// Returns an empty vec when the file does not yet exist.
pub fn read_token_records(project_root: &Path) -> Result<Vec<TokenUsageRecord>, Error> {
    token_usage::read_all(project_root).map_err(Error::Io)
}

/// Load the project configuration from `project.toml`.
///
/// Falls back to default config when the file is absent.
pub fn load_config(project_root: &Path) -> Result<ProjectConfig, Error> {
    ProjectConfig::load(project_root).map_err(Error::Config)
}

/// Read test results persisted in a story's markdown file.
///
/// Returns `None` when the story has no test results section.
pub fn read_test_results_from_file(
    project_root: &Path,
    story_id: &str,
) -> Option<crate::workflow::StoryTestResults> {
    crate::http::workflow::read_test_results_from_story_file(project_root, story_id)
}

/// Read a work item file from a pipeline stage directory.
///
/// Returns `Ok(Some(content))` when found, `Ok(None)` when absent.
pub fn read_work_item_from_stage(
    work_dir: &std::path::Path,
    stage_dir: &str,
    filename: &str,
) -> Result<Option<String>, Error> {
    let file_path = work_dir.join(stage_dir).join(filename);
    if file_path.exists() {
        let content = std::fs::read_to_string(&file_path)
            .map_err(|e| Error::Io(format!("Failed to read work item: {e}")))?;
        Ok(Some(content))
    } else {
        Ok(None)
    }
}

/// Test-fixture helpers that may call `std::fs` — kept here so that
/// `mod.rs` and topic-file `#[cfg(test)]` blocks never need to import
/// `std::fs`, `tokio::fs`, or `std::process` directly.
#[cfg(test)]
pub mod test_helpers {
    use tempfile::TempDir;

    /// Create the `.huskies/` directory.
    pub fn make_huskies_dir(tmp: &TempDir) {
        std::fs::create_dir_all(tmp.path().join(".huskies")).unwrap();
    }

    /// Create all six pipeline stage directories under `.huskies/work/`.
    pub fn make_stage_dirs(tmp: &TempDir) {
        for stage in &[
            "1_backlog",
            "2_current",
            "3_qa",
            "4_merge",
            "5_done",
            "6_archived",
        ] {
            std::fs::create_dir_all(tmp.path().join(".huskies").join("work").join(stage)).unwrap();
        }
    }

    /// Write `.huskies/project.toml` with the given TOML content.
    pub fn make_project_toml(tmp: &TempDir, content: &str) {
        let sk_dir = tmp.path().join(".huskies");
        std::fs::create_dir_all(&sk_dir).unwrap();
        std::fs::write(sk_dir.join("project.toml"), content).unwrap();
    }

    /// Write a fixture file at `relative_path` (relative to the tmp root).
    pub fn write_story_file(tmp: &TempDir, relative_path: &str, content: &str) {
        let path = tmp.path().join(relative_path);
        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent).unwrap();
        }
        std::fs::write(path, content).unwrap();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    fn make_work_dirs(tmp: &TempDir) {
        for stage in &["5_done", "6_archived"] {
            std::fs::create_dir_all(tmp.path().join(".huskies").join("work").join(stage)).unwrap();
        }
    }

    // ── is_archived ───────────────────────────────────────────────────────────

    #[test]
    fn is_archived_false_when_file_absent() {
        let tmp = TempDir::new().unwrap();
        make_work_dirs(&tmp);
        assert!(!is_archived(tmp.path(), "42_story_foo"));
    }

    #[test]
    fn is_archived_true_when_in_5_done() {
        let tmp = TempDir::new().unwrap();
        make_work_dirs(&tmp);
        std::fs::write(
            tmp.path().join(".huskies/work/5_done/42_story_foo.md"),
            "---\nname: test\n---\n",
        )
        .unwrap();
        assert!(is_archived(tmp.path(), "42_story_foo"));
    }

    #[test]
    fn is_archived_true_when_in_6_archived() {
        let tmp = TempDir::new().unwrap();
        make_work_dirs(&tmp);
        std::fs::write(
            tmp.path().join(".huskies/work/6_archived/42_story_foo.md"),
            "---\nname: test\n---\n",
        )
        .unwrap();
        assert!(is_archived(tmp.path(), "42_story_foo"));
    }

    // ── read_agent_log ────────────────────────────────────────────────────────

    #[test]
    fn read_agent_log_returns_empty_when_no_log() {
        let tmp = TempDir::new().unwrap();
        let entries = read_agent_log(tmp.path(), "42_story_foo", "coder-1").unwrap();
        assert!(entries.is_empty());
    }

    // ── read_token_records ────────────────────────────────────────────────────

    #[test]
    fn read_token_records_returns_empty_when_no_file() {
        let tmp = TempDir::new().unwrap();
        let records = read_token_records(tmp.path()).unwrap();
        assert!(records.is_empty());
    }

    // ── load_config ───────────────────────────────────────────────────────────

    #[test]
    fn load_config_returns_default_when_no_file() {
        let tmp = TempDir::new().unwrap();
        std::fs::create_dir_all(tmp.path().join(".huskies")).unwrap();
        let config = load_config(tmp.path()).unwrap();
        // Default config has one "default" agent
        assert_eq!(config.agent.len(), 1);
        assert_eq!(config.agent[0].name, "default");
    }
}
