# Tests for input-statnet.R - parse_statnet() function
# Coverage target: Comprehensive coverage for statnet input parsing

# =============================================================================
# Helper function to create test networks
# =============================================================================

# Helper to create a basic network matrix for testing
skip_coverage_tests()

create_test_adj_matrix <- function(n = 4, symmetric = TRUE, weighted = FALSE) {
  m <- matrix(0, nrow = n, ncol = n)
  # Add edges dynamically based on n
  if (n >= 2) m[1, 2] <- 1
  if (n >= 3) {
    m[1, 3] <- 1
    m[2, 3] <- 1
  }
  if (n >= 4) m[2, 4] <- 1

  if (symmetric) {
    m <- m + t(m)
  }

  if (weighted) {
    m[m > 0] <- runif(sum(m > 0), 0.1, 1.0)
    if (symmetric) {
      m[lower.tri(m)] <- t(m)[lower.tri(m)]
    }
  }

  rownames(m) <- LETTERS[1:n]
  colnames(m) <- LETTERS[1:n]
  m
}

# =============================================================================
# SECTION 1: Basic parse_statnet() functionality tests
# =============================================================================

test_that("parse_statnet errors when network package not available", {
  # This test verifies the error message when network is not installed

  # We can't easily test this without unloading the package, so we skip

  skip("Cannot easily test package unavailability")
})

test_that("parse_statnet errors on non-network object", {
  skip_if_not_installed("network")

  expect_error(
    parse_statnet(matrix(1:4, 2)),
    "must be a network object"
  )

  expect_error(
    parse_statnet(data.frame(a = 1:3)),
    "must be a network object"
  )

  expect_error(
    parse_statnet(list(foo = 1)),
    "must be a network object"
  )
})

test_that("parse_statnet returns correct structure", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix()
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_type(result, "list")
  expect_true("nodes" %in% names(result))
  expect_true("edges" %in% names(result))
  expect_true("directed" %in% names(result))
  expect_true("weights" %in% names(result))
})

test_that("parse_statnet correctly parses undirected network", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(symmetric = TRUE)
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_false(result$directed)
  expect_s3_class(result$nodes, "data.frame")
  expect_s3_class(result$edges, "data.frame")
})

test_that("parse_statnet correctly parses directed network", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(symmetric = FALSE)
  net <- network::network(mat, directed = TRUE)

  result <- parse_statnet(net)

  expect_true(result$directed)
})

test_that("parse_statnet returns correct number of nodes", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(n = 5)
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_equal(nrow(result$nodes), 5)
})

test_that("parse_statnet returns correct number of edges", {
  skip_if_not_installed("network")

  # Create a simple 3-node network with known edges
  mat <- matrix(c(0, 1, 0,
                  1, 0, 1,
                  0, 1, 0), nrow = 3, byrow = TRUE)
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  # For undirected: 1-2, 2-3 = 2 edges
  expect_equal(nrow(result$edges), 2)
})

# =============================================================================
# SECTION 2: Directed parameter override tests
# =============================================================================

test_that("parse_statnet respects directed = TRUE override", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(symmetric = TRUE)
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net, directed = TRUE)

  expect_true(result$directed)
})

test_that("parse_statnet respects directed = FALSE override", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(symmetric = FALSE)
  net <- network::network(mat, directed = TRUE)

  result <- parse_statnet(net, directed = FALSE)

  expect_false(result$directed)
})

test_that("parse_statnet uses network's directedness when directed = NULL", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix()

  # Test undirected
  net_undir <- network::network(mat, directed = FALSE)
  result_undir <- parse_statnet(net_undir, directed = NULL)
  expect_false(result_undir$directed)

  # Test directed
  net_dir <- network::network(mat, directed = TRUE)
  result_dir <- parse_statnet(net_dir, directed = NULL)
  expect_true(result_dir$directed)
})

# =============================================================================
# SECTION 3: Node labels and attributes tests
# =============================================================================

test_that("parse_statnet extracts vertex names", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(n = 3)
  net <- network::network(mat, directed = FALSE)
  network::set.vertex.attribute(net, "vertex.names", c("X", "Y", "Z"))

  result <- parse_statnet(net)

  expect_equal(result$nodes$label, c("X", "Y", "Z"))
})

