//! Tests for changelog domain entity

use chrono::Utc;
use governor_core::domain::changelog::{
    Changelog, ChangelogConfig, ChangelogEntry, ChangelogFormat, ChangelogSection,
    ChangelogSectionConfig,
};
use governor_core::domain::commit::Commit;
use governor_core::domain::version::SemanticVersion;

#[test]
fn test_changelog_new() {
    let version = SemanticVersion::parse("1.0.0").unwrap();
    let changelog = Changelog::new(version.clone());
    assert_eq!(changelog.version, version);
    assert!(changelog.entries.is_empty());
    assert!(changelog.released_at.is_none());
}

#[test]
fn test_changelog_add_entry() {
    let mut changelog = Changelog::new(SemanticVersion::parse("1.0.0").unwrap());
    let entry = ChangelogEntry::new(ChangelogSection::Added, "New feature".to_string());
    changelog.add_entry(entry);
    assert_eq!(changelog.entries.len(), 1);
}

#[test]
fn test_changelog_entries_by_section() {
    let mut changelog = Changelog::new(SemanticVersion::parse("1.0.0").unwrap());
    changelog.add_entry(ChangelogEntry::new(
        ChangelogSection::Added,
        "Feature".to_string(),
    ));
    changelog.add_entry(ChangelogEntry::new(
        ChangelogSection::Fixed,
        "Fix".to_string(),
    ));
    changelog.add_entry(ChangelogEntry::new(
        ChangelogSection::Added,
        "Another".to_string(),
    ));

    let added = changelog.entries_by_section(ChangelogSection::Added);
    assert_eq!(added.len(), 2);

    let fixed = changelog.entries_by_section(ChangelogSection::Fixed);
    assert_eq!(fixed.len(), 1);

    let breaking = changelog.entries_by_section(ChangelogSection::Breaking);
    assert!(breaking.is_empty());
}

#[test]
fn test_changelog_breaking_changes() {
    let mut changelog = Changelog::new(SemanticVersion::parse("1.0.0").unwrap());
    changelog.add_entry(ChangelogEntry::new(
        ChangelogSection::Breaking,
        "Breaking".to_string(),
    ));

    let breaking = changelog.breaking_changes();
    assert_eq!(breaking.len(), 1);
}

#[test]
fn test_changelog_features() {
    let mut changelog = Changelog::new(SemanticVersion::parse("1.0.0").unwrap());
    changelog.add_entry(ChangelogEntry::new(
        ChangelogSection::Added,
        "Feature".to_string(),
    ));

    let features = changelog.features();
    assert_eq!(features.len(), 1);
}

#[test]
fn test_changelog_fixes() {
    let mut changelog = Changelog::new(SemanticVersion::parse("1.0.0").unwrap());
    changelog.add_entry(ChangelogEntry::new(
        ChangelogSection::Fixed,
        "Fix".to_string(),
    ));

    let fixes = changelog.fixes();
    assert_eq!(fixes.len(), 1);
}

#[test]
fn test_changelog_format_keep_a_changelog() {
    let mut changelog = Changelog::new(SemanticVersion::parse("1.0.0").unwrap());
    changelog.released_at = Some(Utc::now());
    changelog.add_entry(ChangelogEntry::new(
        ChangelogSection::Added,
        "Feature".to_string(),
    ));

    let formatted = changelog.format_keep_a_changelog();
    assert!(formatted.contains("## [1.0.0]"));
    assert!(formatted.contains("### Added"));
    assert!(formatted.contains("Feature"));
}

#[test]
fn test_changelog_format_json() {
    let changelog = Changelog::new(SemanticVersion::parse("1.0.0").unwrap());
    let json = changelog.format_json();
    assert!(json.is_ok());
    assert!(json.unwrap().contains("1.0.0"));
}

#[test]
fn test_changelog_section_all() {
    let sections = ChangelogSection::all();
    assert_eq!(sections.len(), 7);
    assert_eq!(sections[0], ChangelogSection::Breaking);
    assert_eq!(sections[1], ChangelogSection::Added);
    assert_eq!(sections[6], ChangelogSection::Security);
}

