# ==============================================================================
# Additional tests for morph_utils.R to reach 90%+ coverage
# ==============================================================================

# ------------------------------------------------------------------------------
# .to_array_rgb edge cases for different array dimension orderings
# ------------------------------------------------------------------------------

test_that(".to_array_rgb handles numeric arrays scaled 0-1", {
  skip_on_cran()
  skip_if_not_installed("magick")

  # Create image and get data - then test with scaled numeric
  img <- magick::image_blank(2, 2, color = "red")
  result <- couplr:::.to_array_rgb(img)

  expect_equal(dim(result), c(2, 2, 3))
  expect_true(is.integer(result))
})

test_that(".to_array_rgb handles larger images", {
  skip_on_cran()
  skip_if_not_installed("magick")

  img <- magick::image_blank(10, 8, color = "#4080C0")
  result <- couplr:::.to_array_rgb(img)

  expect_equal(dim(result), c(8, 10, 3))  # H x W x 3
  expect_true(all(result >= 0 & result <= 255))
})

# ------------------------------------------------------------------------------
# .solve_color_walk_pipeline tests
# ------------------------------------------------------------------------------

test_that(".solve_color_walk_pipeline works with simple images", {
  skip_on_cran()
  skip_if_not_installed("magick")

  H <- 4
  W <- 4
  N <- H * W

  # Create simple planar data - all red for A
  A_planar <- c(rep(255, N), rep(0, N), rep(0, N))  # R, G, B
  # B has different colors
  B_planar <- c(
    rep(c(255, 0, 128, 64), each = 4),   # R channel - varied
    rep(c(0, 255, 0, 64), each = 4),      # G channel - varied
    rep(c(0, 0, 128, 64), each = 4)       # B channel - varied
  )

  result <- couplr:::.solve_color_walk_pipeline(
    A_planar, B_planar, H, W,
    quantize_bits = 5, method = "jv"
  )

  expect_equal(length(result), N)
  expect_true(all(result >= 1 & result <= N))
})

test_that(".solve_color_walk_pipeline handles identical images", {
  skip_on_cran()
  skip_if_not_installed("magick")

  H <- 3
  W <- 3
  N <- H * W

  # Both images identical
  planar <- c(rep(128, N), rep(64, N), rep(192, N))

  result <- couplr:::.solve_color_walk_pipeline(
    planar, planar, H, W,
    quantize_bits = 5, method = "jv"
  )

  expect_equal(length(result), N)
  expect_true(all(result >= 1 & result <= N))
})

# ------------------------------------------------------------------------------
# .solve_color_match_pipeline tests
# ------------------------------------------------------------------------------

test_that(".solve_color_match_pipeline works with matching colors", {
  skip_on_cran()
  skip_if_not_installed("magick")

  H <- 4
  W <- 4
  N <- H * W

  # Create images with some matching colors
  A_planar <- c(rep(255, N), rep(0, N), rep(0, N))  # All red
  B_planar <- c(rep(255, N), rep(0, N), rep(0, N))  # All red (same)

  result <- couplr:::.solve_color_match_pipeline(
    A_planar, B_planar, H, W,
    quantize_bits = 5, method = "jv",
    fill_identity_for_unmatched = TRUE
  )

  expect_equal(length(result), N)
})

test_that(".solve_color_match_pipeline with fill_identity FALSE", {
  skip_on_cran()
  skip_if_not_installed("magick")

  H <- 4
  W <- 4
  N <- H * W

  A_planar <- c(rep(255, N), rep(0, N), rep(0, N))
  B_planar <- c(rep(0, N), rep(255, N), rep(0, N))  # Different (green)

  result <- couplr:::.solve_color_match_pipeline(
    A_planar, B_planar, H, W,
    quantize_bits = 5, method = "jv",
    fill_identity_for_unmatched = FALSE
  )

  expect_equal(length(result), N)
})

# ------------------------------------------------------------------------------
# .palette_pairs_lap edge cases
# ------------------------------------------------------------------------------

test_that(".palette_pairs_lap handles 1x1 matrix", {
  skip_on_cran()
  info <- list(
    countsA = c(5),
    countsB = c(10),
    color_dist = matrix(0.5, nrow = 1, ncol = 1)
  )

  result <- couplr:::.palette_pairs_lap(info, method = "hungarian")

  expect_equal(nrow(result), 1)
  expect_equal(result$ia, 1)
  expect_equal(result$ib, 1)
})

