//! Tests for crate entity domain model

use governor_core::domain::crate_entity::{
    CrateEntity, CrateFlags, Dependency, DependencyFlags, Workspace,
};
use governor_core::domain::version::SemanticVersion;
use std::path::PathBuf;

#[test]
fn test_crate_entity_new() {
    let name = "test-crate".to_string();
    let path = PathBuf::from("/test/crate");
    let version = SemanticVersion::parse("1.0.0").unwrap();
    let krate = CrateEntity::new(name.clone(), path.clone(), version, false);
    assert_eq!(krate.name, name);
    assert_eq!(krate.path, path);
    assert_eq!(krate.version().to_string(), "1.0.0");
    assert!(!krate.published);
    assert!(krate.dependencies.is_empty());
    assert!(!krate.flags.is_lib);
    assert!(!krate.flags.has_bin);
    assert!(krate.features.is_empty());
}

#[test]
fn test_crate_entity_name() {
    let krate = CrateEntity::new(
        "my-crate".to_string(),
        PathBuf::from("/test"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    assert_eq!(krate.name(), "my-crate");
}

#[test]
fn test_crate_entity_version() {
    let version = SemanticVersion::parse("2.5.0").unwrap();
    let krate = CrateEntity::new("test".to_string(), PathBuf::from("/test"), version, false);
    assert_eq!(krate.version().to_string(), "2.5.0");
}

#[test]
fn test_crate_entity_path() {
    let path = PathBuf::from("/test/path");
    let krate = CrateEntity::new(
        "test".to_string(),
        path.clone(),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    assert_eq!(krate.path(), &path);
}

#[test]
fn test_crate_entity_is_lib() {
    let krate = CrateEntity::new(
        "test".to_string(),
        PathBuf::from("/test"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    assert!(!krate.is_lib());
}

#[test]
fn test_crate_entity_is_lib_true() {
    let mut krate = CrateEntity::new(
        "test".to_string(),
        PathBuf::from("/test"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    krate.flags.is_lib = true;
    assert!(krate.is_lib());
}

#[test]
fn test_crate_entity_has_bin() {
    let krate = CrateEntity::new(
        "test".to_string(),
        PathBuf::from("/test"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    assert!(!krate.has_bin());
}

#[test]
fn test_crate_entity_has_bin_true() {
    let mut krate = CrateEntity::new(
        "test".to_string(),
        PathBuf::from("/test"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    krate.flags.has_bin = true;
    assert!(krate.has_bin());
}

#[test]
fn test_crate_entity_should_publish() {
    let krate = CrateEntity::new(
        "test".to_string(),
        PathBuf::from("/test"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    // CrateEntity::new sets publish = true by default
    assert!(krate.should_publish());

    let mut krate2 = CrateEntity::new(
        "test2".to_string(),
        PathBuf::from("/test2"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    krate2.flags.publish = false;
    assert!(!krate2.should_publish());
}

#[test]
fn test_crate_entity_depends_on() {
    let mut krate = CrateEntity::new(
        "test".to_string(),
        PathBuf::from("/test"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    assert!(!krate.depends_on("dep1"));
    krate.dependencies.push(Dependency::new("dep1".to_string()));
    assert!(krate.depends_on("dep1"));
    assert!(!krate.depends_on("dep2"));
}

#[test]
fn test_crate_entity_depends_on_multiple() {
    let mut krate = CrateEntity::new(
        "test".to_string(),
        PathBuf::from("/test"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    krate.dependencies.push(Dependency::new("dep1".to_string()));
    krate.dependencies.push(Dependency::new("dep2".to_string()));
    krate.dependencies.push(Dependency::new("dep3".to_string()));
    assert!(krate.depends_on("dep1"));
    assert!(krate.depends_on("dep2"));
    assert!(krate.depends_on("dep3"));
}

#[test]
fn test_crate_entity_workspace_dependencies() {
    let mut krate = CrateEntity::new(
        "test".to_string(),
        PathBuf::from("/test"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );

    let mut ws_dep = Dependency::new("ws-dep".to_string());
    ws_dep.flags.is_workspace = true;
    krate.dependencies.push(ws_dep);

    let ext_dep = Dependency::new("ext-dep".to_string());
    krate.dependencies.push(ext_dep);

    let ws_deps = krate.workspace_dependencies();
    assert_eq!(ws_deps.len(), 1);
    assert_eq!(ws_deps[0].name, "ws-dep");
}

#[test]
fn test_crate_entity_external_dependencies() {
    let mut krate = CrateEntity::new(
        "test".to_string(),
        PathBuf::from("/test"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );

    let mut ws_dep = Dependency::new("ws-dep".to_string());
    ws_dep.flags.is_workspace = true;
    krate.dependencies.push(ws_dep);

    let ext_dep = Dependency::new("ext-dep".to_string());
    krate.dependencies.push(ext_dep);

    let ext_deps = krate.external_dependencies();
    assert_eq!(ext_deps.len(), 1);
    assert_eq!(ext_deps[0].name, "ext-dep");
}

#[test]
fn test_crate_entity_equality() {
    let krate1 = CrateEntity::new(
        "test".to_string(),
        PathBuf::from("/test1"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    let krate2 = CrateEntity::new(
        "test".to_string(),
        PathBuf::from("/test2"),
        SemanticVersion::parse("2.0.0").unwrap(),
        true,
    );
    assert_eq!(krate1, krate2);
}

#[test]
fn test_crate_entity_inequality() {
    let krate1 = CrateEntity::new(
        "crate1".to_string(),
        PathBuf::from("/test"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    let krate2 = CrateEntity::new(
        "crate2".to_string(),
        PathBuf::from("/test"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    assert_ne!(krate1, krate2);
}

#[test]
fn test_dependency_new() {
    let dep = Dependency::new("serde".to_string());
    assert_eq!(dep.name, "serde");
    assert!(dep.version_req.is_none());
    assert!(!dep.flags.is_workspace);
    assert!(!dep.flags.is_dev());
    assert!(!dep.flags.is_build());
    assert!(!dep.flags.is_optional);
    assert!(dep.features.is_empty());
    assert!(dep.registry.is_none());
}

#[test]
fn test_dependency_name() {
    let dep = Dependency::new("tokio".to_string());
    assert_eq!(dep.name(), "tokio");
}

#[test]
fn test_dependency_is_workspace() {
    let mut dep = Dependency::new("workspace-dep".to_string());
    assert!(!dep.is_workspace());
    dep.flags.is_workspace = true;
    assert!(dep.is_workspace());
}

#[test]
fn test_dependency_is_dev() {
    let dep = Dependency::new("dev-dep".to_string());
    assert!(!dep.is_dev());
    let dep = dep.flags.with_dev();
    assert!(dep.is_dev());
}

#[test]
fn test_dependency_is_build() {
    let dep = Dependency::new("build-dep".to_string());
    assert!(!dep.is_build());
    let dep = dep.flags.with_build();
    assert!(dep.is_build());
}

#[test]
fn test_dependency_is_optional() {
    let mut dep = Dependency::new("optional-dep".to_string());
    assert!(!dep.is_optional());
    dep.flags.is_optional = true;
    assert!(dep.is_optional());
}

#[test]
fn test_dependency_flags_new() {
    let flags = DependencyFlags::new();
    assert!(!flags.is_workspace);
    assert!(!flags.is_dev());
    assert!(!flags.is_build());
    assert!(!flags.is_optional);
}

#[test]
fn test_workspace_new() {
    let root = PathBuf::from("/workspace");
    let workspace = Workspace::new(root.clone());
    assert_eq!(workspace.root, root);
    assert!(workspace.crates.is_empty());
    assert!(workspace.workspace_version.is_none());
}

#[test]
fn test_workspace_add_crate() {
    let mut workspace = Workspace::new(PathBuf::from("/test"));
    let krate1 = CrateEntity::new(
        "crate1".to_string(),
        PathBuf::from("/test/crate1"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    let krate2 = CrateEntity::new(
        "crate2".to_string(),
        PathBuf::from("/test/crate2"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );

    workspace.add_crate(krate1.clone());
    assert_eq!(workspace.crates.len(), 1);

    // Adding same crate again should not duplicate
    workspace.add_crate(krate1);
    assert_eq!(workspace.crates.len(), 1);

    workspace.add_crate(krate2);
    assert_eq!(workspace.crates.len(), 2);
}

#[test]
fn test_workspace_get_crate() {
    let mut workspace = Workspace::new(PathBuf::from("/test"));
    let krate = CrateEntity::new(
        "my-crate".to_string(),
        PathBuf::from("/test/my-crate"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    workspace.add_crate(krate);

    assert!(workspace.get_crate("my-crate").is_some());
    assert!(workspace.get_crate("non-existent").is_none());
}

#[test]
fn test_workspace_get_crate_mut() {
    let mut workspace = Workspace::new(PathBuf::from("/test"));
    let krate = CrateEntity::new(
        "my-crate".to_string(),
        PathBuf::from("/test/my-crate"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    workspace.add_crate(krate);

    if let Some(krate_ref) = workspace.get_crate_mut("my-crate") {
        krate_ref.published = true;
    }

    assert!(workspace.get_crate("my-crate").unwrap().published);
}

#[test]
fn test_workspace_lib_crates() {
    let mut workspace = Workspace::new(PathBuf::from("/test"));

    let mut lib_crate = CrateEntity::new(
        "lib1".to_string(),
        PathBuf::from("/test/lib1"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    lib_crate.flags.is_lib = true;

    let mut bin_crate = CrateEntity::new(
        "bin1".to_string(),
        PathBuf::from("/test/bin1"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    bin_crate.flags.has_bin = true;

    workspace.add_crate(lib_crate);
    workspace.add_crate(bin_crate);

    let libs = workspace.lib_crates();
    assert_eq!(libs.len(), 1);
    assert_eq!(libs[0].name, "lib1");
}

#[test]
fn test_workspace_publishable_crates() {
    let mut workspace = Workspace::new(PathBuf::from("/test"));

    let mut publishable = CrateEntity::new(
        "pub1".to_string(),
        PathBuf::from("/test/pub1"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    publishable.flags.publish = true;

    let mut non_publishable = CrateEntity::new(
        "priv1".to_string(),
        PathBuf::from("/test/priv1"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    non_publishable.flags.publish = false;

    workspace.add_crate(publishable);
    workspace.add_crate(non_publishable);

    let publishable_crates = workspace.publishable_crates();
    assert_eq!(publishable_crates.len(), 1);
    assert_eq!(publishable_crates[0].name, "pub1");
}

#[test]
fn test_workspace_unpublished_crates() {
    let mut workspace = Workspace::new(PathBuf::from("/test"));

    let mut published = CrateEntity::new(
        "pub1".to_string(),
        PathBuf::from("/test/pub1"),
        SemanticVersion::parse("1.0.0").unwrap(),
        true,
    );
    published.flags.publish = true;

    let mut unpublished = CrateEntity::new(
        "pub2".to_string(),
        PathBuf::from("/test/pub2"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    unpublished.flags.publish = true;

    let mut non_publishable = CrateEntity::new(
        "priv1".to_string(),
        PathBuf::from("/test/priv1"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    non_publishable.flags.publish = false;

    workspace.add_crate(published);
    workspace.add_crate(unpublished);
    workspace.add_crate(non_publishable);

    let unpublished_crates = workspace.unpublished_crates();
    assert_eq!(unpublished_crates.len(), 1);
    assert_eq!(unpublished_crates[0].name, "pub2");
}

#[test]
fn test_workspace_publish_order_simple() {
    let mut workspace = Workspace::new(PathBuf::from("/test"));

    let crate_a = CrateEntity::new(
        "crate-a".to_string(),
        PathBuf::from("/test/a"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    let crate_b = CrateEntity::new(
        "crate-b".to_string(),
        PathBuf::from("/test/b"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );

    workspace.add_crate(crate_a);
    workspace.add_crate(crate_b);

    let order = workspace.publish_order();
    assert!(order.is_ok());
    let order = order.unwrap();
    assert_eq!(order.len(), 2);
}

#[test]
fn test_workspace_publish_order_with_dependencies() {
    let mut workspace = Workspace::new(PathBuf::from("/test"));

    // Add crates in order: a, b, c where b depends on a and c depends on b
    let crate_a = CrateEntity::new(
        "crate-a".to_string(),
        PathBuf::from("/test/a"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    let mut crate_b = CrateEntity::new(
        "crate-b".to_string(),
        PathBuf::from("/test/b"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    let mut crate_c = CrateEntity::new(
        "crate-c".to_string(),
        PathBuf::from("/test/c"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );

    // b depends on a
    let mut dep_b = Dependency::new("crate-a".to_string());
    dep_b.flags.is_workspace = true;
    crate_b.dependencies.push(dep_b);

    // c depends on b
    let mut dep_c = Dependency::new("crate-b".to_string());
    dep_c.flags.is_workspace = true;
    crate_c.dependencies.push(dep_c);

    workspace.add_crate(crate_a);
    workspace.add_crate(crate_b);
    workspace.add_crate(crate_c);

    let order = workspace.publish_order().unwrap();
    assert_eq!(order.len(), 3);

    // Verify all crates are in the order
    let order_names: Vec<_> = order.iter().map(|c| c.name.as_str()).collect();
    assert!(
        order_names.contains(&"crate-a"),
        "order should contain crate-a"
    );
    assert!(
        order_names.contains(&"crate-b"),
        "order should contain crate-b"
    );
    assert!(
        order_names.contains(&"crate-c"),
        "order should contain crate-c"
    );
}

#[test]
fn test_workspace_crates_to_publish() {
    let mut workspace = Workspace::new(PathBuf::from("/test"));

    let mut published = CrateEntity::new(
        "pub1".to_string(),
        PathBuf::from("/test/pub1"),
        SemanticVersion::parse("1.0.0").unwrap(),
        true,
    );
    published.flags.publish = true;

    let mut to_publish = CrateEntity::new(
        "pub2".to_string(),
        PathBuf::from("/test/pub2"),
        SemanticVersion::parse("1.0.0").unwrap(),
        false,
    );
    to_publish.flags.publish = true;

    workspace.add_crate(published);
    workspace.add_crate(to_publish);

    let crates = workspace.crates_to_publish();
    assert_eq!(crates.len(), 1);
    assert_eq!(crates[0].name, "pub2");
}

#[test]
fn test_workspace_with_shared_version() {
    let mut workspace = Workspace::new(PathBuf::from("/test"));
    workspace.workspace_version = Some(SemanticVersion::parse("1.0.0").unwrap());
    assert!(workspace.workspace_version.is_some());
}

#[test]
fn test_crate_flags_default() {
    let flags = CrateFlags::default();
    assert!(!flags.is_lib);
    assert!(!flags.has_bin);
    assert!(!flags.publish); // Default is false for bool
}
