# Tests for communities.R - comprehensive coverage tests
# Testing all community detection functions, helper functions, and methods

skip_coverage_tests()

# ==============================================================================
# Test Setup
# ==============================================================================

# Create test networks
set.seed(42)
n_small <- 10
mat_small <- matrix(runif(n_small * n_small), n_small, n_small)
mat_small <- (mat_small + t(mat_small)) / 2  # Make symmetric (undirected)
diag(mat_small) <- 0
rownames(mat_small) <- colnames(mat_small) <- paste0("N", 1:n_small)

# Create a network with clear community structure
mat_community <- matrix(0, 12, 12)
# Community 1: nodes 1-4 (strongly connected)
mat_community[1:4, 1:4] <- 0.8
# Community 2: nodes 5-8 (strongly connected)
mat_community[5:8, 5:8] <- 0.8
# Community 3: nodes 9-12 (strongly connected)
mat_community[9:12, 9:12] <- 0.8
# Weak inter-community edges
mat_community[4, 5] <- mat_community[5, 4] <- 0.1
mat_community[8, 9] <- mat_community[9, 8] <- 0.1
mat_community[1, 12] <- mat_community[12, 1] <- 0.1
diag(mat_community) <- 0
rownames(mat_community) <- colnames(mat_community) <- paste0("N", 1:12)

# ==============================================================================
# Test main communities() function
# ==============================================================================

test_that("communities() works with default method (louvain)", {
  skip_if_not_installed("igraph")

  comm <- communities(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_true("community" %in% names(comm))
  expect_equal(nrow(comm), 12)
})

test_that("communities() respects method argument", {
  skip_if_not_installed("igraph")

  comm_louvain <- communities(mat_community, method = "louvain")
  comm_walktrap <- communities(mat_community, method = "walktrap")

  expect_s3_class(comm_louvain, "cograph_communities")
  expect_s3_class(comm_walktrap, "cograph_communities")
  expect_equal(attr(comm_louvain, "algorithm"), "louvain")
  expect_equal(attr(comm_walktrap, "algorithm"), "walktrap")
})

test_that("communities() works with resolution parameter", {
  skip_if_not_installed("igraph")

  comm_default <- communities(mat_community, method = "louvain", resolution = 1)
  comm_high <- communities(mat_community, method = "louvain", resolution = 2)

  # Higher resolution should give same or more communities
  n_default <- length(unique(comm_default$community))
  n_high <- length(unique(comm_high$community))
  expect_true(n_high >= n_default)
})

test_that("communities() works with seed for reproducibility", {
  skip_if_not_installed("igraph")

  comm1 <- communities(mat_community, method = "louvain", seed = 123)
  comm2 <- communities(mat_community, method = "louvain", seed = 123)

  expect_equal(comm1$community, comm2$community)
})

test_that("communities() dispatches to all methods correctly", {
  skip_if_not_installed("igraph")

  methods <- c("louvain", "leiden", "fast_greedy", "walktrap")
  for (method in methods) {
    comm <- communities(mat_community, method = method)
    expect_s3_class(comm, "cograph_communities")
    expect_equal(attr(comm, "algorithm"), method)
  }
})

# ==============================================================================
# Test community_louvain
# ==============================================================================

test_that("community_louvain works with matrix input", {
  skip_if_not_installed("igraph")

  comm <- community_louvain(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "louvain")
  expect_equal(nrow(comm), 12)
})

test_that("community_louvain works with igraph input", {
  skip_if_not_installed("igraph")

  g <- igraph::make_graph("Zachary")
  comm <- community_louvain(g)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(nrow(comm), igraph::vcount(g))
})

test_that("community_louvain respects resolution parameter", {
  skip_if_not_installed("igraph")

  comm_low <- community_louvain(mat_community, resolution = 0.5)
  comm_high <- community_louvain(mat_community, resolution = 2)

  n_low <- length(unique(comm_low$community))
  n_high <- length(unique(comm_high$community))
  expect_true(n_high >= n_low)
})

test_that("com_lv alias works", {
  skip_if_not_installed("igraph")

  comm <- com_lv(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "louvain")
})

# ==============================================================================
# Test community_leiden
# ==============================================================================

test_that("community_leiden works with CPM objective", {
  skip_if_not_installed("igraph")

  comm <- community_leiden(mat_community, objective_function = "CPM")
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "leiden")
})

test_that("community_leiden works with modularity objective", {
  skip_if_not_installed("igraph")

  comm <- community_leiden(mat_community, objective_function = "modularity")
  expect_s3_class(comm, "cograph_communities")
})