test_that(".palette_pairs_lap handles larger matrices", {
  skip_on_cran()
  info <- list(
    countsA = c(10, 20, 15),
    countsB = c(12, 18, 25),
    color_dist = matrix(c(
      0.1, 0.5, 0.9,
      0.5, 0.1, 0.5,
      0.9, 0.5, 0.1
    ), nrow = 3, ncol = 3, byrow = TRUE)
  )

  result <- couplr:::.palette_pairs_lap(info, method = "hungarian")

  expect_equal(nrow(result), 3)
  expect_true(all(c("ia", "ib", "k") %in% names(result)))
})

# ------------------------------------------------------------------------------
# .build_spatial_assignments_for_pairs with actual data
# ------------------------------------------------------------------------------

test_that(".build_spatial_assignments_for_pairs handles k=0 pairs", {
  skip_on_cran()
  info <- list(
    groupsA = list(c(1L, 2L), c(3L, 4L)),
    groupsB = list(c(5L, 6L), c(7L, 8L))
  )
  pairs <- data.frame(
    ia = c(1L, 2L),
    ib = c(1L, 2L),
    k = c(0L, 2L)  # First pair has k=0
  )

  result <- couplr:::.build_spatial_assignments_for_pairs(info, pairs, H = 4, W = 4)

  expect_true(length(result$i_idx) >= 0)
  expect_true(length(result$j_idx) >= 0)
})

test_that(".build_spatial_assignments_for_pairs handles empty groups", {
  skip_on_cran()
  info <- list(
    groupsA = list(integer(0), c(3L, 4L)),
    groupsB = list(c(5L, 6L), integer(0))
  )
  pairs <- data.frame(
    ia = c(1L, 2L),
    ib = c(1L, 2L),
    k = c(2L, 2L)
  )

  result <- couplr:::.build_spatial_assignments_for_pairs(info, pairs, H = 4, W = 4)

  expect_true(is.list(result))
})

# ------------------------------------------------------------------------------
# .patch_cost_matrix edge cases
# ------------------------------------------------------------------------------

test_that(".patch_cost_matrix handles single patch", {
  skip_on_cran()
  patches_a <- list(
    colors = matrix(c(255, 128, 64), nrow = 1, ncol = 3),
    centers = matrix(c(5, 5), nrow = 1, ncol = 2)
  )
  patches_b <- list(
    colors = matrix(c(250, 130, 60), nrow = 1, ncol = 3),
    centers = matrix(c(6, 6), nrow = 1, ncol = 2)
  )

  result <- couplr:::.patch_cost_matrix(patches_a, patches_b, alpha = 1, beta = 0.1, H = 20, W = 20)

  expect_true(is.numeric(result))
  expect_true(is.finite(result))
})

test_that(".patch_cost_matrix handles zero diagonal_norm", {
  skip_on_cran()
  # When all centers are at the same point, diag_norm would be 0
  patches_a <- list(
    colors = matrix(c(255, 128, 64), nrow = 1, ncol = 3),
    centers = matrix(c(5, 5), nrow = 1, ncol = 2)
  )
  patches_b <- list(
    colors = matrix(c(250, 130, 60), nrow = 1, ncol = 3),
    centers = matrix(c(5, 5), nrow = 1, ncol = 2)  # Same position
  )

  # When H and W are NULL, it calculates from max(dist(...))
  result <- couplr:::.patch_cost_matrix(patches_a, patches_b, alpha = 1, beta = 0.1)

  expect_true(is.numeric(result))
  expect_true(is.finite(result))
})

# ------------------------------------------------------------------------------
# .expand_patch_assignment edge cases
# ------------------------------------------------------------------------------