test_that("parse_statnet generates numeric labels when names are NULL", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 1, 1, 0), nrow = 2)
  net <- network::network(mat, directed = FALSE)
  # Force NA vertex names
  network::set.vertex.attribute(net, "vertex.names", c(NA, NA))

  result <- parse_statnet(net)

  expect_equal(result$nodes$label, c("1", "2"))
})

test_that("parse_statnet transfers vertex attributes", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(n = 3)
  net <- network::network(mat, directed = FALSE)
  network::set.vertex.attribute(net, "color", c("red", "green", "blue"))
  network::set.vertex.attribute(net, "size", c(1, 2, 3))

  result <- parse_statnet(net)

  expect_equal(result$nodes$color, c("red", "green", "blue"))
  expect_equal(result$nodes$size, c(1, 2, 3))
})

test_that("parse_statnet excludes reserved vertex attributes", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(n = 3)
  net <- network::network(mat, directed = FALSE)
  network::set.vertex.attribute(net, "custom_attr", c("a", "b", "c"))

  result <- parse_statnet(net)

  # Should have custom attribute but not "na" or internal attrs
  expect_true("custom_attr" %in% names(result$nodes))
  expect_false("na" %in% names(result$nodes))
})

# =============================================================================
# SECTION 4: Edge weights tests
# =============================================================================

test_that("parse_statnet extracts edge weights", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 0.5, 0.5, 0), nrow = 2)
  net <- network::network(mat, directed = FALSE, ignore.eval = FALSE,
                          names.eval = "weight")

  result <- parse_statnet(net)

  expect_length(result$weights, 1)
  expect_equal(result$weights[1], 0.5)
})

test_that("parse_statnet uses weight of 1 when no weights", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 1, 1, 0), nrow = 2)
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_equal(result$weights, 1)
})

test_that("parse_statnet handles multiple weighted edges", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 0.3, 0.5,
                  0.3, 0, 0.7,
                  0.5, 0.7, 0), nrow = 3, byrow = TRUE)
  net <- network::network(mat, directed = FALSE, ignore.eval = FALSE,
                          names.eval = "weight")

  result <- parse_statnet(net)

  expect_length(result$weights, nrow(result$edges))
  expect_true(all(result$weights > 0))
})

# =============================================================================
# SECTION 5: Edge attributes tests
# =============================================================================

test_that("parse_statnet transfers edge attributes", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 1, 0, 0), nrow = 2)
  net <- network::network(mat, directed = TRUE)
  network::set.edge.attribute(net, "type", "friendship")

  result <- parse_statnet(net)

  expect_true("type" %in% names(result$edges))
  expect_equal(result$edges$type, "friendship")
})

test_that("parse_statnet excludes reserved edge attributes", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 1, 1, 0), nrow = 2)
  net <- network::network(mat, directed = FALSE)
  network::set.edge.attribute(net, "custom_edge_attr", "test_value")

  result <- parse_statnet(net)

  # Should have custom attribute
  expect_true("custom_edge_attr" %in% names(result$edges))
})

# =============================================================================
# SECTION 6: Empty network tests
# =============================================================================

test_that("parse_statnet handles empty network (no edges)", {
  skip_if_not_installed("network")

  mat <- matrix(0, nrow = 3, ncol = 3)
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_equal(nrow(result$nodes), 3)
  expect_equal(nrow(result$edges), 0)
  expect_length(result$weights, 0)
})

test_that("parse_statnet handles single node network", {
  skip_if_not_installed("network")

  mat <- matrix(0, nrow = 1, ncol = 1)
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_equal(nrow(result$nodes), 1)
  expect_equal(nrow(result$edges), 0)
})

# =============================================================================
# SECTION 7: Complex network tests
# =============================================================================

test_that("parse_statnet handles large network", {
  skip_if_not_installed("network")

  n <- 20
  mat <- matrix(0, nrow = n, ncol = n)
  # Create random edges
  set.seed(42)
  for (i in 1:(n-1)) {
    for (j in (i+1):n) {
      if (runif(1) < 0.3) {
        mat[i, j] <- 1
        mat[j, i] <- 1
      }
    }
  }
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_equal(nrow(result$nodes), n)
  expect_true(nrow(result$edges) > 0)
})