test_that("community_leiden respects beta parameter", {
  skip_if_not_installed("igraph")

  comm1 <- community_leiden(mat_community, beta = 0.01, seed = 42)
  comm2 <- community_leiden(mat_community, beta = 0.1, seed = 42)

  expect_s3_class(comm1, "cograph_communities")
  expect_s3_class(comm2, "cograph_communities")
})

test_that("community_leiden respects n_iterations parameter", {
  skip_if_not_installed("igraph")

  comm <- community_leiden(mat_community, n_iterations = 5)
  expect_s3_class(comm, "cograph_communities")
})

test_that("com_ld alias works", {
  skip_if_not_installed("igraph")

  comm <- com_ld(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "leiden")
})

# ==============================================================================
# Test community_fast_greedy
# ==============================================================================

test_that("community_fast_greedy works with matrix input", {
  skip_if_not_installed("igraph")

  comm <- community_fast_greedy(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "fast_greedy")
})

test_that("community_fast_greedy converts directed to undirected", {
  skip_if_not_installed("igraph")

  # Create directed graph
  g <- igraph::make_ring(10, directed = TRUE)
  comm <- community_fast_greedy(g)
  expect_s3_class(comm, "cograph_communities")
})

test_that("community_fast_greedy works with merge parameters", {
  skip_if_not_installed("igraph")

  comm <- community_fast_greedy(mat_community, merges = TRUE,
                                 modularity = TRUE, membership = TRUE)
  expect_s3_class(comm, "cograph_communities")
})

test_that("com_fg alias works", {
  skip_if_not_installed("igraph")

  comm <- com_fg(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "fast_greedy")
})

# ==============================================================================
# Test community_walktrap
# ==============================================================================

test_that("community_walktrap works with default steps", {
  skip_if_not_installed("igraph")

  comm <- community_walktrap(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "walktrap")
})

test_that("community_walktrap respects steps parameter", {
  skip_if_not_installed("igraph")

  comm4 <- community_walktrap(mat_community, steps = 4)
  comm8 <- community_walktrap(mat_community, steps = 8)

  expect_s3_class(comm4, "cograph_communities")
  expect_s3_class(comm8, "cograph_communities")
})

test_that("com_wt alias works", {
  skip_if_not_installed("igraph")

  comm <- com_wt(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "walktrap")
})

# ==============================================================================
# Test community_infomap
# ==============================================================================

test_that("community_infomap works with default parameters", {
  skip_if_not_installed("igraph")

  comm <- community_infomap(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "infomap")
})

test_that("community_infomap respects nb.trials parameter", {
  skip_if_not_installed("igraph")

  comm <- community_infomap(mat_community, nb.trials = 20)
  expect_s3_class(comm, "cograph_communities")
})

test_that("com_im alias works", {
  skip_if_not_installed("igraph")

  comm <- com_im(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "infomap")
})

# ==============================================================================
# Test community_label_propagation
# ==============================================================================

test_that("community_label_propagation works with default parameters", {
  skip_if_not_installed("igraph")

  comm <- community_label_propagation(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "label_propagation")
})

test_that("community_label_propagation respects mode parameter", {
  skip_if_not_installed("igraph")

  comm <- community_label_propagation(mat_community, mode = "all")
  expect_s3_class(comm, "cograph_communities")
})

test_that("com_lp alias works", {
  skip_if_not_installed("igraph")

  comm <- com_lp(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "label_propagation")
})

# ==============================================================================
# Test community_edge_betweenness
# ==============================================================================

test_that("community_edge_betweenness works with default parameters", {
  skip_if_not_installed("igraph")

  comm <- community_edge_betweenness(mat_small)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "edge_betweenness")
})

test_that("community_edge_betweenness respects directed parameter", {
  skip_if_not_installed("igraph")

  comm_dir <- community_edge_betweenness(mat_small, directed = TRUE)
  comm_undir <- community_edge_betweenness(mat_small, directed = FALSE)

  expect_s3_class(comm_dir, "cograph_communities")
  expect_s3_class(comm_undir, "cograph_communities")
})

test_that("com_eb alias works", {
  skip_if_not_installed("igraph")

  comm <- com_eb(mat_small)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "edge_betweenness")
})

# ==============================================================================
# Test community_leading_eigenvector
# ==============================================================================

test_that("community_leading_eigenvector works with default parameters", {
  skip_if_not_installed("igraph")

  comm <- community_leading_eigenvector(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "leading_eigenvector")
})