#[test]
fn test_changelog_section_title() {
    assert_eq!(ChangelogSection::Breaking.title(), "Breaking Changes");
    assert_eq!(ChangelogSection::Added.title(), "Added");
    assert_eq!(ChangelogSection::Fixed.title(), "Fixed");
    assert_eq!(ChangelogSection::Changed.title(), "Changed");
    assert_eq!(ChangelogSection::Deprecated.title(), "Deprecated");
    assert_eq!(ChangelogSection::Removed.title(), "Removed");
    assert_eq!(ChangelogSection::Security.title(), "Security");
}

#[test]
fn test_changelog_section_order() {
    assert_eq!(ChangelogSection::Breaking.order(), 0);
    assert_eq!(ChangelogSection::Added.order(), 1);
    assert_eq!(ChangelogSection::Fixed.order(), 2);
    assert_eq!(ChangelogSection::Changed.order(), 3);
}

#[test]
fn test_changelog_section_from_commit_type() {
    assert_eq!(
        ChangelogSection::from_commit_type("feat", false),
        Some(ChangelogSection::Added)
    );
    assert_eq!(
        ChangelogSection::from_commit_type("fix", false),
        Some(ChangelogSection::Fixed)
    );
    assert_eq!(
        ChangelogSection::from_commit_type("refactor", false),
        Some(ChangelogSection::Changed)
    );
    assert_eq!(
        ChangelogSection::from_commit_type("perf", false),
        Some(ChangelogSection::Changed)
    );
    assert_eq!(
        ChangelogSection::from_commit_type("deprecated", false),
        Some(ChangelogSection::Deprecated)
    );
    assert_eq!(
        ChangelogSection::from_commit_type("removed", false),
        Some(ChangelogSection::Removed)
    );
    assert_eq!(
        ChangelogSection::from_commit_type("security", false),
        Some(ChangelogSection::Security)
    );
    assert_eq!(ChangelogSection::from_commit_type("chore", false), None);
    assert_eq!(
        ChangelogSection::from_commit_type("feat", true),
        Some(ChangelogSection::Breaking)
    );
}

#[test]
fn test_changelog_entry_new() {
    let entry = ChangelogEntry::new(ChangelogSection::Added, "Test".to_string());
    assert_eq!(entry.section, ChangelogSection::Added);
    assert_eq!(entry.message, "Test");
    assert!(entry.scope.is_none());
    assert!(entry.commit_hash.is_none());
    assert!(entry.affected_crates.is_empty());
}

#[test]
fn test_changelog_entry_from_commit() {
    let commit = Commit::new(
        "abc123".to_string(),
        "feat(api): add endpoint".to_string(),
        "Author".to_string(),
        "author@test.com".to_string(),
        Utc::now(),
    );
    let entry = ChangelogEntry::from_commit(&commit);
    assert!(entry.is_some());
    let entry = entry.unwrap();
    assert_eq!(entry.section, ChangelogSection::Added);
    assert_eq!(entry.scope, Some("api".to_string()));
    assert_eq!(entry.commit_hash, Some("abc123".to_string()));
}

#[test]
fn test_changelog_entry_from_commit_breaking() {
    let commit = Commit::new(
        "abc123".to_string(),
        "feat!: breaking change".to_string(),
        "Author".to_string(),
        "author@test.com".to_string(),
        Utc::now(),
    );
    let entry = ChangelogEntry::from_commit(&commit);
    assert!(entry.is_some());
    let entry = entry.unwrap();
    assert_eq!(entry.section, ChangelogSection::Breaking);
}

#[test]
fn test_changelog_entry_from_commit_unknown_type() {
    let commit = Commit::new(
        "abc123".to_string(),
        "chore: update deps".to_string(),
        "Author".to_string(),
        "author@test.com".to_string(),
        Utc::now(),
    );
    let entry = ChangelogEntry::from_commit(&commit);
    assert!(entry.is_none());
}

#[test]
fn test_changelog_entry_with_scope() {
    let entry = ChangelogEntry::new(ChangelogSection::Added, "Test".to_string())
        .with_scope("api".to_string());
    assert_eq!(entry.scope, Some("api".to_string()));
}

#[test]
fn test_changelog_entry_with_commit_hash() {
    let entry = ChangelogEntry::new(ChangelogSection::Added, "Test".to_string())
        .with_commit_hash("abc123".to_string());
    assert_eq!(entry.commit_hash, Some("abc123".to_string()));
}

