//! Regression tests for changelog entry parsing — both legacy-slug and new-format
//! merge commit subjects must resolve to a human-readable "Name (ID)" entry.

/// Parse a single merge commit subject (after stripping the `huskies: merge ` prefix)
/// into `(id, type_word, human_name)`.
///
/// Returns `None` for subjects that are not recognised merge items.
fn parse_changelog_entry(item: &str) -> Option<(String, String, String)> {
    let item = item.trim();
    if item.is_empty() {
        return None;
    }

    // Extract leading numeric ID present in both formats.
    let id: String = item.chars().take_while(|c| c.is_ascii_digit()).collect();
    if id.is_empty() {
        return None;
    }

    // Detect format by the character immediately following the digits.
    // id contains only ASCII digits so id.len() is a valid char boundary.
    let rest = item.get(id.len()..).unwrap_or("");
    if let Some(space_rest) = rest.strip_prefix(' ') {
        // New format: "1063 story Human Name Here"
        let mut words = space_rest.splitn(2, ' ');
        let type_word = words.next().unwrap_or("story").to_string();
        let name = words.next().unwrap_or("").trim().to_string();
        if name.is_empty() {
            return None;
        }
        Some((id, type_word, name))
    } else if let Some(slug_rest) = rest.strip_prefix('_') {
        // Legacy slug format: "1063_story_human_name_here"
        let mut parts = slug_rest.splitn(2, '_');
        let type_word = parts.next().unwrap_or("story").to_string();
        let slug = parts.next().unwrap_or("").replace('_', " ");
        if slug.is_empty() {
            return None;
        }
        Some((id, type_word, slug))
    } else {
        None
    }
}

/// Format a parsed entry as "Human Name (ID)".
fn format_entry(id: &str, name: &str) -> String {
    let mut chars = name.chars();
    let capitalised = match chars.next() {
        None => String::new(),
        Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
    };
    format!("{capitalised} ({id})")
}

#[test]
fn changelog_new_format_story_resolves_to_name_and_id() {
    let item = "1063 story Tee pipeline events into gateway context";
    let (id, _type_word, name) = parse_changelog_entry(item).expect("should parse new format");
    assert_eq!(id, "1063");
    assert_eq!(
        format_entry(&id, &name),
        "Tee pipeline events into gateway context (1063)"
    );
}

#[test]
fn changelog_new_format_bug_resolves_to_name_and_id() {
    let item = "999 bug Fix the broken auth token";
    let (id, type_word, name) = parse_changelog_entry(item).expect("should parse new-format bug");
    assert_eq!(id, "999");
    assert_eq!(type_word, "bug");
    assert_eq!(format_entry(&id, &name), "Fix the broken auth token (999)");
}

#[test]
fn changelog_new_format_refactor_resolves_to_name_and_id() {
    let item = "777 refactor Extract config parsing";
    let (id, type_word, name) = parse_changelog_entry(item).expect("should parse refactor");
    assert_eq!(type_word, "refactor");
    assert_eq!(format_entry(&id, &name), "Extract config parsing (777)");
}

#[test]
fn changelog_legacy_slug_story_resolves_to_name_and_id() {
    let item = "1063_story_tee_pipeline_events_into_gateway_context";
    let (id, _type_word, name) = parse_changelog_entry(item).expect("should parse legacy slug");
    assert_eq!(id, "1063");
    assert_eq!(
        format_entry(&id, &name),
        "Tee pipeline events into gateway context (1063)"
    );
}

#[test]
fn changelog_legacy_slug_bug_resolves_to_name_and_id() {
    let item = "999_bug_fix_the_broken_auth_token";
    let (id, type_word, name) = parse_changelog_entry(item).expect("should parse legacy bug slug");
    assert_eq!(id, "999");
    assert_eq!(type_word, "bug");
    assert_eq!(format_entry(&id, &name), "Fix the broken auth token (999)");
}

#[test]
fn changelog_mixed_fixture_all_entries_have_human_names() {
    // Fixture: a mix of legacy-slug and new-format subjects (as they appear
    // after stripping the "huskies: merge " prefix from the git log).
    let fixture = [
        // Legacy slug formats (pre-migration)
        "1001_story_add_matrix_transport",
        "1002_bug_fix_crdt_sync_disconnect",
        "1003_refactor_extract_gateway_config",
        // New format (post-story-1069)
        "1050 story Add agent pool auto-assign",
        "1063 story Tee pipeline events into gateway context",
        "1064 bug Stop lagged handler re-emitting via same channel",
        "1065 refactor Move squash merge into own module",
    ];

    for item in &fixture {
        let result = parse_changelog_entry(item);
        assert!(result.is_some(), "failed to parse merge subject: {item:?}");
        let (id, _type_word, name) = result.unwrap();
        let entry = format_entry(&id, &name);
        // Every entry must contain the numeric ID in parentheses.
        assert!(
            entry.contains(&format!("({id})")),
            "entry missing numeric ID: {entry:?}"
        );
        // Name must not be empty or just whitespace.
        assert!(
            !name.trim().is_empty(),
            "empty human name for item: {item:?}"
        );
        // Name must not be a raw slug (contains underscores as word separators).
        // (Underscores are OK inside words like "auto-assign" but not as spaces.)
        assert!(
            !name.contains('_'),
            "name still contains underscores (slug not decoded): {name:?}"
        );
    }
}