test_that("parse_statnet handles fully connected network", {
  skip_if_not_installed("network")

  n <- 4
  mat <- matrix(1, nrow = n, ncol = n)
  diag(mat) <- 0
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_equal(nrow(result$nodes), n)
  # Fully connected undirected: n*(n-1)/2 = 6 edges
  expect_equal(nrow(result$edges), 6)
})

test_that("parse_statnet handles self-loops", {
  skip_if_not_installed("network")

  mat <- matrix(c(1, 1, 1, 0), nrow = 2)
  net <- network::network(mat, directed = TRUE, loops = TRUE)

  result <- parse_statnet(net)

  # Should include self-loop
  self_loops <- result$edges[result$edges$from == result$edges$to, ]
  expect_true(nrow(self_loops) > 0)
})

# =============================================================================
# SECTION 8: Data frame structure tests
# =============================================================================

test_that("parse_statnet nodes data frame has required columns", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix()
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_true("id" %in% names(result$nodes))
  expect_true("label" %in% names(result$nodes))
})

test_that("parse_statnet edges data frame has required columns", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix()
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_true("from" %in% names(result$edges))
  expect_true("to" %in% names(result$edges))
  expect_true("weight" %in% names(result$edges))
})

test_that("parse_statnet edge indices match node indices", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(n = 4)
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  # All from/to indices should be valid node ids
  node_ids <- result$nodes$id
  expect_true(all(result$edges$from %in% node_ids))
  expect_true(all(result$edges$to %in% node_ids))
})

# =============================================================================
# SECTION 9: Bipartite network tests
# =============================================================================

test_that("parse_statnet handles bipartite network", {
  skip_if_not_installed("network")

  # Create bipartite adjacency matrix
  mat <- matrix(0, nrow = 5, ncol = 5)
  # Two groups: nodes 1-2 and nodes 3-5
  mat[1, 3] <- 1
  mat[1, 4] <- 1
  mat[2, 4] <- 1
  mat[2, 5] <- 1
  mat[3, 1] <- 1
  mat[4, 1] <- 1
  mat[4, 2] <- 1
  mat[5, 2] <- 1

  net <- network::network(mat, directed = FALSE, bipartite = 2)

  result <- parse_statnet(net)

  expect_equal(nrow(result$nodes), 5)
  expect_true(nrow(result$edges) > 0)
})

# =============================================================================
# SECTION 10: Mixed attribute types tests
# =============================================================================

test_that("parse_statnet handles numeric vertex attributes", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(n = 3)
  net <- network::network(mat, directed = FALSE)
  network::set.vertex.attribute(net, "value", c(1.5, 2.5, 3.5))

  result <- parse_statnet(net)

  expect_equal(result$nodes$value, c(1.5, 2.5, 3.5))
  expect_type(result$nodes$value, "double")
})

test_that("parse_statnet handles character vertex attributes", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(n = 3)
  net <- network::network(mat, directed = FALSE)
  network::set.vertex.attribute(net, "category", c("A", "B", "C"))

  result <- parse_statnet(net)

  expect_equal(result$nodes$category, c("A", "B", "C"))
})

test_that("parse_statnet handles logical vertex attributes", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(n = 3)
  net <- network::network(mat, directed = FALSE)
  network::set.vertex.attribute(net, "active", c(TRUE, FALSE, TRUE))

  result <- parse_statnet(net)

  expect_equal(result$nodes$active, c(TRUE, FALSE, TRUE))
})

# =============================================================================
# SECTION 11: Edge consistency tests
# =============================================================================

test_that("parse_statnet edges have consistent weights", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 0.5, 0.5, 0), nrow = 2)
  net <- network::network(mat, directed = FALSE, ignore.eval = FALSE,
                          names.eval = "weight")

  result <- parse_statnet(net)

  # edges$weight should match weights vector
  expect_equal(result$edges$weight, result$weights)
})