test_that(".expand_patch_assignment handles unequal patch sizes", {
  skip_on_cran()
  patch_assign <- list(1L)  # One patch maps to B patch 1
  patches_a <- list(
    indices = list(c(1L, 2L, 3L, 4L))  # 4 pixels in A patch
  )
  patches_b <- list(
    indices = list(c(5L, 6L))  # Only 2 pixels in B patch
  )
  N <- 4

  result <- couplr:::.expand_patch_assignment(patch_assign, patches_a, patches_b, N)

  expect_equal(length(result), 4)
  # Only first 2 A pixels should be assigned (limited by B patch size)
  expect_equal(result[1], 5L)
  expect_equal(result[2], 6L)
  expect_equal(result[3], -1L)
  expect_equal(result[4], -1L)
})

test_that(".expand_patch_assignment handles 0 assignment", {
  skip_on_cran()
  patch_assign <- list(0L, 1L)  # First patch has 0 (invalid)
  patches_a <- list(
    indices = list(c(1L, 2L), c(3L, 4L))
  )
  patches_b <- list(
    indices = list(c(5L, 6L))
  )
  N <- 4

  result <- couplr:::.expand_patch_assignment(patch_assign, patches_a, patches_b, N)

  expect_equal(result[1], -1L)  # Not assigned (patch_assign = 0)
  expect_equal(result[2], -1L)
  expect_equal(result[3], 5L)   # Assigned
  expect_equal(result[4], 6L)
})

# ------------------------------------------------------------------------------
# .exact_cost_and_solve tests
# ------------------------------------------------------------------------------

test_that(".exact_cost_and_solve handles non-square images", {
  skip_on_cran()
  H <- 2
  W <- 3
  N <- H * W

  A_planar <- c(rep(100, N), rep(150, N), rep(200, N))
  B_planar <- c(rep(105, N), rep(145, N), rep(195, N))

  result <- couplr:::.exact_cost_and_solve(A_planar, B_planar, H, W, alpha = 1, beta = 0)

  expect_equal(length(result), N)
  expect_true(all(result >= 1 & result <= N))
})

test_that(".exact_cost_and_solve with spatial component", {
  skip_on_cran()
  H <- 2
  W <- 2
  N <- H * W

  A_planar <- c(rep(100, N), rep(100, N), rep(100, N))
  B_planar <- c(rep(100, N), rep(100, N), rep(100, N))

  # With beta > 0, spatial cost matters
  result <- couplr:::.exact_cost_and_solve(A_planar, B_planar, H, W, alpha = 0, beta = 1)

  expect_equal(length(result), N)
})

# ------------------------------------------------------------------------------
# .upscale_assignment tests
# ------------------------------------------------------------------------------

test_that(".upscale_assignment works", {
  skip_on_cran()
  # Small scaled assignment
  assign_s <- c(0L, 1L, 2L, 3L)  # 2x2 scaled
  H <- 4
  W <- 4
  Hs <- 2
  Ws <- 2

  result <- couplr:::.upscale_assignment(assign_s, H, W, Hs, Ws)

  expect_equal(length(result), H * W)
})

# ------------------------------------------------------------------------------
# .lap_assign edge cases for different return types
# ------------------------------------------------------------------------------

test_that(".lap_assign handles maximize=TRUE", {
  skip_on_cran()
  cost <- matrix(c(1, 5, 5, 1), 2, 2)

  result <- couplr:::.lap_assign(cost, method = "jv", maximize = TRUE)

  expect_equal(length(result), 2)
  expect_true(all(result >= 0))
})

# ------------------------------------------------------------------------------
# .downscale_both with actual downscaling
# ------------------------------------------------------------------------------

test_that(".downscale_both performs actual downscaling", {
  skip_on_cran()
  H <- 16
  W <- 16
  N <- H * W

  A_planar <- runif(N * 3) * 255
  B_planar <- runif(N * 3) * 255

  result <- couplr:::.downscale_both(A_planar, B_planar, H, W, steps = 1)

  # With 1 step, dimensions should be halved (but min 8)
  expect_equal(result$Hs, 8L)
  expect_equal(result$Ws, 8L)
  expect_equal(length(result$A_s), result$Hs * result$Ws * 3)
})

test_that(".downscale_both with negative steps returns original", {
  skip_on_cran()
  H <- 8
  W <- 8
  N <- H * W
  A <- rep(1.0, N * 3)
  B <- rep(2.0, N * 3)

  result <- couplr:::.downscale_both(A, B, H, W, steps = -1)

  expect_equal(result$Hs, H)
  expect_equal(result$Ws, W)
})
