//! update_story_in_file: replace `## User Story` / `## Description` sections
//! in an existing story's content body.
//!
//! Story 929: the legacy `front_matter` HashMap parameter is gone — every
//! known typed field is intercepted in `tool_update_story` and routed to a
//! typed CRDT setter (set_name, set_agent, set_qa_mode, set_depends_on, …).
//! Any caller still trying to write to YAML front matter is a bug.

use std::path::Path;

#[allow(unused_imports)]
use super::super::{
    create_section_content, next_item_number, read_story_content, replace_section_content,
    slugify_name, story_stage, write_story_content,
};

/// Update the user story text and/or description in a story.
///
/// At least one of `user_story` or `description` must be provided.
/// Replaces the content of the corresponding `##` section in place.
pub fn update_story_in_file(
    project_root: &Path,
    story_id: &str,
    user_story: Option<&str>,
    description: Option<&str>,
) -> Result<(), String> {
    if user_story.is_none() && description.is_none() {
        return Err("At least one of 'user_story' or 'description' must be provided.".to_string());
    }

    let mut contents = read_story_content(project_root, story_id)?;

    if let Some(us) = user_story {
        contents = match replace_section_content(&contents, "User Story", us) {
            Ok(updated) => updated,
            Err(_) => {
                create_section_content(&contents, "User Story", us, Some("Acceptance Criteria"))
            }
        };
    }
    if let Some(desc) = description {
        contents = match replace_section_content(&contents, "Description", desc) {
            Ok(updated) => updated,
            Err(_) => {
                create_section_content(&contents, "Description", desc, Some("Acceptance Criteria"))
            }
        };
    }

    // Write back to content store.
    let stage = story_stage(story_id).unwrap_or_else(|| "2_current".to_string());
    write_story_content(project_root, story_id, &stage, &contents, None);

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;

    /// Helper: write a story to both the in-memory content store and the
    /// `.huskies/work/2_current/` directory so `read_story_content` picks it up
    /// regardless of which read path is used.
    fn setup_story_in_fs(root: &std::path::Path, story_id: &str, content: &str) {
        let current = root.join(".huskies/work/2_current");
        fs::create_dir_all(&current).unwrap();
        fs::write(current.join(format!("{story_id}.md")), content).unwrap();
        crate::db::ensure_content_store();
        crate::db::write_content(crate::db::ContentKey::Story(story_id), content);
    }

    #[test]
    fn update_story_replaces_user_story_section() {
        let tmp = tempfile::tempdir().unwrap();
        let content =
            "# Story\n\n## User Story\n\nOld text\n\n## Acceptance Criteria\n\n- [ ] AC\n";
        setup_story_in_fs(tmp.path(), "20_test", content);

        update_story_in_file(tmp.path(), "20_test", Some("New user story text"), None).unwrap();

        let result = read_story_content(tmp.path(), "20_test").unwrap();
        assert!(
            result.contains("New user story text"),
            "new text should be present"
        );
        assert!(!result.contains("Old text"), "old text should be replaced");
        assert!(
            result.contains("## Acceptance Criteria"),
            "other sections preserved"
        );
    }

    #[test]
    fn update_story_replaces_description_section() {
        let tmp = tempfile::tempdir().unwrap();
        let content =
            "# Story\n\n## Description\n\nOld description\n\n## Acceptance Criteria\n\n- [ ] AC\n";
        setup_story_in_fs(tmp.path(), "21_test", content);

        update_story_in_file(tmp.path(), "21_test", None, Some("New description")).unwrap();

        let result = read_story_content(tmp.path(), "21_test").unwrap();
        assert!(
            result.contains("New description"),
            "new description present"
        );
        assert!(
            !result.contains("Old description"),
            "old description replaced"
        );
    }

    #[test]
    fn update_story_no_args_returns_error() {
        let tmp = tempfile::tempdir().unwrap();
        setup_story_in_fs(tmp.path(), "22_test", "# Story\n");

        let result = update_story_in_file(tmp.path(), "22_test", None, None);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("At least one"));
    }

    #[test]
    fn update_story_creates_user_story_section_if_missing() {
        let tmp = tempfile::tempdir().unwrap();
        let content = "# Story\n\n## Acceptance Criteria\n\n- [ ] AC\n";
        setup_story_in_fs(tmp.path(), "23_test", content);

        let result = update_story_in_file(tmp.path(), "23_test", Some("New user story"), None);
        assert!(
            result.is_ok(),
            "should succeed when section is missing: {result:?}"
        );

        let updated = read_story_content(tmp.path(), "23_test").unwrap();
        assert!(
            updated.contains("## User Story"),
            "section should be created"
        );
        assert!(updated.contains("New user story"), "text should be present");
        let pos_us = updated.find("## User Story").unwrap();
        let pos_ac = updated.find("## Acceptance Criteria").unwrap();
        assert!(
            pos_us < pos_ac,
            "User Story should be before Acceptance Criteria"
        );
    }

    #[test]
    fn update_story_creates_description_section_if_missing() {
        let tmp = tempfile::tempdir().unwrap();
        let content =
            "# Story\n\n## User Story\n\nAs a user...\n\n## Acceptance Criteria\n\n- [ ] AC\n";
        setup_story_in_fs(tmp.path(), "32_test", content);

        let result =
            update_story_in_file(tmp.path(), "32_test", None, Some("New description text"));
        assert!(
            result.is_ok(),
            "should succeed when section is missing: {result:?}"
        );

        let updated = read_story_content(tmp.path(), "32_test").unwrap();
        assert!(
            updated.contains("## Description"),
            "section should be created"
        );
        assert!(
            updated.contains("New description text"),
            "text should be present"
        );
        let pos_desc = updated.find("## Description").unwrap();
        let pos_ac = updated.find("## Acceptance Criteria").unwrap();
        assert!(
            pos_desc < pos_ac,
            "Description should be before Acceptance Criteria"
        );
    }

    #[test]
    fn update_story_creates_description_section_no_ac_section() {
        let tmp = tempfile::tempdir().unwrap();
        let content = "# Story\n\nSome content here.\n";
        setup_story_in_fs(tmp.path(), "33_test", content);

        let result =
            update_story_in_file(tmp.path(), "33_test", None, Some("Appended description"));
        assert!(
            result.is_ok(),
            "should succeed even with no Acceptance Criteria: {result:?}"
        );

        let updated = read_story_content(tmp.path(), "33_test").unwrap();
        assert!(
            updated.contains("## Description"),
            "section should be created"
        );
        assert!(
            updated.contains("Appended description"),
            "text should be present"
        );
    }
}