test_that("parse_statnet preserves edge order", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 1, 1,
                  0, 0, 1,
                  0, 0, 0), nrow = 3, byrow = TRUE)
  net <- network::network(mat, directed = TRUE)

  result <- parse_statnet(net)

  # Should have 3 edges for directed network
  expect_equal(nrow(result$edges), 3)
})

# =============================================================================
# SECTION 12: Integration with cograph tests
# =============================================================================

test_that("parse_statnet output works with cograph", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix()
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  # Should be valid input for cograph
  cn <- cograph(result)
  expect_s3_class(cn, "cograph_network")
})

test_that("parse_statnet with vertex names works with cograph", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(n = 3)
  net <- network::network(mat, directed = FALSE)
  network::set.vertex.attribute(net, "vertex.names", c("Alpha", "Beta", "Gamma"))

  result <- parse_statnet(net)
  cn <- cograph(result)

  expect_s3_class(cn, "cograph_network")
  expect_equal(get_labels(cn), c("Alpha", "Beta", "Gamma"))
})

# =============================================================================
# SECTION 13: Error handling edge cases
# =============================================================================

test_that("parse_statnet handles network with NA in matrix", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 1, NA, 0), nrow = 2)
  mat[is.na(mat)] <- 0  # Clean NAs before creating network
  net <- network::network(mat, directed = TRUE)

  result <- parse_statnet(net)

  expect_type(result, "list")
  expect_true(nrow(result$nodes) > 0)
})

test_that("parse_statnet handles string input error", {
  skip_if_not_installed("network")

  expect_error(
    parse_statnet("not a network"),
    "must be a network object"
  )
})

test_that("parse_statnet handles NULL input error", {
  skip_if_not_installed("network")

  expect_error(
    parse_statnet(NULL),
    "must be a network object"
  )
})

# =============================================================================
# SECTION 14: Network property preservation tests
# =============================================================================

test_that("parse_statnet preserves network size", {
  skip_if_not_installed("network")

  for (n in c(2, 5, 10)) {
    mat <- matrix(0, nrow = n, ncol = n)
    if (n > 1) {
      mat[1, 2] <- 1
      mat[2, 1] <- 1
    }
    net <- network::network(mat, directed = FALSE)

    result <- parse_statnet(net)

    expect_equal(nrow(result$nodes), n)
  }
})

test_that("parse_statnet edge count matches network edge count", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix()
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_equal(nrow(result$edges), network::network.edgecount(net))
})

# =============================================================================
# SECTION 15: Special character handling tests
# =============================================================================

test_that("parse_statnet handles special characters in vertex names", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 1, 1, 0), nrow = 2)
  net <- network::network(mat, directed = FALSE)
  network::set.vertex.attribute(net, "vertex.names", c("Node-1", "Node_2"))

  result <- parse_statnet(net)

  expect_equal(result$nodes$label, c("Node-1", "Node_2"))
})

test_that("parse_statnet handles unicode in vertex names", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 1, 1, 0), nrow = 2)
  net <- network::network(mat, directed = FALSE)
  network::set.vertex.attribute(net, "vertex.names", c("Alpha", "Beta"))

  result <- parse_statnet(net)

  expect_equal(result$nodes$label, c("Alpha", "Beta"))
})

# =============================================================================
# SECTION 16: Negative and zero weight tests
# =============================================================================

test_that("parse_statnet handles negative weights", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, -0.5, -0.5, 0), nrow = 2)
  net <- network::network(mat, directed = FALSE, ignore.eval = FALSE,
                          names.eval = "weight")

  result <- parse_statnet(net)

  expect_true(any(result$weights < 0))
})

test_that("parse_statnet handles mixed positive and negative weights", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 0.5, -0.3,
                  0.5, 0, 0.7,
                  -0.3, 0.7, 0), nrow = 3, byrow = TRUE)
  net <- network::network(mat, directed = FALSE, ignore.eval = FALSE,
                          names.eval = "weight")

  result <- parse_statnet(net)

  expect_true(any(result$weights > 0))
  expect_true(any(result$weights < 0))
})

# =============================================================================
# SECTION 17: Multiple edge attribute tests
# =============================================================================