#[test]
fn test_changelog_entry_with_affected_crate() {
    let entry = ChangelogEntry::new(ChangelogSection::Added, "Test".to_string())
        .with_affected_crate("crate1".to_string());
    assert_eq!(entry.affected_crates.len(), 1);
    assert_eq!(entry.affected_crates[0], "crate1");
}

#[test]
fn test_changelog_entry_format_markdown() {
    let entry = ChangelogEntry::new(ChangelogSection::Added, "New feature".to_string());
    let formatted = entry.format_markdown();
    assert!(formatted.contains("- New feature"));
}

#[test]
fn test_changelog_entry_format_markdown_with_scope() {
    let entry = ChangelogEntry::new(ChangelogSection::Added, "New feature".to_string())
        .with_scope("api".to_string());
    let formatted = entry.format_markdown();
    assert!(formatted.contains("**api**"));
    assert!(formatted.contains("New feature"));
}

#[test]
fn test_changelog_entry_format_markdown_with_hash() {
    let entry = ChangelogEntry::new(ChangelogSection::Added, "New feature".to_string())
        .with_commit_hash("abc123".to_string());
    let formatted = entry.format_markdown();
    assert!(formatted.contains("(abc123)"));
}

#[test]
fn test_changelog_entry_format_markdown_full() {
    let entry = ChangelogEntry::new(ChangelogSection::Added, "New feature".to_string())
        .with_scope("api".to_string())
        .with_commit_hash("abc123".to_string())
        .with_affected_crate("my-crate".to_string());
    let formatted = entry.format_markdown();
    assert!(formatted.contains("**api**"));
    assert!(formatted.contains("New feature"));
    assert!(formatted.contains("(abc123)"));
    assert!(formatted.contains("in my-crate"));
}

#[test]
fn test_changelog_config_default() {
    let config = ChangelogConfig::default();
    assert_eq!(config.format, ChangelogFormat::KeepAChangelog);
    assert_eq!(config.path, "CHANGELOG.md");
    assert!(config.incremental);
    assert_eq!(config.sections.len(), 4);
    assert_eq!(config.exclude_types.len(), 6);
    assert_eq!(config.exclude_scopes.len(), 2);
}

#[test]
fn test_changelog_section_config_default_all() {
    let sections = ChangelogSectionConfig::default_all();
    assert_eq!(sections.len(), 4);
    assert_eq!(sections[0].section, ChangelogSection::Breaking);
    assert_eq!(sections[1].section, ChangelogSection::Added);
    assert_eq!(sections[2].section, ChangelogSection::Fixed);
    assert_eq!(sections[3].section, ChangelogSection::Changed);
}

#[test]
fn test_changelog_empty_format() {
    let changelog = Changelog::new(SemanticVersion::parse("1.0.0").unwrap());
    let formatted = changelog.format_keep_a_changelog();
    assert!(formatted.contains("## [1.0.0]"));
    // Should not contain any section headers if empty
    assert!(!formatted.contains("###"));
}

#[test]
fn test_changelog_multiple_sections() {
    let mut changelog = Changelog::new(SemanticVersion::parse("2.0.0").unwrap());
    changelog.released_at = Some(Utc::now());
    changelog.add_entry(ChangelogEntry::new(
        ChangelogSection::Breaking,
        "API change".to_string(),
    ));
    changelog.add_entry(ChangelogEntry::new(
        ChangelogSection::Added,
        "Feature".to_string(),
    ));
    changelog.add_entry(ChangelogEntry::new(
        ChangelogSection::Fixed,
        "Bug fix".to_string(),
    ));

    let formatted = changelog.format_keep_a_changelog();
    assert!(formatted.contains("### Breaking Changes"));
    assert!(formatted.contains("### Added"));
    assert!(formatted.contains("### Fixed"));
}

#[test]
fn test_changelog_section_from_commit_type_case_insensitive() {
    assert_eq!(
        ChangelogSection::from_commit_type("FEAT", false),
        Some(ChangelogSection::Added)
    );
    assert_eq!(
        ChangelogSection::from_commit_type("FIX", false),
        Some(ChangelogSection::Fixed)
    );
}