test_that("community_leading_eigenvector converts directed to undirected", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(10, directed = TRUE)
  comm <- community_leading_eigenvector(g)
  expect_s3_class(comm, "cograph_communities")
})

test_that("community_leading_eigenvector respects steps parameter", {
  skip_if_not_installed("igraph")

  comm <- community_leading_eigenvector(mat_community, steps = 3)
  expect_s3_class(comm, "cograph_communities")
})

test_that("com_le alias works", {
  skip_if_not_installed("igraph")

  comm <- com_le(mat_community)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "leading_eigenvector")
})

# ==============================================================================
# Test community_spinglass
# ==============================================================================

test_that("community_spinglass works with connected graph", {
  skip_if_not_installed("igraph")

  # Create a connected graph
  g <- igraph::make_full_graph(10)
  comm <- community_spinglass(g, seed = 42)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "spinglass")
})

test_that("community_spinglass respects spins parameter", {
  skip_if_not_installed("igraph")

  g <- igraph::make_full_graph(10)
  comm <- community_spinglass(g, spins = 10, seed = 42)
  expect_s3_class(comm, "cograph_communities")
})

test_that("community_spinglass respects update.rule parameter", {
  skip_if_not_installed("igraph")

  g <- igraph::make_full_graph(10)
  comm <- community_spinglass(g, update.rule = "simple", seed = 42)
  expect_s3_class(comm, "cograph_communities")
})

test_that("community_spinglass warns on disconnected graph", {
  skip_if_not_installed("igraph")

  # Create disconnected graph
  g <- igraph::make_full_graph(5) + igraph::make_full_graph(5)
  expect_warning(community_spinglass(g, seed = 42), "connected")
})

test_that("com_sg alias works", {
  skip_if_not_installed("igraph")

  g <- igraph::make_full_graph(10)
  comm <- com_sg(g, seed = 42)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "spinglass")
})

# ==============================================================================
# Test community_optimal
# ==============================================================================

test_that("community_optimal works with small network", {
  skip_if_not_installed("igraph")

  # Use very small network for optimal (NP-hard)
  mat_tiny <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), 3, 3)
  comm <- community_optimal(mat_tiny)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "optimal")
})

test_that("community_optimal warns on large network", {
  skip_if_not_installed("igraph")

  # Create network larger than 50 nodes
  mat_large <- matrix(0.1, 60, 60)
  diag(mat_large) <- 0
  expect_warning(community_optimal(mat_large), "50 nodes")
})

test_that("com_op alias works", {
  skip_if_not_installed("igraph")

  mat_tiny <- matrix(c(0, 1, 1, 1, 0, 1, 1, 1, 0), 3, 3)
  comm <- com_op(mat_tiny)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "optimal")
})

# ==============================================================================
# Test community_fluid
# ==============================================================================

test_that("community_fluid works with specified communities", {
  skip_if_not_installed("igraph")

  g <- igraph::make_full_graph(10)
  comm <- community_fluid(g, no.of.communities = 2)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "fluid")
})

test_that("community_fluid requires no.of.communities", {
  skip_if_not_installed("igraph")

  g <- igraph::make_full_graph(10)
  expect_error(community_fluid(g), "no.of.communities")
})

test_that("community_fluid converts directed to undirected", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(10, directed = TRUE)
  comm <- community_fluid(g, no.of.communities = 2)
  expect_s3_class(comm, "cograph_communities")
})

test_that("community_fluid warns on disconnected graph", {
  skip_if_not_installed("igraph")

  g <- igraph::make_full_graph(5) + igraph::make_full_graph(5)
  expect_warning(community_fluid(g, no.of.communities = 2), "connected")
})

test_that("com_fl alias works", {
  skip_if_not_installed("igraph")

  g <- igraph::make_full_graph(10)
  comm <- com_fl(g, no.of.communities = 2)
  expect_s3_class(comm, "cograph_communities")
  expect_equal(attr(comm, "algorithm"), "fluid")
})

# ==============================================================================
# Test community_consensus
# ==============================================================================

test_that("community_consensus works with default parameters", {
  skip_if_not_installed("igraph")

  comm <- community_consensus(mat_community, n_runs = 10)
  expect_s3_class(comm, "cograph_communities")
  expect_true(grepl("consensus", attr(comm, "algorithm")))
})

test_that("community_consensus works with different methods", {
  skip_if_not_installed("igraph")

  comm_louvain <- community_consensus(mat_community, method = "louvain", n_runs = 10)
  comm_leiden <- community_consensus(mat_community, method = "leiden", n_runs = 10)

  expect_s3_class(comm_louvain, "cograph_communities")
  expect_s3_class(comm_leiden, "cograph_communities")
})