test_that("parse_statnet handles multiple edge attributes", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 1, 1, 0), nrow = 2)
  net <- network::network(mat, directed = FALSE)
  network::set.edge.attribute(net, "type", "friendship")
  network::set.edge.attribute(net, "strength", 0.8)
  network::set.edge.attribute(net, "active", TRUE)

  result <- parse_statnet(net)

  expect_true("type" %in% names(result$edges))
  expect_true("strength" %in% names(result$edges))
  expect_true("active" %in% names(result$edges))
})

# =============================================================================
# SECTION 18: Directed network edge tests
# =============================================================================

test_that("parse_statnet correctly counts directed edges", {
  skip_if_not_installed("network")

  # Asymmetric matrix - directed edges
  mat <- matrix(c(0, 1, 0,
                  0, 0, 1,
                  1, 0, 0), nrow = 3, byrow = TRUE)
  net <- network::network(mat, directed = TRUE)

  result <- parse_statnet(net)

  expect_true(result$directed)
  expect_equal(nrow(result$edges), 3)  # 1->2, 2->3, 3->1
})

test_that("parse_statnet handles bidirectional edges in directed network", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 1, 1, 0), nrow = 2)
  net <- network::network(mat, directed = TRUE)

  result <- parse_statnet(net)

  expect_true(result$directed)
  # Should have 2 edges: 1->2 and 2->1
  expect_equal(nrow(result$edges), 2)
})

# =============================================================================
# SECTION 19: Sparse network tests
# =============================================================================

test_that("parse_statnet handles very sparse network", {
  skip_if_not_installed("network")

  n <- 10
  mat <- matrix(0, nrow = n, ncol = n)
  mat[1, 2] <- 1
  mat[2, 1] <- 1
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_equal(nrow(result$nodes), n)
  expect_equal(nrow(result$edges), 1)
})

test_that("parse_statnet handles network with isolated nodes", {
  skip_if_not_installed("network")

  mat <- matrix(0, nrow = 5, ncol = 5)
  mat[1, 2] <- 1
  mat[2, 1] <- 1
  # Nodes 3, 4, 5 are isolated
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_equal(nrow(result$nodes), 5)
  expect_equal(nrow(result$edges), 1)
})

# =============================================================================
# SECTION 20: Dense network tests
# =============================================================================

test_that("parse_statnet handles dense weighted network", {
  skip_if_not_installed("network")

  n <- 5
  set.seed(123)
  mat <- matrix(runif(n * n), nrow = n)
  mat <- (mat + t(mat)) / 2  # Make symmetric
  diag(mat) <- 0

  net <- network::network(mat, directed = FALSE, ignore.eval = FALSE,
                          names.eval = "weight")

  result <- parse_statnet(net)

  expect_equal(nrow(result$nodes), n)
  # Fully connected: n*(n-1)/2 = 10 edges
  expect_equal(nrow(result$edges), 10)
})

# =============================================================================
# SECTION 21: Network metadata tests
# =============================================================================

test_that("parse_statnet works with named network", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix(n = 3)
  net <- network::network(mat, directed = FALSE)

  result <- parse_statnet(net)

  expect_type(result, "list")
  expect_equal(length(result), 4)  # nodes, edges, directed, weights
})

# =============================================================================
# SECTION 22: Reproducibility tests
# =============================================================================

test_that("parse_statnet is deterministic", {
  skip_if_not_installed("network")

  mat <- create_test_adj_matrix()
  net <- network::network(mat, directed = FALSE)

  result1 <- parse_statnet(net)
  result2 <- parse_statnet(net)

  expect_identical(result1$nodes, result2$nodes)
  expect_identical(result1$edges, result2$edges)
  expect_identical(result1$directed, result2$directed)
  expect_identical(result1$weights, result2$weights)
})

test_that("parse_statnet gives same result with same input", {
  skip_if_not_installed("network")

  mat <- matrix(c(0, 1, 1, 0), nrow = 2)

  net1 <- network::network(mat, directed = FALSE)
  net2 <- network::network(mat, directed = FALSE)

  result1 <- parse_statnet(net1)
  result2 <- parse_statnet(net2)

  expect_equal(nrow(result1$nodes), nrow(result2$nodes))
  expect_equal(nrow(result1$edges), nrow(result2$edges))
})