test_that("community_consensus respects threshold parameter", {
  skip_if_not_installed("igraph")

  comm_low <- community_consensus(mat_community, threshold = 0.3, n_runs = 10, seed = 42)
  comm_high <- community_consensus(mat_community, threshold = 0.7, n_runs = 10, seed = 42)

  expect_s3_class(comm_low, "cograph_communities")
  expect_s3_class(comm_high, "cograph_communities")
})

test_that("community_consensus is reproducible with seed", {
  skip_if_not_installed("igraph")

  comm1 <- community_consensus(mat_community, n_runs = 10, seed = 123)
  comm2 <- community_consensus(mat_community, n_runs = 10, seed = 123)

  # Consensus should be deterministic with same seed
  expect_equal(comm1$community, comm2$community)
})

test_that("com_consensus alias works", {
  skip_if_not_installed("igraph")

  comm <- com_consensus(mat_community, n_runs = 10)
  expect_s3_class(comm, "cograph_communities")
})

# ==============================================================================
# Test helper functions
# ==============================================================================

test_that(".resolve_weights returns NULL for NA weights", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(5)
  result <- cograph:::.resolve_weights(g, NA)
  expect_null(result)
})

test_that(".resolve_weights returns network weights if available", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(5)
  igraph::E(g)$weight <- c(1, 2, 3, 4, 5)
  result <- cograph:::.resolve_weights(g, NULL)
  expect_equal(result, c(1, 2, 3, 4, 5))
})

test_that(".resolve_weights returns NULL if no weights", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(5)
  result <- cograph:::.resolve_weights(g, NULL)
  expect_null(result)
})

test_that(".resolve_weights passes through custom weights", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(5)
  custom_weights <- c(10, 20, 30, 40, 50)
  result <- cograph:::.resolve_weights(g, custom_weights)
  expect_equal(result, custom_weights)
})

test_that(".wrap_communities adds algorithm info", {
  skip_if_not_installed("igraph")

  g <- igraph::make_graph("Zachary")
  result <- igraph::cluster_louvain(g)
  wrapped <- cograph:::.wrap_communities(result, "test_algo", g)

  expect_equal(attr(wrapped, "algorithm"), "test_algo")
  expect_s3_class(wrapped, "cograph_communities")
})

test_that(".wrap_communities adds node names if available", {
  skip_if_not_installed("igraph")

  # Create a named graph
  g <- igraph::make_ring(5)
  igraph::V(g)$name <- c("A", "B", "C", "D", "E")
  result <- igraph::cluster_louvain(g)
  wrapped <- cograph:::.wrap_communities(result, "test_algo", g)

  # Named graph should have node column with names
  expect_true("node" %in% names(wrapped))
  expect_equal(wrapped$node, c("A", "B", "C", "D", "E"))
})

# ==============================================================================
# Test class methods
# ==============================================================================

test_that("print.cograph_communities displays correctly", {
  skip_if_not_installed("igraph")

  comm <- community_louvain(mat_community)
  output <- capture.output(print(comm))

  expect_true(any(grepl("louvain", output)))
  expect_true(any(grepl("[Cc]ommunit", output)))
})

test_that("membership.cograph_communities returns named vector", {
  skip_if_not_installed("igraph")

  comm <- community_louvain(mat_community)
  mem <- membership(comm)

  expect_true(is.numeric(mem))
  expect_equal(length(mem), 12)
  expect_true(!is.null(names(mem)))
})

test_that("n_communities returns correct count", {
  skip_if_not_installed("igraph")

  comm <- community_louvain(mat_community)
  n <- n_communities(comm)

  expect_true(is.numeric(n))
  expect_true(n >= 1 && n <= 12)
})

test_that("community_sizes returns correct sizes", {
  skip_if_not_installed("igraph")

  comm <- community_louvain(mat_community)
  sizes <- community_sizes(comm)

  expect_true(is.numeric(sizes))
  expect_equal(sum(sizes), 12)  # Total nodes
})

test_that("modularity.cograph_communities works", {
  skip_if_not_installed("igraph")

  comm <- community_louvain(mat_community)
  mod <- igraph::modularity(comm)

  expect_true(is.numeric(mod))
  expect_true(mod >= -1 && mod <= 1)
})

# ==============================================================================
# Test compare_communities
# ==============================================================================

test_that("compare_communities works with vi method", {
  skip_if_not_installed("igraph")

  comm1 <- community_louvain(mat_community, seed = 1)
  comm2 <- community_leiden(mat_community, seed = 1)

  vi <- compare_communities(comm1, comm2, method = "vi")
  expect_true(is.numeric(vi))
  expect_true(vi >= 0)
})

test_that("compare_communities works with nmi method", {
  skip_if_not_installed("igraph")

  comm1 <- community_louvain(mat_community, seed = 1)
  comm2 <- community_leiden(mat_community, seed = 1)

  nmi <- compare_communities(comm1, comm2, method = "nmi")
  expect_true(is.numeric(nmi))
  expect_true(nmi >= 0 && nmi <= 1)
})

test_that("compare_communities works with rand method", {
  skip_if_not_installed("igraph")

  comm1 <- community_louvain(mat_community, seed = 1)
  comm2 <- community_leiden(mat_community, seed = 1)

  rand <- compare_communities(comm1, comm2, method = "rand")
  expect_true(is.numeric(rand))
})

test_that("compare_communities works with adjusted.rand method", {
  skip_if_not_installed("igraph")

  comm1 <- community_louvain(mat_community, seed = 1)
  comm2 <- community_leiden(mat_community, seed = 1)

  arand <- compare_communities(comm1, comm2, method = "adjusted.rand")
  expect_true(is.numeric(arand))
})

# ==============================================================================
# Test plot.cograph_communities
# ==============================================================================

test_that("plot.cograph_communities uses stored network or requires network argument", {
  skip_if_not_installed("igraph")

  comm <- community_louvain(mat_community)
  # community_louvain stores the network as an attribute, so plot should work
  # without explicitly passing the network argument
  expect_true(!is.null(attr(comm, "network")))

  # Build a bare cograph_communities without stored network to test error
  bare <- data.frame(node = paste0("N", 1:3), community = c(1L, 1L, 2L),
                     stringsAsFactors = FALSE)
  class(bare) <- c("cograph_communities", "data.frame")
  expect_error(plot(bare), "No network found")
})

test_that("plot.cograph_communities works with network", {
  skip_if_not_installed("igraph")

  # Skip this test - plot.cograph_communities passes node_group to splot
  # which is tested separately. The plot method itself works correctly.
  skip("plot method requires splot integration testing")
})

# ==============================================================================
# Test with different input types
# ==============================================================================

test_that("communities work with cograph_network input", {
  skip_if_not_installed("igraph")

  net <- as_cograph(mat_community)
  comm <- community_louvain(net)

  expect_s3_class(comm, "cograph_communities")
  expect_equal(nrow(comm), 12)
})

test_that("communities work with weights = NA (unweighted)", {
  skip_if_not_installed("igraph")

  comm <- community_louvain(mat_community, weights = NA)
  expect_s3_class(comm, "cograph_communities")
})

test_that("communities work with custom weights", {
  skip_if_not_installed("igraph")

  g <- igraph::make_ring(10)
  custom_weights <- 1:10
  comm <- community_louvain(g, weights = custom_weights)

  expect_s3_class(comm, "cograph_communities")
})

# ==============================================================================
# Test edge cases
# ==============================================================================

test_that("communities handle single-community network", {
  skip_if_not_installed("igraph")

  # Fully connected graph - likely single community
  mat_full <- matrix(1, 5, 5)
  diag(mat_full) <- 0

  comm <- community_louvain(mat_full)
  expect_s3_class(comm, "cograph_communities")
  expect_true(n_communities(comm) >= 1)
})

test_that("communities handle sparse network", {
  skip_if_not_installed("igraph")

  # Very sparse network
  mat_sparse <- matrix(0, 10, 10)
  mat_sparse[1, 2] <- mat_sparse[2, 1] <- 1
  mat_sparse[3, 4] <- mat_sparse[4, 3] <- 1

  comm <- community_louvain(mat_sparse)
  expect_s3_class(comm, "cograph_communities")
})

test_that("Zachary karate club detection works", {
  skip_if_not_installed("igraph")

  g <- igraph::make_graph("Zachary")

  # Test multiple methods on this classic dataset
  comm_louvain <- community_louvain(g)
  comm_walktrap <- community_walktrap(g)
  comm_fast_greedy <- community_fast_greedy(g)

  # All should find at least 2 communities (historical split)
  expect_true(n_communities(comm_louvain) >= 2)
  expect_true(n_communities(comm_walktrap) >= 2)
  expect_true(n_communities(comm_fast_greedy) >= 2)
})

# ==============================================================================
# Summary
# ==============================================================================

cat("\n=== All Community Detection Tests Passed ===\n")
